An Exceptional Failure
判断下面代码的打印结果:
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
val f = Future { throw new Error("fatal!") } recoverWith {
case err: Error => Future.successful("Ignoring error: " + err.getMessage)
}
f onComplete {
case Success(res) => println("Yay: " + res)
case Failure(e) => println("Oops: " + e.getMessage)
}
在下面的结果中选择:
1. Prints:
Yay: Ignoring error: fatal!
2. Throws an error
3. Prints:
Oops: fatal!
4. Prints:
Oops: Boxed Error
而正确的答案应该是第四个.
在Future中包含了一个抛出的Error,并且带有一条消息”fatal!”,你会认为会在recoverWith中恢复并且最终返回一个成功的状态.但事实并非如此,Future中抛出的是一个异常(Throwable)而不是一个Error,所以并不能到达recoverWith的处理逻辑.
现在我们能确定这个future失败了,但是在onComplete部分匹配到的错误消息并不是我们在Future中定义的”fatal!”,因为我们抛出的是一个来自box自外的 java.util.concurrent.ExecutionException
,而Future失败时创建的是一个 “Boxed Error
消息.
因此,这个Future的结果是:
Failure(new ExecutionException("Boxed Error", new Error("fatal!")))
$!.*%
Iterators!
判断一下代码执行的结果:
val t = "this is a test"
val rx = " ".r
val m = rx.findAllIn(t)
println(m)
println(m.end)
println(rx.findAllIn(t).end)
这个片段创建了一个正则表达式,以在给定的字符串当中找到匹配的空格.调用findAllIn会返回一个Iterator.当打印一个迭代器时只会显示”epmty iterator”或者”non-empty iterator”.因为我们在字符串中会找到三个空格,因此打印的结果是”non-empty iterator”.
然后在打印 m.end
时会得到第一个命中的位置之后的字符索引,第一个空格的索引是4,下一个字符索引就是5.
但是下一步我们得到了一个 IllegalStateException
异常.这表示,在正则匹配中,除非你去请求或者检查第一个元素,这个正则是不会被激活的.而上面的例子中,首先是对m调用了println,从而调用了它的toString方法以此激活了这个正则,所以在打印 m.end
时没有任何问题.
What’s in a Name?
class C {
def sum(x: Int = 1, y: Int = 2): Int = x + y
}
class D extends C {
override def sum(y: Int = 3, x: Int = 4): Int = super.sum(x, y)
}
val d: D = new D
val c: C = d
c.sum(x = 0)
d.sum(x = 0)
首先由两个问题:
- 参数名的绑定是编译器完成的,同时编译器唯一能够使用的信息就是变量的静态类型
- 对于带有默认值的参数,编译器会创建一个方法,一个会按照默认参数计算的表达式.上面的例子中,C和D都有一个为默认值提供的方法:
sum$default$1
和sum$default$1
.如果该参数未提供,编译器会直接使用这些方法进行计算,这些方法会在运行时被唤起.
在调用 c.sum(x = 0)
时,这时只提供了一个参数x,会使用y的迷默认值.但是后面真正发生了什么呢,因为c是有d创建的,它会携带D中带有默认参数的方法sum,当他选择方法进行使用时,并不会考虑该方法中的参数名.这时第二个参数没有提供,则会使用默认的4,因此计算得到结果 0+4=4.
在调用 d.sum(x = 0)
时,情况会有点不同,这时会直接找到D类中的sum方法,y的参数没有提供,会使用默认的3,因此计算结果是 3+0=3.
Josh Suereth 把这个规则总结为: 名字是静态的,而值是运行时(Names are static; values are runtime).
(Ex)Stream Surprise
val nats: Stream[Int] = 1 #:: (nats map { _ + 1 })
val odds: Stream[Int] = 1 #:: (odds map { _ + 1 } filter { _ % 2 != 0 })
nats filter { _ % 2 != 0 } take 2 foreach println
odds take 2 foreach println
这里创建了两个stream,nats和odds.nats包含了 (1,?),同时定义了向他添加元素的规则,即增加1.类似的,odds拥有同样的规则,并且带有一个filter,只允许添加奇数.问题出在我们要打印它的时候.
对于nats,我们在它进行懒创建(1, (2, (3, ?)))
之后执行了过滤,取前两个奇数元素,则会得到1和3,这没有任何问题.
对于odds,我们去不到2个元素,而只能取到一个.我们进入了一个无止境的递归状态.因为2不是奇数,当stream按照 +1
的规则创建时同时激活了右侧的filter.我们永远无法到达3同时得到一个运行时异常.
A Case of Strings
def objFromJava: Object = "string"
def stringFromJava: String = null
def printLengthIfString(a: AnyRef) {
a match {
case s: String => println("String of length " + s.length)
case _ => println("Not a string")
}
}
printLengthIfString(objFromJava)
printLengthIfString(stringFromJava)
对于objFromJava,尽管它被定义为一个Object,但是我们仍然给他返回一个String类型,由于模式匹配基于运行时类型,因此能够打印出”string”的长度.
对于stringFromJava,是一个null,得到的并不是一个String类型.因为null是一个完全不同的类型,因此在case时需要明确指定进行检查.
Alex Zhitnitsky: 5 Scala Puzzlers That Will Make Your Brain Hurt