每个 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
orvar
时,关键字后面的内容不仅仅是一个标识符,而是一个模式。这就是为什么它有效:
val (a, b, c) = (1, 3.14159, "Hello, world")
右手表达式创建一个Tuple3[Int, Double, String]
可以匹配模式的(a, b, c)
。
大多数时候,您的模式使用作为单例对象成员的提取器。例如,如果您编写类似的模式
Some(value)
然后你隐式调用 extractor Some.unapply
。
但是您也可以在模式中使用类实例,这就是这里发生的事情。val 正则表达式是 的一个实例Regex
,当您在模式中使用它时,您会隐式调用regex.unapplySeq
(unapply
vsunapplySeq
超出了此答案的范围),它将匹配组提取到 aSeq[String]
中,其中的元素被分配以变量年、月和日。
结构类型定义——即由它支持的方法描述的类型。例如:
object Closer {
def using(closeable: { def close(): Unit }, f: => Unit) {
try {
f
} finally { closeable.close }
}
}
请注意,除了具有方法外,未定义参数的类型closeable
close
例如,如果没有此功能,您可以表达将函数映射到列表以返回另一个列表,或将函数映射到树以返回另一棵树的想法。但是你不能在没有更高种类的情况下普遍表达这个想法。
对于更高的类型,您可以捕捉到任何类型的概念,该类型使用另一种类型进行参数化。接受一个参数的类型构造函数称为 kind (*->*)
。例如,List
。返回另一个类型构造函数的类型构造函数称为 kind (*->*->*)
。例如,Function1
。但是在 Scala 中,我们有更高的种类,所以我们可以拥有使用其他类型构造函数参数化的类型构造函数。所以它们的种类就像((*->*)->*)
.
例如:
trait Functor[F[_]] {
def fmap[A, B](f: A => B, fa: F[A]): F[B]
}
现在,如果你有一个Functor[List]
,你可以映射列表。如果你有Functor[Tree]
,你可以在树上映射。但更重要的是,如果你有Functor[A]
任何 A 的 kind(*->*)
,你可以映射一个函数 over 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 已经具体化了类型一样。
在 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)
案例类自动混合 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))
它并不是完全隐藏的,但肯定是一个宣传不足的功能: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
,这是一个应用程序implicit def augmentString
Predef.scala 中现在的应用。
scalac -Xprint:<phase>将在某个编译器阶段之后打印语法树。要查看可用阶段,请使用scalac -Xshow-phases。
这是了解幕后发生的事情的好方法。
尝试
case class X(a:Int,b:String)
使用typer阶段来真正感受它的用处。
您可以定义自己的控制结构。它实际上只是函数和对象以及一些语法糖,但它们的外观和行为就像真实的东西。
例如,以下代码定义dont {...} unless (cond)
and 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)
@switch
Scala 2.8 中的注释:
要应用于匹配表达式的注释。如果存在,编译器将验证匹配是否已编译为 tableswitch 或 lookupswitch,如果它编译为一系列条件表达式,则会发出错误。
例子:
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 {
不知道这是否真的隐藏,但我觉得它很好。
接受 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
}
}
Scala 2.8 引入了默认和命名参数,这使得 Scala 添加到案例类中的新“复制”方法成为可能。如果你定义这个:
case class Foo(a: Int, b: Int, c: Int, ... z:Int)
并且你想创建一个新的 Foo 就像现有的 Foo 一样,只有不同的“n”值,那么你可以说:
foo.copy(n = 3)
在 scala 2.8 中,您可以将 @specialized 添加到您的泛型类/方法中。这将为原始类型创建特殊版本的类(扩展 AnyVal)并节省不必要的装箱/拆箱成本:
class Foo[@specialized T]...
您可以选择 AnyVals 的一个子集:
class Foo[@specialized(Int,Boolean) T]...
您可以为函数指定一个按名称调用的参数(已编辑:这与惰性参数不同!),并且在函数使用之前不会对其进行评估(编辑:实际上,每次都会重新评估它用过的)。有关详细信息,请参阅此常见问题解答
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 中做这样的事情(不能)。但在 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)
您可以使用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
locally
在“Predef.scala”中定义为:
@inline def locally[T](x: T): T = x
作为内联,它不会强加任何额外的开销。
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
之前初始化块中的字段。with AbstractT2
这保证了在执行value
主体之前初始化AbstractT2
,如运行脚本时所示。
匿名函数的占位符语法
来自 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))
您可以使用“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
}
}
隐式定义,尤其是转换。
例如,假设一个函数将格式化输入字符串以适应大小,通过将其中间替换为“...”:
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 方法来转换任何内容。但你也可以这样写:
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
现在您可以调用该函数传递一个双精度:
sizeBoundedString(12345.12345D, 8)
最后一个参数是隐式的,并且由于隐式 de 声明而自动传递。此外,“s”被视为sizeBoundedString 内的字符串,因为存在从它到字符串的隐式转换。
这种类型的隐式更好地定义为不常见的类型,以避免意外的转换。你也可以显式传递一个转换,它仍然会在 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。
虽然这个简单的例子可以用重载和私有状态重写,但隐式不需要私有状态,并且可以与导入一起进入上下文。
也许不是太隐藏,但我认为这很有用:
@scala.reflect.BeanProperty
var firstName:String = _
这将自动为匹配 bean 约定的字段生成 getter 和 setter。
developerworks上的进一步描述
闭包中的隐式参数。
函数参数可以像方法一样被标记为隐式。在函数体的范围内,隐式参数是可见的并且有资格进行隐式解析:
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")
})
}
Stream
使用 Scala 的s
构建无限数据结构:http: //www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient
结果类型取决于隐式解析。这可以给你一种多重分派的形式:
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 allows you to create an anonymous subclass with the body of the class (the constructor) containing statements to initialize the instance of that class.
This pattern is very useful when building component-based user interfaces (for example Swing , Vaadin) as it allows to create UI components and declare their properties more concisely.
See http://spot.colorado.edu/~reids/papers/how-scala-experience-improved-our-java-development-reid-2011.pdf for more information.
Here is an example of creating a Vaadin button:
val button = new Button("Click me"){
setWidth("20px")
setDescription("Click on this")
setIcon(new ThemeResource("icons/ok.png"))
}
import
从语句中排除成员假设您要使用Logger
包含 aprintln
和printerr
方法的 a,但您只想将一个用于错误消息,并将旧Predef.println
的用于标准输出。你可以这样做:
val logger = new Logger(...)
import logger.printerr
但是如果logger
还包含其他十二个您想要导入和使用的方法,则列出它们变得不方便。您可以尝试:
import logger.{println => donotuseprintlnt, _}
但这仍然“污染”了导入成员的列表。输入超强大的通配符:
import logger.{println => _, _}
这将做正确的事情™。
require
方法(定义在 中Predef
),允许您定义将在运行时检查的附加功能约束。想象一下,您正在开发另一个 Twitter 客户端,并且您需要将推文长度限制为 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)
另一个例子是here。
您可以在每次要求失败时执行操作:
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
然后必须将这些特征与其方法的具体实现混合在一起。
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,但我非常喜欢使用这些Stackable Traits来构建具有预定义导入、自定义绑定和类路径的 Scala 解释器实例。Stackable Traits使创建我的工厂成为可能,然后new InterpreterFactory with JsonLibs with LuceneLibs
为用户脚本提供有用的导入和范围变量。