scala雾中风景(21): auto-tupling与auto-detupling

前几天和王福强讨论一个偏函数的问题,聊到了auto tupling的问题,以前有过几篇blog是跟这个相关的。参考:scala雾中风景(16): println(1,2,3)为什么work?scala雾中风景(17): toSet()的谜题;

其实当方法参数是Unit类型时,传入的参数不是Unit的话编译器最后自动生成一个”()” 也属于auto tupling的机制,只是之前不知道它们是同一种机制造成的,参考:scala雾中风景(4): Unit类型, 和 scala雾中风景(8): 高阶函数与Unit的谜题

不过在跟afo讨论的过程中发现另一个现象,用下面的例子来看:

class A{
    val p: Function[(Int,Int),String]  = { case (a:Int,b:Int) => "OK" }
    val p2: Function2[Int,Int,String]  = { case (a:Int,b:Int) => "OK" }
}

这里面的偏函数{ case (a:Int,b:Int) => "OK" } 即可以声明为Function类型,也可以声明为Function2类型。

最初我以为这里编译器是把偏函数类型向上造型,第一个能通过,第二个通不过,因为PartialFunction类型是继承自A=>B这种只有一个参数的函数类型,即Function[A,B],见scala源码:

trait PartialFunction[-A, +B] extends (A => B) 

所以,偏函数对象可以造型为Function,不能造型为Function2,因为不是它的子类;但实际中这么写却能编译通过,也没有给出任何警告,觉得有些蹊跷,确认了一下后边的偏函数{ case (x,y) ... } 里面case后边的小括号在模式匹配中是被当成Tuple对待的,猜测背后是类型推断时期做了一些适配的事情。

scala> import scala.tools.reflect.ToolBox

scala> val tb =  scala.reflect.runtime.currentMirror.mkToolBox()

scala> tb.parse("val p2: Function[(Int,Int),String]  = { case (a:Int,b:Int) => \"OK\" }")
res0: tb.u.Tree =
val p2: Function[scala.Tuple2[Int, Int], String] = <empty> match {
  case scala.Tuple2((a @ (_: Int)), (b @ (_: Int))) => "OK"
}

scala> tb.parse("val p2: Function2[Int,Int,String]  = { case (a:Int,b:Int) => \"OK\" }")
res1: tb.u.Tree =
val p2: Function2[Int, Int, String] = <empty> match {
  case scala.Tuple2((a @ (_: Int)), (b @ (_: Int))) => "OK"
}

今天有空用typer-debug参数分析了一下过程, 发现在类型推断的时候,也存在把一个tuple类型适配为(adapt)若干个方法参数的过程;这个过程跟auto tupling相反,称为auto detupling;通过编译器的-Ytyper-debug选项,分别编译这两种写法:

class A{
    val p: Function[(Int,Int),String]  = { case (a:Int,b:Int) => "OK" }
}

$ scala-2.11.4/bin/scalac -Yno-adapted-args -Ytyper-debug  A.scala >a 2>&1

class A{
    val p: Function2[Int,Int,String]  = { case (a:Int,b:Int) => "OK" }
}

$ scala-2.11.4/bin/scalac -Yno-adapted-args -Ytyper-debug  B.scala >b 2>&1   

然后通过diff工具来比较:

可以看到左边红色是Function2的声明,高亮部分:Tuple2确实被适配为了2个方法参数:

[adapt] Tuple2.type adapted to [T1, T2](_1: T1, _2: T2)(T1, T2)

遗憾的是,不像auto tupling可以通过-Yno-adapted-args编译选项禁止,auto detupling没有参数可以禁止掉,或许有我没有找到。

scala雾中风景(21): auto-tupling与auto-detupling》上有6个想法

  1. val p1: Function[(Int, Int), String] = {
    x => "OK"
    }

    val p3: Function[(Int, Int), String] = {
    x =>
    x match {
    case (a: Int, b: Int) => "OK"
    }
    }

    val p3: Function[(Int, Int), String] = {
    case (a: Int, b: Int) => "OK"
    }

    先写以上三种 Function[(Int, Int), String] 的写法。

    注意,p1 是所有 Function[T, U] 的标准写法,尽管在这里 T 是 (Int, Int),但对于编译器来说,最简单的处理方案就是统一按 p1 对待。

    然后看 p2,这个写法也是编译器很容易理解和处理的,没什么 magic。

    再然后,在 Scala 中,下列形式的代码:

    {
    x =>
    x match {
    case xxxxxx =>
    }
    }

    总是可以缩写为

    {
    case xxxxxx =>
    }

    这样,就自然得到了 p3,这也是编译器的一个统一约定,编译器理解和实现也是很一致和简单的。

    其实在 Map[T, U] 的 foreach 和偏函数等初中常看到这种缩写方式:

    Map("a" -> 1, "b" -> 2).forach {
    entry => entry match {
    case (k, v) =>
    }
    }

    总可以写成:

    Map("a" -> 1, "b" -> 2).forach {

    case (k, v) =>

    }

  2. 不过博主谈的其实应该是调用时为何 p(1,2) 和 p((1,2)) 都行 :-)

  3. 也即,若scalac -Yno-adapted-args,则 p(1, 2) 编译不过,只有 p((1, 2)) 可。

发表评论

电子邮件地址不会被公开。 必填项已用*标注