【问题标题】:Hidden features of ScalaScala的隐藏特性
【发布时间】:2009-06-22 01:05:03
【问题描述】:

每个 Scala 开发人员都应该了解的 Scala 隐藏特性是什么?

请每个答案一个隐藏功能。

【问题讨论】:

  • 嘿,这个问题与问题本身一样有用,因为它链接到其他隐藏功能帖子。干杯!
  • @mettadore 只需查看右侧的相关链接即可。
  • @JohnMetta:或者使用tag

标签: scala hidden-features


【解决方案1】:

好的,我必须再添加一个。 Scala 中的每个 Regex 对象都有一个提取器(参见上面 oxbox_lakes 的答案),可以让您访问匹配组。所以你可以这样做:

// Regex to split a date in the format Y/M/D.
val regex = "(\\d+)/(\\d+)/(\\d+)".r
val regex(year, month, day) = "2010/1/13"

如果您不习惯使用模式匹配和提取器,那么第二行看起来很混乱。每当您定义valvar 时,关键字后面的内容不仅仅是一个标识符,而是一个模式。这就是它起作用的原因:

val (a, b, c) = (1, 3.14159, "Hello, world")

右手表达式创建一个Tuple3[Int, Double, String],它可以匹配模式(a, b, c)

大多数时候,您的模式使用作为单例对象成员的提取器。例如,如果你写一个像

这样的模式
Some(value)

然后你隐式调用提取器Some.unapply

但是您也可以在模式中使用类实例,这就是这里发生的事情。 val 正则表达式是Regex 的一个实例,当您在模式中使用它时,您会隐式调用regex.unapplySequnapplyunapplySeq 超出了此答案的范围),它会提取匹配组到 Seq[String] 中,其中的元素按顺序分配给变量年、月和日。

【讨论】:

  • 感谢您发布此内容!仅供参考,在第一版第 503 页和第二版第 611 页的“Scala 编程”一书中的“使用正则表达式提取”一章中提到。
【解决方案2】:

Structural 类型定义 - 即由它支持的方法描述的类型。例如:

object Closer {
    def using(closeable: { def close(): Unit }, f: => Unit) {
      try { 
        f
      } finally { closeable.close }
    }
}

注意参数closeable类型除了有一个close方法外没有定义

【讨论】:

  • 在“Scala 编程”中甚至没有提到结构类型。尽管它们使用反射来调用正确的方法,但它们比其他传递类型的技术要慢一些。 (希望他们能想出一种方法来加快速度。)
  • 而且还可以为它们创建别名,就像外部分配的接口(非常慢):type Closeable = { def close(): Unit }
【解决方案3】:

Type-Constructor Polymorphism(又名更高种类的类型)

例如,如果没有此功能,您可以表达将函数映射到列表以返回另一个列表,或将函数映射到树以返回另一棵树的想法。但是你不能一般地在没有更高种类的情况下表达这个想法。

对于更高的种类,您可以捕捉到 任何类型 的概念,即用另一种类型进行参数化。接受一个参数的类型构造函数称为(*->*)。例如,List。返回另一个类型构造函数的类型构造函数称为(*->*->*)。例如,Function1。但是在 Scala 中,我们有 更高 种类型,因此我们可以拥有使用其他类型构造函数参数化的类型构造函数。所以他们是((*->*)->*)之类的。

例如:

trait Functor[F[_]] {
  def fmap[A, B](f: A => B, fa: F[A]): F[B]
}

现在,如果您有Functor[List],您可以映射列表。如果你有Functor[Tree],你可以映射树。但更重要的是,如果你有Functor[A] 对于任何类型的(*->*),你可以在A 上映射一个函数。

