月度归档:2014年10月

最近看过的电影(9)

偶然在电视里看的《红灯》(Red Lights)是一部蛮吸引我的电影,尽管豆瓣上的评分不算高。

它的结尾完全出人意料,不过仔细想想前边也是留了很多伏笔的。主角是希里安.墨菲,这个演员我是在看《蝙蝠侠前传》和《风吹麦浪》的时候留下深刻印象的。片子很有意思,汤姆跟随玛格丽特教授揭露一些所谓有超能力的神棍们的骗局,结果却是主人公汤姆才是真正具有超能力的人,只是因为他因为对母亲的愧疚,一直不敢直面自己所具备的超能力,否定自己。

周末看的另一部《拳外重生》(Max Schmeling)讲述了德国的一个伟大的拳击手马克斯·施梅林的故事,也还不错。

scala雾中风景(18): postfix operator的问题

这个谜题也是邮件列表里看到的,当使用下面的代码时,编译正常:

object ListTests1 extends App {
    val numbers: List[Int] = (1 to 10).toList
    println(s"numbers: $numbers") 
}

但当把toList方法前边的点给去掉时,编译居然报错:

scala> :pas
// Entering paste mode (ctrl-D to finish)

object ListTests1 extends App {
    val numbers: List[Int] = (1 to 10) toList
    println(s"numbers: $numbers")
}

// Exiting paste mode, now interpreting.

<console>:9: error: type mismatch;
 found   : Unit
 required: Int
     println(s"numbers: $numbers")
            ^

可能第一眼以为是(1 to 10) toList这种写法有问题,但直接在repl下运行这一句的话,是ok的,只是警告toList是一个后缀操作符

scala> val numbers: List[Int] = (1 to 10) toList

<console>:7: warning: postfix operator toList should be enabled
by making the implicit value scala.language.postfixOps visible.
This can be achieved by adding the import clause 'import scala.language.postfixOps'
or by setting the compiler option -language:postfixOps.
See the Scala docs for value scala.language.postfixOps for a discussion
why the feature should be explicitly enabled.
   val numbers: List[Int] = (1 to 10) toList
                                      ^
numbers: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

这里有趣的是,这行表达式跟下一行表达式的结果也可以组成一个表达式;在使用后缀操作符的情况下,方法后边的表达式,优先解析为参数对待。这里本来 toList 方法是一个无参方法,但它的结果类型List是可以apply的,所以toList方法后边也可以接受Int类型的参数;而下一行的println得到是一个Unit类型,所以错误。

类似这个例子:

object Test {
    def say  =  println("say nothing")
    def say(s:String) = println("say " + s)
}

定义了2个say方法,一个无参和一个String类型参数的,当调用

Test.say
println("hello")

上面语句被解释为2个表达式,第一句调用的是无参的 say 方法,而写为:

Test say
println("hello")

这种情况下被解析为Test.say(println("hello")) 把下一行表达式的结果当作say的参数,因为类型不匹配而失败。如果在第一行加一个分号行为就不一样了:

Test say;
println("hello")

postfix operator对于设计DSL很有用,但平常程序里最好还是不要用。

相关内容:scala2.10中采纳了SIP-18:模块化语言特性

scala的诊断方法(5) 用scalac-aspects诊断scalac各阶段耗时

在scala-user邮件列表里看到的,有人说他有两个类用scalac编译非常慢,别人给出了一个工具可以诊断scalac的编译过程在各环节耗时情况,这个工具是基于aspectj的,尝试了一把。

先下载aspectj,然后用java -jar aspectj-1.8.2.jar安装,它是个图形界面的安装程序,过程就是文件解压到指定路径。

然后设置一下环节变量:

$ export ASPECTJ_HOME=/data/tools/aspectj/1.8
$ export PATH=${ASPECTJ_HOME}/bin:${PATH}

从github上clone一份scalac-aspects,然后运行一下样例:

$ ./scalac-aspects PerUnitTiming.aj Foo.scala
...

