谈谈Scala的语法糖

语法糖:指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。

通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

语法糖和其他编程思想一样重要,什么duck type,人本接口,最小接口,约定优于配置,广义来讲都是一些思想上的“语法糖“。

实际上从面向过程到面向对象也是一种语法糖,C语言可以通过它的指针、类型转换,结构实现面向对象的编程风格,但是C++更进一步的推广了这种风格,更加易用,不过到了C#把OO的风格发挥得淋漓尽致

1.符号语法糖

初学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  

 

转载于:https://my.oschina.net/wii01/blog/893514

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值