Hidden features of Scala
每个scala开发人员都应该了解scala的哪些隐藏特性?
请为每个答案提供一个隐藏功能。
好吧,我得再加一个。scala中的每个
1 2 3 |
如果不习惯使用模式匹配和提取器,那么第二行看起来很混乱。每当您定义一个
1 |
右手边的表达式创建了一个与模式
大多数情况下,您的模式使用的提取器是singleton对象的成员。例如,如果编写类似
1 | Some(value) |
然后您隐式地调用提取器
但是您也可以在模式中使用类实例,这就是这里所发生的。val regex是
结构类型定义——即通过其支持的方法描述的类型。例如:
1 2 3 4 5 6 7 |
注意参数
类型构造器多态性(A.K.A.更高类型)
例如,如果没有这个特性,您可以表示在列表上映射函数以返回另一个列表,或者在树上映射函数以返回另一个树。但是如果没有更高的种类,你就不能一般地表达这个想法。
对于更高的类型,您可以捕获与其他类型参数化的任何类型的想法。接受一个参数的类型构造函数被称为
例如:
现在,如果您有一个
提取器,允许您用模式替换混乱的
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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) } |
有了这个,在我看来更清楚了
1 2 3 4 5 6 |
我得在后台做点腿部运动…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 } } |
但这项立法工作是值得的,因为它将一个业务逻辑分割成一个合理的地方。我可以实现我的
1 2 3 4 5 6 7 |
case类自动混合产品特征,提供对字段的非类型化索引访问,而不进行任何反射:
1 2 3 4 5 6 |
此功能还提供了一种简化的方法来更改
1 2 3 4 5 6 |
清单是在运行时获取类型信息的一种方式,就好像scala已经重新定义了类型。
在scala 2.8中,可以通过使用scala.util.control.tailcalls包(实际上是蹦床)来使用tail递归方法。
一个例子:
1 2 3 4 5 6 7 8 9 10 |
它并不是完全隐藏的,但肯定是一个未被宣传的特性:scalac-xprint。
作为使用说明,请考虑以下来源:
1 |
用scalac-xprint编译:typer输出:
1 2 3 4 5 6 7 8 9 |
注意
scalac-xprint:
这是一个了解幕后情况的好方法。
尝试用
使用typer阶段来真正感觉它有多有用。
您可以定义自己的控制结构。它实际上只是函数和对象以及一些语法上的糖分,但是它们看起来和行为都像真实的东西。
例如,以下代码定义了
1 2 3 4 5 6 7 8 9 10 11 |
现在您可以执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /* 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) |
不知道这是不是真的被隐藏了,但我觉得很好。
采用2个类型参数的类型构造函数可以用中缀表示法编写。
1 2 3 4 5 6 7 8 |
scala 2.8中的
An annotation to be applied to a match
expression. If present, the compiler
will verify that the match has been
compiled to a tableswitch or
lookupswitch, and issue an error if it
instead compiles into a series of
conditional expressions.
例子:
1 2 3 4 5 6 7 8 9 10 11 12 |
在scala 2.8中,可以将@specialized添加到泛型类/方法中。这将为基元类型(扩展anyval)创建类的特殊版本,并节省不必要的装箱/拆箱成本:
您可以选择任意值的子集:
scala 2.8引入了默认参数和命名参数,这使得添加一个新的"copy"方法成为可能,scala将该方法添加到case类中。如果您定义了这一点:
你想创建一个新的foo,就像一个现有的foo,只有一个不同的"n"值,然后你可以说:
1 | foo.copy(n = 3) |
可以按名称参数指定调用(已编辑:这与惰性参数不同!)对于一个函数,在函数使用之前不会对其进行评估(编辑:实际上,每次使用它时都会对其进行重新评估)。有关详细信息,请参阅本常见问题解答。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 */ |
扩展语言。我一直想在Java中做这样的事情(不能)。但在斯卡拉,我可以有:
1 2 3 4 5 6 7 |
然后写:
1 2 3 4 5 |
得到
1 2 | Executed in: 6.410311 millisec List(3, 3, 6, 11, 12, 42, 44, 77) |
您可以使用
用途:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
1 |
作为内联的,它不会施加任何额外的开销。
匿名函数的占位符语法
从scala语言规范:
1 | SimpleExpr1 ::= '_' |
An expression (of syntactic category
Expr ) may contain embedded underscore symbols_ at places where identifiers are legal. Such an expression represents an anonymous function where subsequent occurrences of underscores denote successive parameters.
从scala语言更改:
1 2 3 4 5 6 |
使用这个你可以做如下的事情:
1 2 |
早期初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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) |
输出:
1 2 3 | In AbstractT2: AbstractT2: value = 10, inverse = 0.1 c2c.value = 10, inverse = 0.1 |
We instantiate an anonymous inner
class, initializing thevalue field
in the block, before thewith clause. This guarantees
AbstractT2
thatvalue is initialized before the
body ofAbstractT2 is executed, as
shown when you run the script.
可以使用"with"关键字组合结构类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 } } |
隐式定义,尤其是转换。
例如,假设一个函数将格式化一个输入字符串以适应某个大小,方法是将其中间替换为"…":
1 2 3 4 5 6 7 8 |
您可以将它与任何字符串一起使用,当然,还可以使用ToString方法来转换任何内容。但你也可以这样写:
1 2 3 4 5 6 7 8 | 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 } |
然后,您可以通过这样做来传递其他类型的类:
现在可以调用传递double的函数:
1 | sizeBoundedString(12345.12345D, 8) |
最后一个参数是隐式的,由于隐式de声明,正在自动传递。此外,"s"被视为sizebounderstring中的字符串,因为存在从它到字符串的隐式转换。
这种类型的隐式更好地为不常见的类型定义,以避免意外的转换。您还可以明确地传递转换,它仍将在sizebounderstring中隐式使用:
1 | sizeBoundedString(1234567890L, 8)((l : Long) => l.toString) |
您也可以有多个隐式参数,但是必须传递所有参数,或者不传递任何参数。还有一个用于隐式转换的快捷语法:
1 2 3 4 5 6 7 8 | 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 } |
使用方法完全相同。
implicit可以有任何值。例如,它们可以用来隐藏库信息。举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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会将日志发送到显式给定的默认守护进程。
虽然这个简单的示例可以用重载和私有状态重新编写,但隐式不需要私有状态,并且可以通过导入引入上下文。
闭包中的隐式参数。
函数参数可以像方法一样标记为隐式。在函数体的范围内,隐式参数可见并符合隐式解析的条件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
也许不是太隐蔽,但我认为这是有用的:
1 2 |
这将自动为符合bean约定的字段生成getter和setter。
developerWorks的进一步说明
结果类型依赖于隐式解析。这可以为您提供一种多重分派的形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 |
用scala的
Scala等效的Java双括号初始化器。
scala允许您使用包含语句的类主体(构造函数)创建匿名子类,以初始化该类的实例。
当构建基于组件的用户界面(例如Swing、Vaadin)时,此模式非常有用,因为它允许创建UI组件并更简洁地声明其属性。
有关详细信息,请参阅http://spot.colladora.edu/~reids/papers/how-scala-experience-improved-our-java-development-reid-2011.pdf。
下面是创建vaadin按钮的示例:
1 2 3 4 5 |
从
想你想使用
但如果
1 |
但这仍然是"the list of imported pollutes"成员。wildcard:Enter theü- powerfulP></
1 |
那会给你只是the right thing贸;。P></
1 2 3 4 |
现在,使用不适当的长度参数调用post将导致异常:
1 2 3 4 5 6 7 8 9 10 11 12 | 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) |
您可以编写多个需求,甚至可以为每个需求添加描述:
1 2 3 4 5 | def post(tweet: String) = { require(tweet.length > 0,"too short message") require(tweet.length < 140,"too long message") println(tweet) } |
现在例外是详细的:
1 2 3 4 | scala> post("") java.lang.IllegalArgumentException: requirement failed: too short message at scala.Predef$.require(Predef.scala:157) at .post(<console>:8) |
这里还有一个例子。
奖金每次需求失败时,您都可以执行一个操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 |
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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 |
虽然我的示例实际上并不比一个可怜的mansAOP多,但我非常喜欢使用这些可堆叠特性来构建带有预定义导入、自定义绑定和类路径的scala解释器实例。这些可堆叠的特性使我们可以沿着