Per-file timings (all times are in micro seconds)
Foo.scala 1260255
    parser                    170939
    namer                     144580
    packageobjects            60
    typer                     223685
    patmat                    10843
    superaccessors            7899
    extmethods                4354
    pickler                   35800
    refchecks                 78355
    selectiveanf              2013
    selectivecps              1724
    uncurry                   26039
    tailcalls                 16333
    specialize                59948
    explicitouter             86753
    erasure                   236839
    posterasure               855
    lazyvals                  11423
    lambdalift                38314
    constructors              43564
    flatten                   466
    mixin                     20772
    cleanup                   10301
    icode                     27698
    inliner                   626
    inlineExceptionHandlers   28
    closelim                  26
    dce                       18

对这个简单的只定义了一个方法的Foo.scala,编译过程最耗时也是在typererasure 阶段,估计大部分代码的编译过程类型相关的处理都会占大头。

maven调试web应用

mvn tomcat7:run运行web应用的一个小脚本,方便debug:

$ cat mvn-tomcat
#!/bin/bash

suspend="n"
if [ "$1" != "" ]; then
  lower=`echo $1 | tr '[:upper:]' '[:lower:]'`
  if [ "$lower" == "y" ] || [ "$lower" == "n" ]; then
    suspend=$lower
  else
    echo "param error" && exit -1;
  fi
fi

port=8080
if [ "$2" != "" ]; then
  re='^[0-9]+$'
  if ! [[ "$2" =~ $re ]] ; then
     echo "port: error, not a number" && exit -2;
  else
    port=$2
  fi
fi

export MAVEN_OPTS=-agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend="$suspend"
mvn tomcat7:run -Dmaven.tomcat.port="$port"

把脚本放到放到PATH路径下(比如~/bin目录下)。使用方式:

$ mvn-tomcat # 默认8080端口

$ mvn-tomcat y # 断点suspend

$ mvn-tomcat n 7001 # 指定tomcat用7001端口

scala雾中风景(17): toSet()的谜题

看到hackernews上推荐的《scala: the good,the bad and the very ugly》在这篇ppt里,有一个例子挺有意思的:

List(1,2,3).toSet()

猜一下它的结果,直观上我们会认为它返回一个Set类型的集合,实际却不是:

scala> List(1,2,3).toSet()
<console>:8: warning: Adaptation of argument list by inserting () has been deprecated: this is unlikely to be what you want.
    signature: GenSetLike.apply(elem: A): Boolean
  given arguments: <none>
 after adaptation: GenSetLike((): Unit)
          List(1,2,3).toSet()
                           ^
res1: Boolean = false

在repl下测试,在给出警告之后,输出了一个Boolean值。让人大跌眼镜,要想得到预期的结果,方法后边的小括号是不能写的:

scala> List(1,2,3).toSet
res9: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

其实警告信息里已经很明确的说明了,toSet()实际是对toSet的结果再调用的GenSetLike.apply(elem: A),即它相当于

List(1,2,3).toSet.apply()

如果apply方法是无参的,上面的也好理解,但警告信息里提示的是:GenSetLike.apply(elem: A) 明明时带有一个参数的,为什么用空的参数apply()也可以运行?

等等,警告信息里还有2句:

given arguments: <none>
after adaptation: GenSetLike((): Unit)

这两句是说给了一个空参数,被编译器适配成了一个Unit类型的()实例对象,是不是有些匪夷所思?编译器为何要自作聪明?如果我们对Unit类型的谜题还有印象的话,会怀疑是否因为Unit类型用作方法参数引起的:

scala> def foo(u:Unit) { println("ok") }

scala> foo() // 可以运行,同样也会有类似上面的警告信息

参考以前两篇有关Unit的: scala雾中风景(4): Unit类型scala雾中风景(8): 高阶函数与Unit的谜题

不过,这里是否真的就是Unit类型引起的? 虽然警告信息里提示把空参数适配成了Unit类型,但并不是apply方法声明的参数,GenSetLike.apply(elem: A) 参数类型是一个泛型参数,我们先根据直觉判断 List(1,2,3).toSet 之后得到的是Set[Int],所以这里A是Int类型?我们一步步来验证一下:

