Scala的隐藏功能

Hidden features of Scala

每个scala开发人员都应该了解scala的哪些隐藏特性?

请为每个答案提供一个隐藏功能。


好吧,我得再加一个。scala中的每个Regex对象都有一个提取器(请参阅上面OxBox_Lakes的答案),可以访问匹配组。所以你可以这样做:

1
2
3
// 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时,关键字后面的不仅仅是一个标识符,而是一个模式。这就是为什么这是有效的:

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

右手边的表达式创建了一个与模式(a, b, c)匹配的Tuple3[Int, Double, String]

大多数情况下,您的模式使用的提取器是singleton对象的成员。例如,如果编写类似

1
Some(value)

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

但是您也可以在模式中使用类实例,这就是这里所发生的。val regex是Regex的一个实例,当您以一种模式使用它时,您隐式地称为regex.unapplySeq(unapplyunapplySeq超出了这个答案的范围),它将匹配组提取到Seq[String]中,元素按年、月、日的顺序分配。


结构类型定义——即通过其支持的方法描述的类型。例如:

1
2
3
4
5
6
7
object Closer {
    def using(closeable: { def close(): Unit }, f: => Unit) {
      try {
        f
      } finally { closeable.close }
    }
}

注意参数closeable的类型没有定义,只是它有一个close方法。


类型构造器多态性(A.K.A.更高类型)

例如,如果没有这个特性,您可以表示在列表上映射函数以返回另一个列表,或者在树上映射函数以返回另一个树。但是如果没有更高的种类,你就不能一般地表达这个想法。

对于更高的类型,您可以捕获与其他类型参数化的任何类型的想法。接受一个参数的类型构造函数被称为(*->*)类型。例如,List。返回另一个类型构造函数的类型构造函数被称为(*->*->*)类型。例如,Function1。但是在scala中,我们有更高的类型,所以我们可以有与其他类型构造函数参数化的类型构造函数。所以它们有点像((*->*)->*)

例如:

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

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


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

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
implicit val ps: ProductService = ...
val p = code match {
  case SyntheticCodes.Cash(c) => c
  case SyntheticCodes.Forward(f) => f
  case _ => ps.lookupProductByRic(code)
}

我得在后台做点腿部运动…

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
    }
  }

但这项立法工作是值得的,因为它将一个业务逻辑分割成一个合理的地方。我可以实现我的Product.getCode方法,如下所示。

1
2
3
4
5
6
7
class CashProduct {
  def getCode = SyntheticCodes.Cash(this)
}

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


case类自动混合产品特征,提供对字段的非类型化索引访问,而不进行任何反射:

1
2
3
4
5
6
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方法的输出:

1
2
3
4
5
6
case class Person(name: String, age: Int) {
   override def productPrefix ="person:"
}

// prints"person: (Aaron,28)" instead of"Person(Aaron, 28)"
println(Person("Aaron", 28))

清单是在运行时获取类型信息的一种方式,就好像scala已经重新定义了类型。


在scala 2.8中,可以通过使用scala.util.control.tailcalls包(实际上是蹦床)来使用tail递归方法。

一个例子:

1
2
3
4
5
6
7
8
9
10
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)


它并不是完全隐藏的,但肯定是一个未被宣传的特性:scalac-xprint。

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

1
class A {"xx".r }

用scalac-xprint编译:typer输出:

1
2
3
4
5
6
7
8
9
package  {
  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阶段。

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

尝试用

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

使用typer阶段来真正感觉它有多有用。


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

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

1
2
3
4
5
6
7
8
9
10
11
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
  }
}

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

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
object Main {                                                                  
  class FooBar[A, B]

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


scala 2.8中的@switch注释:

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> 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 {

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

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


scala 2.8引入了默认参数和命名参数,这使得添加一个新的"copy"方法成为可能,scala将该方法添加到case类中。如果您定义了这一点:

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

你想创建一个新的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
  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
  }

然后写:

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

得到

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

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

用途:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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"中定义为:

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

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


匿名函数的占位符语法

从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                  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)

使用这个你可以做如下的事情:

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


早期初始化:

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 the value field
in the block, before the with
AbstractT2
clause. This guarantees
that value is initialized before the
body of AbstractT2 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
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方法来转换任何内容。但你也可以这样写:

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
}

然后,您可以通过这样做来传递其他类型的类:

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

现在可以调用传递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
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")
  })
}

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

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

这将自动为符合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的Streams建立无限数据结构:http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitially-patient


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
val button = new Button("Click me"){
 setWidth("20px")
 setDescription("Click on this")
 setIcon(new ThemeResource("icons/ok.png"))
}

importexcluding成员声明

想你想使用Loggerthat contains a printlnprinterrmethod,但你只想使用一个for error messages,保持好和老Predef.printlnfor标准输出。本:你可以给我P></

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

但如果Loggercontains方法也一样,你会到十二inconvenient进出口和利用,它becomes them to list。你可以尝试:insteadP></

1
import logger.{println => donotuseprintlnt, _}

但这仍然是"the list of imported pollutes"成员。wildcard:Enter theü- powerfulP></

1
import logger.{println => _, _}

那会给你只是the right thing贸;。P></


require方法(在Predef中定义),允许您定义在运行时检查的其他函数约束。想象一下,你开发了另一个Twitter客户端,你需要将tweet的长度限制在140个符号以内。而且你不能发布空的tweet。

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

现在,使用不适当的长度参数调用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


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

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解释器实例。这些可堆叠的特性使我们可以沿着new InterpreterFactory with JsonLibs with LuceneLibs的路线创建工厂,然后为用户脚本提供有用的导入和范围变量。