Scala 的哪些隐藏特性是每个 Scala 开发者都应该知道的?
请每个回答只列举一个隐藏特性。
Scala 的哪些隐藏特性是每个 Scala 开发者都应该知道的?
请每个回答只列举一个隐藏特性。
好的,我需要再补充一点。在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"
如果你不习惯使用模式匹配和提取器,第二行代码可能会让人感到困惑。每当你定义一个val
或var
时,关键字后面的内容不仅仅是一个标识符,而是一个模式。这就是为什么以下代码可以正常工作:
val (a, b, c) = (1, 3.14159, "Hello, world")
右侧表达式创建了一个Tuple3[Int, Double, String]
,它可以匹配模式(a, b, c)
。
大多数情况下,您的模式使用单例对象的提取器。例如,如果您编写了像下面这样的模式:
Some(value)
那么你隐式地调用了提取器Some.unapply
。
但是你也可以在模式中使用类实例,这就是这里正在发生的事情。val regex是Regex
的一个实例,当你在模式中使用它时,你隐式地调用了regex.unapplySeq
(unapply
与unapplySeq
超出了本回答的范围),它将匹配组提取到Seq[String]
中,其中元素按顺序分配给变量year、month和day。
结构类型定义 - 即由其支持的方法描述的类型。例如:
object Closer {
def using(closeable: { def close(): Unit }, f: => Unit) {
try {
f
} finally { closeable.close }
}
}
请注意参数closeable的类型未定义,但它有一个close
方法。
如果没有这个功能,你可以用一个列表来表达将函数映射到另一个列表并返回,或者将函数映射到树上并返回另一棵树的想法。但你不能通用地表达这个想法而不使用高阶类型。
有了高阶类型,你可以捕获任何类型,该类型都是由另一个类型参数化的。一个接受一个参数的类型构造器被称为(*->*)
类型。例如,List
就是这样的类型构造器。一个返回另一个类型构造器的类型构造器被称为(*->*->*)
类型。例如,Function1
。但在Scala中,我们有更高级别的类型构造器,因此我们可以有将其他类型构造器作为参数化类型构造器的类型构造器,所以它们的种类像((*->*)->*)
。
例如:
trait Functor[F[_]] {
def fmap[A, B](f: A => B, fa: F[A]): F[B]
}
现在,如果你有一个 Functor[List]
,你可以对列表进行映射。 如果你有一个 Functor[Tree]
,你可以对树进行映射。 但更重要的是,如果你有任何一种类型为(*->*)
的 A
的 Functor[A]
,你可以将函数映射到 A
。
提取器允许您使用模式替换混乱的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)
}
在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)
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))
Manifests 是一种在运行时获取类型信息的方法,就像Scala具有实体化类型一样。
它并不完全是隐藏的,但肯定是一个不太被宣传的功能: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)
来真正感受它的有用性。
你可以定义自己的控制结构。它们实际上只是函数和对象以及一些语法糖,但它们看起来和行为都像真正的控制结构。
例如,以下代码定义了 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)
zif[A : Zero](cond: => Boolean)(t: => A): A = if(cond) t else mzero
。需要Scalaz。 - missingfaktor不确定这是否真正隐藏,但我认为它相当不错。
接受两个类型参数的类型构造函数可以用中缀表示法编写。
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
将使类型参数的顺序自我说明。 - Esko Luontola