scala> val s = List(1,2,3).toSet
s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

scala> s.apply() // 错误,提示缺乏参数!用IDE的话发现编译时就提示错误了

诡异了,分成两步的时候居然又不行了,虽然这时的提示更符合预期,但之前用一句表达 List(1,2,3).toSet.apply() 为什么又可以运行?

经过google之后,发现这竟然是一个很有趣的类型推导问题,参考这篇讨论

scala.collection.immutable.Set是非协变的(实际是invariant的),这点不同于List,Seq,Queue等主流集合的声明。为什么要定义为非协变的,也是由Set自身的函数特征决定的trait Set[A] extends (A => Boolean),可以参考这里的解释

TraversableOnce.toSet方法声明是:

def toSet[B >: A]: immutable.Set[B] = ...

注意,它返回的类型不同于List[A]里的A,而是A的某个父类型B!这里存在一个很常见的类型推导问题:

scala> List(1,2,3).toSet.map(x=>x+1) // 错误,缺乏参数类型

scala> List(1,2,3).toSet.map((x:Int)=>x+1) // 需要显式声明参数类型 
res28: scala.collection.immutable.Set[Int] = Set(2, 3, 4)

scala> List(1,2,3).toSet[Int].map(x=>x+1) // 或在toSet的时候显式的声明元素类型为Int 
res29: scala.collection.immutable.Set[Int] = Set(2, 3, 4)

或分两步,先得到set结果,再map不需要显式声明参数类型:

scala> val s = List(1,2,3).toSet // 先得到set结果
s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

scala> s.map(x=>x+1)  // OK
res30: scala.collection.immutable.Set[Int] = Set(2, 3, 4)

为啥分开写,跟连着写在toSet阶段的类型推导是不一样的呢,typesafe的开发Adriaan Moors给了解释

You can think of inference as a breadth-first sweep over the expression, collecting constraints (which arise from subtype bounds and required implicit arguments) on type variables, followed by solving those constraints. This approach allows, e.g., implicits to guide type inference. In your example, even though there is a single solution if you only look at the xs.toSet subexpression, later chained calls could introduce constraints that make the system unsatisfiable. The downside of leaving the type variables unsolved is that type inference for closures requires the target type to be known, and will thus fail (it needs something concrete to go on — the required type of the closure and the type of its argument types must not both be unknown).

Now, when delaying solving the constraints makes inference fail, we could backtrack, solve all the type variables, and retry, but this is tricky to implement (and probably quite inefficient).

大致是类型推导是广度优先的方式扫描表达式,搜集类型变量上由子类型界定和隐式参数导致的约束,接下来解释这些约束。。。

所以在一整句连着写List(1,2,3).toSet.apply()toSet阶段因为其上下文约束不同于单句的List(1,2,3).toSet,还未能推导此时Set的元素类型。

我们通过-Ytyper-debug选项可以看到分开写的时候,类型推导的确是明确推导出结果是Set[Int]了:

而连在一起写的时候,并未明确推导出Set的元素类型,这里B类型是Int的父类型:

现在,我们了解到了List(1,2,3).toSeq.apply()在apply()之前因为元素类型还未明确(B是Int的父类型),B可能是一个非常“泛”的类型,比如AnyVal或Any,这样相当于:

scala> val s: Set[Any] = List(1,2,3).toSeq
scala> s.apply()  // 给出警告,但运行OK!

现在问题就变成了,为和一个很宽泛的类型比如AnyAnyRef,用作参数类型的时候,实际调用这个方法时参数可以随意传递?

scala> def foo(a:Any) { println("ok") }
scala> foo(1,2,3) // 给出警告,但运行OK
scala> foo() // 给出警告,但运行OK

其实这个问题跟之前的这篇 scala雾中风景(16): println(1,2,3)为什么work? 属于同一个问题,scala编译器发觉对只有一个参数的方法在调用时参数不一致的情况下,会在最后阶段尝试一次“适配”,简单的说就是用”()”进行tuple化,如果参数多于一个,将整个TupleN当作参数传入,如果参数为0,则tupling得到一个Unit类型实例传入。