【讨论】:

    【解决方案4】:

    Extractors 允许您用模式替换凌乱的if-elseif-else 样式代码。我知道这些并不完全隐藏,但我已经使用 Scala 几个月了,但并没有真正了解它们的强大功能。对于(长)示例,我可以替换:

    val code: String = ...
    val ps: ProductService = ...
    var p: Product = null
    if (code.endsWith("=")) {
      p = ps.findCash(code.substring(0, 3)) //e.g. USD=, GBP= etc
    }
    else if (code.endsWith(".FWD")) {
      //e.g. GBP20090625.FWD
      p = ps.findForward(code.substring(0,3), code.substring(3, 9))
    }
    else {
      p = ps.lookupProductByRic(code)
    }
    

    有了这个,我认为这更加更清晰

    implicit val ps: ProductService = ...
    val p = code match {
      case SyntheticCodes.Cash(c) => c
      case SyntheticCodes.Forward(f) => f
      case _ => ps.lookupProductByRic(code)
    }
    

    我必须在后台做一些跑腿工作......

    object SyntheticCodes {
      // Synthetic Code for a CashProduct
      object Cash extends (CashProduct => String) {
        def apply(p: CashProduct) = p.currency.name + "="
    
        //EXTRACTOR
        def unapply(s: String)(implicit ps: ProductService): Option[CashProduct] = {
          if (s.endsWith("=") 
            Some(ps.findCash(s.substring(0,3))) 
          else None
        }
      }
      //Synthetic Code for a ForwardProduct
      object Forward extends (ForwardProduct => String) {
        def apply(p: ForwardProduct) = p.currency.name + p.date.toString + ".FWD"
    
        //EXTRACTOR
        def unapply(s: String)(implicit ps: ProductService): Option[ForwardProduct] = {
          if (s.endsWith(".FWD") 
            Some(ps.findForward(s.substring(0,3), s.substring(3, 9)) 
          else None
        }
      }
    

    但跑腿的工作是值得的,因为它将一段业务逻辑分离到一个合理的地方。我可以按如下方式实现我的Product.getCode 方法..

    class CashProduct {
      def getCode = SyntheticCodes.Cash(this)
    }
    
    class ForwardProduct {
      def getCode = SyntheticCodes.Forward(this)     
    }
    

    【讨论】:

    • 这不是开关吗?也许这可以重构更多。
    • 模式就像涡轮增压开关:更加强大和清晰
    • 很好,但我不喜欢你必须使用隐式,因为它的范围比匹配{}更远。您也可以向 ProductService 添加一个通过代码查找 Product 的方法。无论如何,您都可以将重构的 sn-p 包装在一个方法中,以便能够在任何地方使用它。
    【解决方案5】:

    Manifests 这是一种在运行时获取类型信息的方法,就好像 Scala 已经具体化了类型一样。

    【讨论】:

    • 我认为最好在答案中解释答案in,而不是参考链接。顺便说一句,嗨再次牛弓! :-)
    • 这是一个真正隐藏的功能……甚至在 API 文档中也没有。不过非常有用。
    【解决方案6】:

    在 scala 2.8 中,您可以使用包 scala.util.control.TailCalls 获得尾递归方法(实际上它是蹦床)。

    一个例子:

    def u(n:Int):TailRec[Int] = {
      if (n==0) done(1)
      else tailcall(v(n/2))
    }
    def v(n:Int):TailRec[Int] = {
      if (n==0) done(5)
      else tailcall(u(n-1))
    }
    val l=for(n<-0 to 5) yield (n,u(n).result,v(n).result)
    println(l)
    

    【讨论】:

      【解决方案7】:

      案例类自动混合 Product trait,提供对字段的无类型、索引访问,无需任何反射:

      case class Person(name: String, age: Int)
      
      val p = Person("Aaron", 28)
      val name = p.productElement(0) // name = "Aaron": Any
      val age = p.productElement(1) // age = 28: Any
      val fields = p.productIterator.toList // fields = List[Any]("Aaron", 28)
      

      此功能还提供了一种更改toString 方法输出的简化方法:

      case class Person(name: String, age: Int) {
         override def productPrefix = "person: "
      }
      
      // prints "person: (Aaron,28)" instead of "Person(Aaron, 28)"
      println(Person("Aaron", 28)) 
      

      【讨论】:

        【解决方案8】:

        它并不是完全隐藏的,但肯定是一个宣传不足的功能:scalac -Xprint

        作为使用说明,请考虑以下来源:

        class A { "xx".r }
        

        scalac -Xprint:typer 编译这个输出:

        package <empty> {
          class A extends java.lang.Object with ScalaObject {
            def this(): A = {
              A.super.this();
              ()
            };
            scala.this.Predef.augmentString("xx").r
          }
        }
        

        注意 scala.this.Predef.augmentString("xx").r,这是 Predef.scala 中 implicit def augmentString 的应用程序。

        scalac -Xprint: 将在某个编译器阶段之后打印语法树。要查看可用的阶段,请使用 scalac -Xshow-phases

        这是了解幕后情况的好方法。

        试试

        case class X(a:Int,b:String)

        使用 typer 阶段来真正感受它的用处。

        【讨论】:

          【解决方案9】:

          您可以定义自己的控制结构。它实际上只是函数和对象以及一些语法糖,但它们的外观和行为就像真实的东西。

          例如,下面的代码定义了dont {...} unless (cond)dont {...} until (cond)

          def dont(code: => Unit) = new DontCommand(code)
          
          class DontCommand(code: => Unit) {
            def unless(condition: => Boolean) =
              if (condition) code
          
            def until(condition: => Boolean) = {
              while (!condition) {}
              code
            }
          }
          

          现在您可以执行以下操作:

          /* This will only get executed if the condition is true */
          dont {
            println("Yep, 2 really is greater than 1.")
          } unless (2 > 1) 
          
          /* Just a helper function */
          var number = 0;
          def nextNumber() = {
            number += 1
            println(number)
            number
          }
          
          /* This will not be printed until the condition is met. */
          dont {
            println("Done counting to 5!")
          } until (nextNumber() == 5) 
          

          【讨论】:

          • 我很好奇是否有人知道一种方法来定义 if-then-else 块与可选的 else 像标准的类型检查一样。
          • @Philippe:zif[A : Zero](cond: =&gt; Boolean)(t: =&gt; A): A = if(cond) t else mzero。需要 Scalaz。
          【解决方案10】:

          Scala 2.8 中的@switch 注释:

          要应用于匹配的注释 表达。如果存在,编译器 将验证匹配已 编译到 tableswitch 或 lookupswitch,如果它发出错误 而是编译成一系列 条件表达式。

          例子:

          scala> val n = 3
          n: Int = 3
          
          scala> import annotation.switch
          import annotation.switch
          
          scala> val s = (n: @switch) match {
               |   case 3 => "Three"
               |   case _ => "NoThree"
               | }
          <console>:6: error: could not emit switch for @switch annotated match
                 val s = (n: @switch) match {
          

          【讨论】:

            【解决方案11】:

            不知道这是否真的隐藏,但我觉得很好。

            接受 2 个类型参数的类型构造函数可以用中缀表示法编写

            object Main {                                                                   
              class FooBar[A, B]
            
              def main(args: Array[String]): Unit = {
                var x: FooBar[Int, BigInt] = null
                var y: Int FooBar BigInt   = null
              }
            }
            

            【讨论】:

            • 不错!我可以想象这有时对提高可读性很有用。例如var foo2barConverter: Foo ConvertTo Bar 会使类型参数的顺序不言自明。
            • 我有时会在某种程度上使用 PartialFunction 的代码中这样做:键入 ~>[A, B] = PartialFunction[A, B]
            【解决方案12】:

            Scala 2.8 引入了默认参数和命名参数,这使得 Scala 添加到案例类的新“复制”方法成为可能。如果你定义这个:

            case class Foo(a: Int, b: Int, c: Int, ... z:Int)
            

            如果你想创建一个新的 Foo 就像现有的 Foo 一样,只是具有不同的“n”值,那么你可以说:

            foo.copy(n = 3)
            

            【讨论】:

            • 警告:如果您从另一个案例类继承一个案例类,则不会覆盖复制方法。所以你必须手动覆盖它
            • 相关:更新嵌套结构的更简洁方法stackoverflow.com/q/3900307/203968
            • 案例类不再(Scala 2.8)允许从案例类继承。感谢 Scala 之主弃用了这种邪恶的继承。
            【解决方案13】:

            在 Scala 2.8 中,您可以将 @specialized 添加到您的通用类/方法中。这将为原始类型创建特殊版本的类(扩展 AnyVal)并节省不必要的装箱/拆箱成本: class Foo[@specialized T]...

            您可以选择 AnyVals 的子集: class Foo[@specialized(Int,Boolean) T]...

            【讨论】:

            • 您是否可以指出一个更长的解释?我想了解更多。
            【解决方案14】:

            扩展语言。我一直想在 Java 中做这样的事情(不能)。但在 Scala 中我可以:

              def timed[T](thunk: => T) = {
                val t1 = System.nanoTime
                val ret = thunk
                val time = System.nanoTime - t1
                println("Executed in: " + time/1000000.0 + " millisec")
                ret
              }
            

            然后写:

            val numbers = List(12, 42, 3, 11, 6, 3, 77, 44)
            val sorted = timed {   // "timed" is a new "keyword"!
              numbers.sortWith(_<_)
            }
            println(sorted)
            

            得到

            Executed in: 6.410311 millisec
            List(3, 3, 6, 11, 12, 42, 44, 77)
            

            【讨论】:

              【解决方案15】:

              您可以为函数指定一个按名称调用的参数(已编辑:这与惰性参数不同!),并且在函数使用之前不会对其进行评估(编辑:实际上,它将在每个使用时间)。详情见this faq

              class Bar(i:Int) {
                  println("constructing bar " + i)
                  override def toString():String = {
                      "bar with value: " + i
                  }
              }
              
              // NOTE the => in the method declaration.  It indicates a lazy paramter
              def foo(x: => Bar) = {
                  println("foo called")
                  println("bar: " + x)
              }
              
              
              foo(new Bar(22))
              
              /*
              prints the following:
              foo called
              constructing bar 22
              bar with value: 22
              */
              

              【讨论】:

              • 我认为“x: => Bar”意味着 x 是一个不带参数并返回 Bar 的函数。因此,“new bar(22)”只是一个匿名函数,并且像任何其他函数一样被评估为函数。
              • "x: ()=>Bar" 定义 x 一个不带参数并返回 Bar 的函数。 x: => Bar 将 x 定义为按名称调用。更多详情请关注scala.sygneca.com/faqs/…
              • 您显示的是按名称调用的参数。延迟参数尚未实现:lampsvn.epfl.ch/trac/scala/ticket/240
              【解决方案16】:

              您可以使用locally 引入本地块,而不会导致分号推断问题。

              用法:

              scala> case class Dog(name: String) {
                   |   def bark() {
                   |     println("Bow Vow")
                   |   }
                   | }
              defined class Dog
              
              scala> val d = Dog("Barnie")
              d: Dog = Dog(Barnie)
              
              scala> locally {
                   |   import d._
                   |   bark()
                   |   bark()
                   | }
              Bow Vow
              Bow Vow
              

              locally 在“Predef.scala”中定义为:

              @inline def locally[T](x: T): T = x
              

              作为内联,它不会带来任何额外的开销。

              【讨论】:

              【解决方案17】:

              Early Initialization:

              trait AbstractT2 {
                println("In AbstractT2:")
                val value: Int
                val inverse = 1.0/value
                println("AbstractT2: value = "+value+", inverse = "+inverse)
              }
              
              val c2c = new {
                // Only initializations are allowed in pre-init. blocks.
                // println("In c2c:")
                val value = 10
              } with AbstractT2
              
              println("c2c.value = "+c2c.value+", inverse = "+c2c.inverse)
              

              输出:

              In AbstractT2:  
              AbstractT2: value = 10, inverse = 0.1  
              c2c.value = 10, inverse = 0.1
              

              我们实例化一个匿名内部 类,初始化value 字段 在块中,在with AbstractT2 子句之前。这保证 valueAbstractT2 的主体被执行,如 运行脚本时显示。

              【讨论】:

              • 该构造称为“早期初始化”。
              【解决方案18】:

              您可以使用 'with' 关键字组合结构类型

              object Main {
                type A = {def foo: Unit}
                type B = {def bar: Unit}
              
                type C = A with B
              
                class myA {
                  def foo: Unit = println("myA.foo")
                }
              
              
                class myB {
                  def bar: Unit = println("myB.bar")
                }
                class myC extends myB {
                  def foo: Unit = println("myC.foo")
                }
              
                def main(args: Array[String]): Unit = { 
                  val a: A = new myA 
                  a.foo
                  val b: C = new myC 
                  b.bar
                  b.foo
                }
              }
              

              【讨论】:

                【解决方案19】:

                匿名函数的占位符语法

                来自 Scala 语言规范:

                SimpleExpr1 ::= '_'
                

                一个表达式(句法类别Expr)可以在标识符合法的地方包含嵌入的下划线符号_。这样的表达式表示一个匿名函数,其中后续出现的下划线表示连续的参数。

                来自Scala Language Changes

                _ + 1                  x => x + 1
                _ * _                  (x1, x2) => x1 * x2
                (_: Int) * 2           (x: Int) => x * 2
                if (_) x else y        z => if (z) x else y
                _.map(f)               x => x.map(f)
                _.map(_ + 1)           x => x.map(y => y + 1)
                

                使用它,您可以执行以下操作:

                def filesEnding(query: String) =
                  filesMatching(_.endsWith(query))
                

                【讨论】:

                • 这应该被称为“匿名函数的占位符语法”。隐式在 Scala 中有不同的含义,与此无关。
                • 链接与答案的关系不明显。 “隐式”不是这个的正确术语。如上所述,它应该是“占位符”。
                • 它并不是真正的“隐藏”,我在我读过的几乎所有关于 Scala 的教程中都看到了这种用法...... :-) 但我很欣赏我还没有看到的正式定义。
                • @PhiLho 也许它在 2009 年不太为人所知。我不知道。
                • 我错过了原始日期,因为只显示了最后一次编辑日期。而且,并非此线程中解释的所有功能都是“隐藏的”。无论如何,很酷的线程和好的答案。
                【解决方案20】:

                隐式定义,尤其是转换。

                例如,假设一个函数将格式化输入字符串以适应大小,方法是将其中间替换为“...”:

                def sizeBoundedString(s: String, n: Int): String = {
                  if (n < 5 && n < s.length) throw new IllegalArgumentException
                  if (s.length > n) {
                    val trailLength = ((n - 3) / 2) min 3
                    val headLength = n - 3 - trailLength
                    s.substring(0, headLength)+"..."+s.substring(s.length - trailLength, s.length)
                  } else s
                }
                

                您可以将它与任何字符串一起使用,当然,也可以使用 toString 方法来转换任何内容。但你也可以这样写:

                def sizeBoundedString[T](s: T, n: Int)(implicit toStr: T => String): String = {
                  if (n < 5 && n < s.length) throw new IllegalArgumentException
                  if (s.length > n) {
                    val trailLength = ((n - 3) / 2) min 3
                    val headLength = n - 3 - trailLength
                    s.substring(0, headLength)+"..."+s.substring(s.length - trailLength, s.length)
                  } else s
                }
                

                然后,您可以通过以下方式传递其他类型的类:

                implicit def double2String(d: Double) = d.toString
                

                现在您可以通过双精度调用该函数:

                sizeBoundedString(12345.12345D, 8)
                

                最后一个参数是隐式的,并且由于隐式 de 声明而被自动传递。此外,“s”在 sizeBoundedString 内被处理为字符串,因为从它到字符串的隐式转换。

                这种类型的隐式更好地定义为不常见的类型,以避免意外的转换。你也可以显式传递一个转换,它仍然会在 sizeBoundedString 内部隐式使用:

                sizeBoundedString(1234567890L, 8)((l : Long) => l.toString)
                

                你也可以有多个隐式参数,但是你必须要么全部传递,要么不传递任何一个。还有一种隐式转换的快捷语法:

                def sizeBoundedString[T <% String](s: T, n: Int): String = {
                  if (n < 5 && n < s.length) throw new IllegalArgumentException
                  if (s.length > n) {
                    val trailLength = ((n - 3) / 2) min 3
                    val headLength = n - 3 - trailLength
                    s.substring(0, headLength)+"..."+s.substring(s.length - trailLength, s.length)
                  } else s
                }
                

                这个用法完全一样。

                隐式可以有任何值。例如,它们可用于隐藏图书馆信息。以下面的例子为例:

                case class Daemon(name: String) {
                  def log(msg: String) = println(name+": "+msg)
                }
                
                object DefaultDaemon extends Daemon("Default")
                
                trait Logger {
                  private var logd: Option[Daemon] = None
                  implicit def daemon: Daemon = logd getOrElse DefaultDaemon
                
                  def logTo(daemon: Daemon) = 
                    if (logd == None) logd = Some(daemon) 
                    else throw new IllegalArgumentException
                
                  def log(msg: String)(implicit daemon: Daemon) = daemon.log(msg)
                }
                
                class X extends Logger {
                  logTo(Daemon("X Daemon"))
                
                  def f = {
                    log("f called")
                    println("Stuff")
                  }
                
                  def g = {
                    log("g called")(DefaultDaemon)
                  }
                }
                
                class Y extends Logger {
                  def f = {
                    log("f called")
                    println("Stuff")
                  }
                }
                

                在此示例中,在 Y 对象中调用“f”会将日志发送到默认守护程序,并在 X 的实例上将日志发送到守护程序 X 守护程序。但是在 X 的实例上调用 g 会将日志发送到显式给定的 DefaultDaemon。

                虽然这个简单的示例可以用重载和私有状态重写,但隐式不需要私有状态,并且可以通过导入进入上下文。

                【讨论】:

                  【解决方案21】:

                  也许不是太隐蔽,但我认为这很有用:

                  @scala.reflect.BeanProperty
                  var firstName:String = _
                  

                  这将自动为匹配 bean 约定的字段生成 getter 和 setter。

                  developerworks的进一步说明

                  【讨论】:

                  • 如果你经常使用它,你可以为它制作快捷方式,例如:import scala.reflect.{BeanProperty => BP}
                  【解决方案22】:

                  闭包中的隐式参数。

                  函数参数可以像方法一样被标记为隐式。在函数体的范围内,隐式参数是可见的并且有资格进行隐式解析:

                  trait Foo { def bar }
                  
                  trait Base {
                    def callBar(implicit foo: Foo) = foo.bar
                  }
                  
                  object Test extends Base {
                    val f: Foo => Unit = { implicit foo =>
                      callBar
                    }
                    def test = f(new Foo {
                      def bar = println("Hello")
                    })
                  }
                  

                  【讨论】:

                    【解决方案23】:

                    使用 Scala 的 Streams 构建无限数据结构: http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient

                    【讨论】:

                      【解决方案24】:

                      结果类型取决于隐式解析。这可以给你一种多分派的形式:

                      scala> trait PerformFunc[A,B] { def perform(a : A) : B }
                      defined trait PerformFunc
                      
                      scala> implicit val stringToInt = new PerformFunc[String,Int] {
                        def perform(a : String)  = 5
                      }
                      stringToInt: java.lang.Object with PerformFunc[String,Int] = $anon$1@13ccf137
                      
                      scala> implicit val intToDouble = new PerformFunc[Int,Double] {
                        def perform(a : Int) = 1.0
                      }
                      intToDouble: java.lang.Object with PerformFunc[Int,Double] = $anon$1@74e551a4
                      
                      scala> def foo[A, B](x : A)(implicit z : PerformFunc[A,B]) : B = z.perform(x)
                      foo: [A,B](x: A)(implicit z: PerformFunc[A,B])B
                      
                      scala> foo("HAI")
                      res16: Int = 5
                      
                      scala> foo(1)
                      res17: Double = 1.0
                      

                      【讨论】:

                      • 可能是这样,但上面的会话具有误导性。 foo 的定义使用了a,在执行这些命令之前,该a 必须存在于环境中。我猜你的意思是z.perform(x)
                      【解决方案25】:

                      Scala 等效于 Java 双括号初始化器。

                      Scala 允许您使用包含初始化该类实例的语句的类主体(构造函数)创建一个匿名子类。

                      这种模式在构建基于组件的用户界面(例如 Swing、Vaadin)时非常有用,因为它允许创建 UI 组件并更简洁地声明它们的属性。

                      更多信息请参见http://spot.colorado.edu/~reids/papers/how-scala-experience-improved-our-java-development-reid-2011.pdf

                      以下是创建 Vaadin 按钮的示例:

                      val button = new Button("Click me"){
                       setWidth("20px")
                       setDescription("Click on this")
                       setIcon(new ThemeResource("icons/ok.png"))
                      }
                      

                      【讨论】:

                        【解决方案26】:

                        import 语句中排除成员

                        假设您想使用包含printlnprinterr 方法的Logger,但您只想将一个用于错误消息,并保留旧的Predef.println 用于标准输出。你可以这样做:

                        val logger = new Logger(...)
                        import logger.printerr
                        

                        但如果logger 还包含另外十二个您想要导入和使用的方法,则列出它们变得不方便。你可以试试:

                        import logger.{println => donotuseprintlnt, _}
                        

                        但这仍然“污染”了导入成员的列表。输入超强大的通配符:

                        import logger.{println => _, _}
                        

                        这将做正确的事情™。

                        【讨论】:

                          【解决方案27】:

                          require 方法(在Predef 中定义)允许您定义将在运行时检查的其他函数约束。想象一下,您正在开发另一个 Twitter 客户端,并且您需要将推文长度限制为最多 140 个符号。此外,您不能发布空推文。

                          def post(tweet: String) = {
                            require(tweet.length < 140 && tweet.length > 0) 
                            println(tweet)
                           }
                          

                          现在使用不适当的长度参数调用 post 将导致异常:

                          scala> post("that's ok")
                          that's ok
                          
                          scala> post("")
                          java.lang.IllegalArgumentException: requirement failed
                              at scala.Predef$.require(Predef.scala:145)
                              at .post(<console>:8)
                          
                          scala> post("way to looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong tweet") 
                          java.lang.IllegalArgumentException: requirement failed
                              at scala.Predef$.require(Predef.scala:145)
                              at .post(<console>:8)
                          

                          您可以编写多个需求,甚至可以为每个需求添加描述:

                          def post(tweet: String) = {
                            require(tweet.length > 0, "too short message")
                            require(tweet.length < 140, "too long message")
                            println(tweet)
                          }
                          

                          现在例外很详细:

                          scala> post("")
                          java.lang.IllegalArgumentException: requirement failed: too short message
                              at scala.Predef$.require(Predef.scala:157)
                              at .post(<console>:8)
                          

                          还有一个例子是here


                          奖金

                          您可以在每次要求失败时执行操作:

                          scala> var errorcount = 0
                          errorcount: Int = 0
                          
                          def post(tweet: String) = {
                            require(tweet.length > 0, {errorcount+=1})
                            println(tweet)
                            }
                          
                          scala> errorcount
                          res14: Int = 0
                          
                          scala> post("")
                          java.lang.IllegalArgumentException: requirement failed: ()
                              at scala.Predef$.require(Predef.scala:157)
                              at .post(<console>:9)
                          ...
                          
                          scala> errorcount
                          res16: Int = 1
                          

                          【讨论】:

                          • require 不是保留字。它只是Predef中定义的一种方法。
                          【解决方案28】:

                          具有 abstract override 方法的特征是 Scala 中的一个特性,它不像许多其他特性那样被广泛宣传。带有abstract override 修饰符的方法的目的是执行一些操作并将调用委托给super。然后必须将这些特征与其abstract override 方法的具体实现混合在一起。

                          trait A {
                            def a(s : String) : String
                          }
                          
                          trait TimingA extends A {
                            abstract override def a(s : String) = {
                              val start = System.currentTimeMillis
                              val result = super.a(s)
                              val dur = System.currentTimeMillis-start
                              println("Executed a in %s ms".format(dur))
                              result
                            }
                          }
                          
                          trait ParameterPrintingA extends A {
                            abstract override def a(s : String) = {
                              println("Called a with s=%s".format(s))
                              super.a(s)
                            }
                          }
                          
                          trait ImplementingA extends A {
                            def a(s: String) = s.reverse
                          }
                          
                          scala> val a = new ImplementingA with TimingA with ParameterPrintingA
                          
                          scala> a.a("a lotta as")
                          Called a with s=a lotta as
                          Executed a in 0 ms
                          res4: String = sa attol a
                          

                          虽然我的示例实际上只是一个可怜的 AOP,但我非常喜欢使用这些 Stackable Traits 来构建具有预定义导入、自定义绑定和类路径的 Scala 解释器实例。 Stackable Traits 可以按照new InterpreterFactory with JsonLibs with LuceneLibs 的方式创建我的工厂,然后为用户脚本提供有用的导入和范围变量。

                          【讨论】:

                            猜你喜欢
                            • 2011-03-19
                            • 1970-01-01
                            • 2010-11-07
                            • 2010-09-15
                            • 2010-11-07
                            • 2010-09-22
                            • 2010-10-31
                            • 2011-01-30
                            • 2010-10-31
                            相关资源
                            最近更新 更多