Scala中那些令人头痛的符号

Scala中符号语法糖

初学Scala看到那些稀奇古怪的符号(e.g.   <: , >: ,  <%  ,  =:= , <:< ,  <%<,  +T, -T ),总让人摸不着头脑,Scala创造这些语法糖究竟是要做甚?再参详了几篇文章(具体见参考文献)后,作者终于可以摸着一些皮毛了,于是决定记录下来。

1. 上下界约束符号 <: 与 >:

这对符号个人觉得是里面最好理解的了,这对符号用于写范型类/函数时约束范型类型。

先举个栗子:

def using[A <: Closeable, B](closeable: A) (getB: A => B): B =
  try { 
    getB(closeable)
  } finally {
    closeable.close() 
  }
例子中A <: Closeable(java.io.Cloaseable)的意思就是保证类型参数A是Closeable的子类(含本类),语法“A <: B"定义了B为A的上界;同理相反的A>:B的意思就是A是B的超类(含本类),定义了B为A的下界。

其实<: 和 >: 就等价于java范型编程中的 extends,super(PS: 说起来C#中只有where A:B形似的上界约束,怎么没有下界约束呢?求高人指教)

2. 协变与逆变符号+T, -T

协变是指能够使用与原始指定的派生类型相比,派生程度更大的类型。e.g. String => AnyRef

逆变则是指能够使用派生程度更小的类型。e.g. AnyRef => String

【+T】表示协变,【-T】表示逆变

3. view bounds(视界) 与 <%

<%的意思是“view bounds”(视界),它比<:适用的范围更广,除了所有的子类型,还允许隐式转换过去的类型

def method [A <% B](arglist): R = ...

等价于:

def method [A](arglist)(implicit viewAB: A => B): R = ...

或等价于:

implicit def conver(a:A): B = …

def method [A](arglist): R = ...

<% 除了方法使用之外,class声明类型参数时也可使用:

scala> class A[T <% Int]
defined class A

但无法对trait的类型参数使用 <%

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

4. 广义类型约束符号 =:=, <:<,  <%<

这些被称为广义的类型约束。他们允许你从一个类型参数化的class或trait,进一步约束其类型参数之一。下面是一个例子:

case class Foo[A](a:A) { // 'A' can be substituted with any type
    // getStringLength can only be used if this is a Foo[String]
    def getStringLength(implicit evidence: A =:= String) = a.length
}


这个隐式的参数 evidence 由编译器提供,A =:=String表示证明A是String类型(PS:即使A可以隐式转换成String类型也不行),因此参数a就可以调用a.length 而编译器不会报错。

我们可以如下使用:

scala> Foo("blah").getStringLength
res0: Int = 4


一旦我们使用其他不能转换成String类型的参数,就会报错,如下:

scala> Foo(123).getStringLength
<console>:10: error: Cannot prove that Int =:= String.
              Foo(123).getStringLength
                       ^
scala> implicit def charSeq2String(s: Seq[Char]) = s.mkString
charSeq2String: (s: Seq[Char])String

scala> Foo(Seq[Char]('a','b','c')).getStringLength
<console>:11: error: Cannot prove that Seq[Char] =:= String.
              Foo(Seq[Char]('a','b','c')).getStringLength
                                          ^


<:< 和 <%< 使用类似, 有细微不同:

  • A =:= B 表示 A 必须是 B 类型
  • A <:< B 表示 A 必须是B的子类型 (类似于简单类型约束 <:)
  • A <%< B 表示 A 必须是可视化为 B类型, 可能通过隐式转换 (类似与简单类型约束 <%)

5. 传名调用(call-by-name)符号: => type

传名调用 (Call by name)[编辑]

在“传名调用”求值中,根本就不求值给函数的实际参数 — 而是使用避免捕获代换把函数的实际参数直接代换入函数体内。如果实际参数在函数的求值中未被用到,则它永不被求值;如果这个实际参数使用多次,则它每次都被重新求值。

传名调用求值超过传值调用求值的优点是传名调用求值在一个值存在的时候总是生成这个值,而传名调用可能不终止如果这个函数的实际参数是求值这个函数所不需要的不终止计算。反过来说,在函数的实际参数会用到的时候传名调用就非常慢了,这是因为实践中几乎总是要使用如 thunk 这样的机制。

传需求调用 (Call by need)

“传需求调用”是传名调用的记忆化版本,如果“函数的实际参数被求值了”,这个值被存储起来已备后续使用。在“纯”(无副作用)设置下,这产生同传名调用一样的结果;当函数实际参数被使用两次或更多次的时候,传需求调用总是更快。

Scala中call by name使用:
object TargetTest2 extends Application {
  def loop(body: => Unit): LoopUnlessCond =
    new LoopUnlessCond(body)
  protected class LoopUnlessCond(body: => Unit) {
    def unless(cond: => Boolean) {
      body
      if (!cond) unless(cond)
    }
  }
  var i = 10
  loop {
    println("i = " + i)
    i -= 1
  } unless (i == 0)
}
上面的程序运行结果是
i = 10
i = 9
i = 8
i = 7
i = 6
i = 5
i = 4
i = 3
i = 2
i = 1




6.参考文献


阅读更多

没有更多推荐了,返回首页