对于scala编译器在方法参数上自作聪明的“适配”,应该严格禁止它发生的可能,建议所有的项目编译时,都开启 -Yno-adapted-args 让编译器给出错误,避免混乱。

shell里的|&用法

shell中默认的管道是将前边的命令执行的的标准输出结果传给管道后的命令,而标准错误输出是没有传递的,有些时候,我们想要对前边的命令的标准错误输出也做处理,这样的话就需要将stderr给重定向到stdout了。

不过在高版本的bash里和zsh里现在都支持了 |& 来将标准输出和错误输出一并交给后边的命令处理。举个例子:

$ cat test.sh 
#!/bin/bash
echo "stdout message"
echo "stderr message" 1>&2

我们模拟了2个输出,第一个是标准输出,第二个是错误输出。如果我们用普通的管道 | 来连接的话:

$ ./test.sh | grep stdout
stderr message
stdout message

我们本来只想要过滤包含”stdout”的内容,但错误输出的内容并不会经过管道处理,所以其内容还是完整的打印在了终端上。如果采用 |& 就会把两个输出内容一并交给管道来处理了:

$ ./test.sh |& grep stdout  
stdout message

我不确定bash是从哪个版本开始支持这个特性的,最早是csh/tcsh里有这个特性,被bash和zsh学过来了,ksh里|&是另外一种用法,表示的是协同处理。

如果你的shell不支持这个用法,还是老实的用重定向来解决吧:

$ ./test.sh 2>&1 | grep stdout
stdout message

上面的例子没有实际价值,可能有人说把 2>/dev/null 就可以了,实际应用中,可能是碰到某些命令的输出不确定我们想要的内容是在标准输出里还是在错误输出里,比如我们想看到scalac的选项里包含warn关键字的:

$ scalac -Y |& grep warn 
-Yinline-warnings                       Emit inlining warnings. (Normally surpressed due to high volume)
-Ywarn-adapted-args                     Warn if an argument list is modified to match the receiver.
-Ywarn-dead-code                        Warn when dead code is identified.
-Ywarn-inaccessible                     Warn about inaccessible types in method signatures.
-Ywarn-infer-any                        Warn when a type argument is inferred to be `Any`.
-Ywarn-nullary-override                 Warn when non-nullary `def f()' overrides nullary `def f'.
-Ywarn-nullary-unit                     Warn when nullary methods return Unit.
-Ywarn-numeric-widen                    Warn when numerics are widened.
-Ywarn-unused                           Warn when local and private vals, vars, defs, and types are are unused
-Ywarn-unused-import                    Warn when imports are unused
-Ywarn-value-discard                    Warn when non-Unit expression results are unused.

scala的诊断方法(4) -Ytyper-debug 编译项

在定位之前的那个问题时,发现了-Ytyper-debug选项,可以吧编译器在typer阶段的一些信息打印出来,比-Xprint:typer多出一些类型推导的信息:

$ cat A.scala
object A {
    println(1,2,3)
}

