Scala 的隐藏特性

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

请给每个答案一个隐藏特征。

54172 次浏览

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

提取器 允许您用模式替换混乱的 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.reflect.BeanProperty
var firstName:String = _

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

Developerworks的进一步描述

您可以指定一个按名称调用的参数(EDITED: 这不同于一个惰性参数!)并且它在被函数使用之前不会被计算(EDIT: 事实上,它每次被使用都会被重新计算)。详情请参阅 这个常见问题

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
*/

匿名函数的占位符语法

来自 Scala 语言规范:

SimpleExpr1 ::= '_'

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

来自 Scala 语言变化:

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

结构化的 类型定义-也就是说,类型是由它所支持的方法描述的,例如:

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

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

隐式定义,尤其是转换。

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

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
}

您可以对任何 String 使用它,当然,还可以使用 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

现在可以通过一个 double 调用该函数:

sizeBoundedString(12345.12345D, 8)

最后一个参数是隐式的,并且由于隐式 de 声明而自动传递。此外,“ s”的 治疗类似于 sizeBoundedString 中的 String,因为存在从它到 String 的隐式转换。

对于不常见类型,更好地定义此类型的隐式,以避免意外转换。您还可以显式地传递一个转换,并且它仍然在 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。

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

类型构造函数多态性 (也称为高类型类型)

例如,如果没有这个特性,您可以表达将函数映射到一个列表以返回另一个列表的想法,或者将函数映射到一棵树以返回另一棵树。但是如果没有更高级的类,你就不能表达这个观点。

使用更高级的类型,您可以捕获使用另一种类型参数化的 任何类型的思想。一个带有一个参数的型别构造器被认为是类似于 (*->*)的。例如,List。返回另一个型别构造器的型别构造器被认为是类 ABc2。例如,Function1。但是在 Scala 中,我们有 再高点类型,所以我们可以使用与其他类型构造函数一起参数化的类型构造函数。所以它们的种类类似于 ((*->*)->*)

例如:

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

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

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

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

Scala 2.8引入了默认参数和命名参数,这使得 Scala 在 case 类中添加一个新的“ copy”方法成为可能。如果你这样定义:

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

你想创建一个新的 Foo 就像现有的 Foo 一样,只是有不同的 n 值,然后你就可以说:

foo.copy(n = 3)

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

// 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.unapplySeq(unapplyunapplySeq超出了这个答案的范围) ,它将匹配组提取到 Seq[String]中,其中的元素按照变量的年、月和日分配。

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

举个例子:

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)

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

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

及早启动:

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字段 在块中,在 < code > 之前使用 AbstractT2 子句 方法之前初始化 value 执行 AbstractT2的主体,如 在运行脚本时显示。

Scala 2.8中的 @switch 注释:

要应用于匹配的注释 如果存在,则编译器 将证实比赛已经 编译成表开关或 查找开关,并且如果 而是编译成一系列 条件表达式。

例如:

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

可以使用 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

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

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

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

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

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

class A { "xx".r }

使用 Scalac-Xprint: 打字机输出编译此文件:

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: < stage > 将在一些编译器阶段之后打印语法树。

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

试试

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

使用 打字机阶段来真正感受它是多么有用。

扩展语言。我一直想在 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)

Case 类自动混合 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))

可以使用“ 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
}
}

不知道这是不是真的藏起来了,但我觉得挺好看的。

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

闭包中的隐式参数。

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

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

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

使用 abstract override方法的 Traits 是 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 强多少,但我非常喜欢使用这些 堆叠特征来构建带有预定义导入、自定义绑定和类路径的 Scala 解释器实例。堆叠特征使得按照 new InterpreterFactory with JsonLibs with LuceneLibs的思路创建我的工厂成为可能,然后为用户脚本提供了有用的导入和范围变量。

require方法(在 Predef中定义) ,它允许您定义在运行时检查的其他函数约束。假设您正在开发另一个 Twitter 客户端,您需要将 tweet 长度限制为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)

另一个例子是 给你


意外收获

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

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

import报表中排除成员

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

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

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

import logger.{println => donotuseprintlnt, _}

但这仍然“污染”了进口成员的名单:

import logger.{println => _, _}

这将做 正确的选择 & 贸易; 。