Scala的隐藏特性

149

Scala 的哪些隐藏特性是每个 Scala 开发者都应该知道的?

请每个回答只列举一个隐藏特性。


6
嘿,这个问题之所以有用,是因为它链接到其他隐藏功能帖子,而不仅仅是问题本身。干杯! - JohnMetta
1
@mettadore,只需查看右侧的相关链接即可。 - Daniel C. Sobral
2
@JohnMetta:或者使用 tag - Roger Pate
28个回答

85

好的,我需要再补充一点。在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的一个实例,当你在模式中使用它时,你隐式地调用了regex.unapplySequnapplyunapplySeq超出了本回答的范围),它将匹配组提取到Seq[String]中,其中元素按顺序分配给变量year、month和day。


1
感谢您的发布!请注意,这个问题在《Scala编程》一书第一版503页和第二版611页中“使用正则表达式提取”一章中有所提及。 - earthling paul

51

结构类型定义 - 即由其支持的方法描述的类型。例如:

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

请注意参数closeable的类型未定义,但它有一个close方法。


1
《Scala编程》中甚至没有提到结构类型。然而,它们与其他传递类型的技术相比稍微慢一些,因为它们使用反射调用正确的方法。(希望他们能想出加速这一过程的方法。) - Ken Bloom
1
还有一种可能是为它们创建别名,这类似于外部分配的接口(非常慢):类型 Closeable = { def close(): Unit } - Alexey

45

类型构造器的多态性(又名高阶类型)

如果没有这个功能,你可以用一个列表来表达将函数映射到另一个列表并返回,或者将函数映射到树上并返回另一棵树的想法。但你不能通用地表达这个想法而不使用高阶类型。

有了高阶类型,你可以捕获任何类型,该类型都是由另一个类型参数化的。一个接受一个参数的类型构造器被称为(*->*)类型。例如,List就是这样的类型构造器。一个返回另一个类型构造器的类型构造器被称为(*->*->*)类型。例如,Function1。但在Scala中,我们有更高级别的类型构造器,因此我们可以有将其他类型构造器作为参数化类型构造器的类型构造器,所以它们的种类像((*->*)->*)

例如:

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

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


39

提取器允许您使用模式替换混乱的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)     
}

这不就像一个开关吗?也许这可以进行更多的重构。 - Geo
14
模式就像增压开关一样:更加强大和清晰。 - oxbow_lakes
1
不错,但我不喜欢你必须使用隐式,因为它的范围超出了匹配 { }。您还可以向 ProductService 添加一个按代码查找产品的方法。无论如何,您都会将重构的片段包装在一个方法中,以便能够在任何地方使用它。 - Martin Konicek

35

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

35

Case类自动混入Product特质,提供无需任何反射即可访问字段的未类型化、索引访问:

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

35

Manifests 是一种在运行时获取类型信息的方法,就像Scala具有实体化类型一样。


8
我认为在回答中解释答案比引用链接更好。顺便问候一下,你好,oxbow! :-) - Daniel C. Sobral
这是一个真正的隐藏功能...甚至在API文档中也找不到。虽然非常有用。 - André Laszlo

32

它并不完全是隐藏的,但肯定是一个不太被宣传的功能: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:<phase>会在某些编译器阶段之后打印语法树。使用scalac -Xshow-phases查看可用的阶段。

这是了解幕后发生的事情的好方法。

尝试使用typer阶段和case class X(a:Int,b:String)来真正感受它的有用性。


30

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

例如,以下代码定义了 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) 

这里有更多的例子:http://programmers.stackexchange.com/questions/13072/alternatives-to-if-else-statement/13086#13086 - missingfaktor
如果有人知道一种方法可以定义带有可选else的if-then-else块,并且像标准的那样进行类型检查,我会很感兴趣。 - Philippe
@Philippe: zif[A : Zero](cond: => Boolean)(t: => A): A = if(cond) t else mzero。需要Scalaz。 - missingfaktor

26

不确定这是否真正隐藏,但我认为它相当不错。

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

object Main {                                                                   
  class FooBar[A, B]

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

1
不错!我可以想象这在提高可读性方面有时会很有用。例如,var foo2barConverter: Foo ConvertTo Bar 将使类型参数的顺序自我说明。 - Esko Luontola
4
我有时会在使用PartialFunction的代码中这样写:type ~>[A, B] = PartialFunction[A, B]。将其翻译为:定义一个类型别名 ~>[A, B],它是PartialFunction[A, B]的一种形式。 - raichoo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接