$ scalac -Ytyper-debug  A.scala
|-- <empty> EXPRmode-POLYmode-QUALmode (site: package <root>)
|    \-> <empty>.type
|-- object A BYVALmode-EXPRmode (site: package <empty>)
|    |-- super EXPRmode-POLYmode-QUALmode (silent: <init> in A)
|    |    |-- this EXPRmode (silent: <init> in A)
|    |    |    \-> A.type
|    |    \-> A.type
|    |-- println(1, 2, 3) BYVALmode-EXPRmode (site: value <local A> in A)
|    |    |-- println BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local A> in A)
|    |    |    \-> (x: Any)Unit <and> ()Unit
|    |    |-- scala.Tuple3(1, 2, 3) : pt=Any BYVALmode-EXPRmode (silent: value <local A> in A)
|    |    |    |-- scala.Tuple3 BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local A> in A)
|    |    |    |    |-- scala.Tuple3.apply BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local A> in A)
|    |    |    |    |    [adapt] [T1, T2, T3](_1: T1, _2: T2, _3: T3)(T1, T2, T3) adapted to [T1, T2, T3](_1: T1, _2: T2, _3: T3)(T1, T2, T3)
|    |    |    |    |    \-> (_1: T1, _2: T2, _3: T3)(T1, T2, T3)
|    |    |    |    [adapt] Tuple3.type adapted to [T1, T2, T3](_1: T1, _2: T2, _3: T3)(T1, T2, T3)
|    |    |    |    \-> (_1: T1, _2: T2, _3: T3)(T1, T2, T3)
|    |    |    |-- 1 BYVALmode-EXPRmode-POLYmode (silent: value <local A> in A)
|    |    |    |    \-> Int(1)
|    |    |    |-- 2 BYVALmode-EXPRmode-POLYmode (silent: value <local A> in A)
|    |    |    |    \-> Int(2)
|    |    |    |-- 3 BYVALmode-EXPRmode-POLYmode (silent: value <local A> in A)
|    |    |    |    \-> Int(3)
|    |    |    solving for (T1: ?T1, T2: ?T2, T3: ?T3)
|    |    |    \-> (Int, Int, Int)
|    |    \-> Unit
|    \-> [object A] A.type
|-- (_1: <?>, _2: <?>, _3: <?>)(T1, T2, T3) EXPRmode-FUNmode-POLYmode-TAPPmode (site: object A) implicits disabled
|    |-- new (Int, Int, Int) EXPRmode-POLYmode-QUALmode (site: object A) implicits disabled
|    |    \-> (Int, Int, Int)
|    \-> (_1: Int, _2: Int, _3: Int)(Int, Int, Int)

在解析的时候,对println(1,2,3)里的参数执行了Tuple3(1,2,3)最终参数识别成了Tuple3[Int,Int,Int]类型。暂没发现更大的价值,先记下来以后再补充。

scala雾中风景(16): println(1,2,3)为什么work?

Console的println方法只有一个参数,但执行println(1,2,3)却可以打印出”(1,2,3)” 而不是报错:

scala> println(1,2,3)
(1,2,3)

编译器为什么会自作聪明的把里面的三个参数当成一个Tuple?

google了一下找到了这个issue, 在Typers.scala 里可以看到注释:

/** Try packing all arguments into a Tuple and apply `fun'
 *  to that. This is the last thing which is tried (after
 *  default arguments)
 */
def tryTupleApply: Option[Tree] = {
    // if 1 formal, 1 arg (a tuple), otherwise unmodified args
    val tupleArgs = actualArgs(tree.pos.makeTransparent, args, formals.length)
    ...
}

在编译的typer阶段,最后发现参数与方法定义要求的不匹配时,如果方法只有一个参数,编译器最后会尝试把所有参数打包为一个Tuple,把这个Tuple传递给方法。

这个特性之前没有在规范里明确定义出来。不过编译器有选项可以给出警告或报错的:

$ cat A.scala
object A {
    println(1,2,3)
}

$ scalac -Ywarn-adapted-args A.scala
A.scala:2: warning: Adapting argument list by creating a 3-tuple: this may not be what you want.
    signature: Predef.println(x: Any): Unit
given arguments: 1, 2, 3
after adaptation: Predef.println((1, 2, 3): (Int, Int, Int))
println(1,2,3)
        ^
one warning found

如果严格一些,可以使用-Yno-adapted-args选项禁止这个特性,这样编译时将报错:

$ scalac -Yno-adapted-args  A.scala
A.scala:2: error: too many arguments for method println: (x: Any)Unit
    println(1,2,3)
            ^
one error found

-Xlint里已经包含了-Ywarn-adapted-args,在编译时用-Xlint就可以发现这个警告了。

另外,auto-tupling的特性只能针对方法参数不超过一个情况,如果方法存在重载的且参数有超过2个的话将不起作用:

scala> def foo(p1:Any) { println(p1) }
foo: (p1: Any)Unit

scala> def foo(p1:Any, p2:Any) { println(p1.toString + p2.toString) }
foo: (p1: Any, p2: Any)Unit

scala> foo(1,2,3)
<console>:9: error: too many arguments for method foo: (p1: Any, p2: Any)Unit
          foo(1,2,3)
             ^

scala雾中风景(15): class A { type T }与class A[T] {}

回答一个新的问题:为什么class A { type T } 里面的T未定义,表示某种抽象类型,而A却不需要声明为abstract,这是为何?

scala> class A { type T }
defined class A

scala> val a =new A
a: A = A@69284047

确实,尽管T类型未声明,却并不影响A的实例化。其实,这种写法,跟 class A[T] {} 效果上是相似的,都是定义了一个泛型参数。不过我们看看它们在类型推断上的差异:

scala>  class A[T] { def foo(p:T) {println(p)} }
defined class A

scala> new A
res3: A[Nothing] = A@49bb2f1c

scala> (new A).foo("hi")
hi

上面把T声明为泛型参数(type parameter)的话,构造的时候不给出具体参数,编译器会推导为A[Nothing]。

scala> class B { type T; def foo(p:T) {println(p)} }
defined class B

scala> new B
res4: B = B@72f1b266

scala> b.foo("hi")
<console>:9: error: type mismatch;
 found   : String("hi")
 required: b.T 
          (new B).foo("hi")
                      ^

而在class内部定义一个抽象类型T,创建实例时不指定具体类型时,这个类型是一个抽象的路径依赖类型”b.T”,要想满足的话,需要显式的声明一个这样的参数传入:

scala> val p: b.T = "hi".asInstanceOf[b.T]
p: b.T = hi

scala> b.foo(p)
hi

scala雾中风景(14): trait的泛型参数为何不支持context bounds

回答一个网友的问题:为什么定义一个trait时无法对其泛型参数声明为context boundsview bounds,而class可以? 当然可以在方法层面使用context bounds而绕过这个限制,但不理解为何trait会与class在这个问题上存在差异。

scala> import scala.reflect.runtime.universe.TypeTag

scala> trait My[T:TypeTag] {}
<console>:1: error: traits cannot have type parameters with context bounds `: ...' nor view bounds `<% ...'

关于context boundsview bounds的感念可以参考以前的这篇这篇

其实自己google一下就可以看到martin odersky对这个问题的回答,他解释这个是因为trait是没有构造参数的,而泛型参数使用context boundsview bounds在背后是靠编译器把隐式参数做为一个私有成员让整个类都可以访问,并在构造方法上限制了这个上下文必须存在这个隐式参数。未来也会考虑在trait上支持context boundsview bounds,但还没有具体的规划。

先看看class泛型参数在context bound的背后实现:

$ scala -Xprint:typer -e 'import scala.reflect.runtime.universe._; class My[T:TypeTag]'
    ...

    import scala.reflect.runtime.`package`.universe._;
    class My[T] extends scala.AnyRef {
      implicit <synthetic> <paramaccessor> private[this] val evidence$1: reflect.runtime.universe.TypeTag[T] = _;
      def <init>()(implicit evidence$1: reflect.runtime.universe.TypeTag[T]): this.My[T] = {
        My.super.<init>();
        ()
      }
    }
    ...

再看看view bound的背后实现:

$ scala -Xprint:typer -e 'class  My[T <% java.io.Closeable]'
    ...

    class My[T] extends scala.AnyRef {
      implicit <synthetic> <paramaccessor> private[this] val evidence$1: T => java.io.Closeable = _;
      def <init>()(implicit evidence$1: T => java.io.Closeable): this.My[T] = {
        My.super.<init>();
        ()
      }
    }
    ...

简单的说,是因为当前采用的底层实现方式限制了trait还不能支持类型参数的context boundsview bounds

想要达到目的的话,只能在每个方法层面声明:

scala> trait A { def foo[T:TypeTag](t:T):Unit }
defined trait A