2010-04-12

Case Classes

Scala 里有个叫做 "case classes" 的东西,这个应该是从 ML 家族的语言特性启发而来,习惯于 C/C++ 的程序员可能较少看到这个名词。比如,我们要写一个算术表达式解析器,比如下面就是一些合法的表达式:

  • 5

  • -5

  • 2+3

  • foo

  • 4 + foo


这在ML家族语言比如Haskell中非常容易表达。如下,这也是迄今为止我看到的最简洁的表达方式(看起来直接就是EBNF的描述方法):

> data Expr =
>     Number Int
>   | Var String
>   | UnOp String Expr
>   | BinOp String Expr Expr
> deriving (Show)


Scala 中用 "case classes" 来描述,如下:

abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String, left: Expr, right: Expr) extends Expr


看起来没有 Haskell 那么简单直接,但远远聊胜于无了:

scala> val op = BinOp("+", Number(1), Var("x"))
op: BinOp = BinOp(+,Number(1.0),Var(x))


Haskell中类似,

ghci> let op = BinOp "+" (Number 1) (Var "x")
ghci> op

BinOp "+" (Number 1) (Var "x")

标签: ,

2010-04-08

call by name vs. call by value

假设现在需要在Scala实现一个断言函数,我们该怎么做呢?可能第一反应是下面的代码:
> val assertionEnabled = true
>
> def myAssert(predicate: Boolean) =
>   if (assertionEnabled && !predicate)
>     throw new AssertionError

简单来说,就是我们期望传入一个bool值,然后检查它,比如:myAssert(5 > 3)。看起来很完美,至少一眼看下去简单得无法挑剔。但是,有一个重大问题是:无论断言是否被打开,predicate都会被计算一次。如果写成myAssert(foo(x,y)),则foo(x,y)会被执行一次 -- 虽然接下来因为&&是个短路操作符,myAssert()并不检查该函数的执行结果。这叫call-by-value[1],就是说参数传递给函数之前会先计算出其结果。

Scala除了支持C家族中的call-by-value外还支持call-by-name,代码改动很简单:

-- def myAssert(predicate: Boolean) =
++ def myAssert(predicate: => Boolean) =

我这样,predicate将会在myAssert中求值,而非传入时求值。C程序员会认为 "x = 1"是个赋值语句,而对习惯于FP思维的程序员来说,他们可能倾向另一种解释:x只是个name(label),但它绑定了一个类型为整型的数值1(或者x是个返回整数1的函数)。

参考:
[1]  Evaluation Strategy,http://en.wikipedia.org/wiki/Evaluation_strategy

标签:

2010-04-07

Stackable Modifications

Scala里面支持trait,可以提供类似Ruby mixin以及Java interface的功能,还有一个有趣的功能是 "stackable modification",感觉很像Ruby中常提到的 monkey patch。摘录 "Programming in Scala" 中的一个小例子,假设有个整型数构成的队列,提供两个基本操作:put和get,分别用于存、取一个数。接下来,我们对存入数据分别做三种操作:

  1. 倍乘 - 对输入数据n,存入n * 2;

  2. 增一 - 对输入数据n,存入n + 1;

  3. 过滤 - 对输入数据n,当且仅当 n 不为负数时才存入。


这在Scala里面实现起来相当舒服:

> abstract class IntQueue {
>   def get(): Int
>   def put(x: Int)
> }
>
> import scala.collection.mutable.ArrayBuffer
>
> class BasicIntQueue extends IntQueue {
>   private val buf = new ArrayBuffer[Int]
>   def get() = buf.remove(0)
>   def put(x: Int) { buf += x }
> }
>
> trait Doubling extends IntQueue {
>   abstract override def put(x: Int) {super.put(2 * x)}
> }
>
> trait Incrementing extends IntQueue {
>   abstract override def put(x: Int) {super.put(1 + x)}
> }
>
> trait Filtering extends IntQueue {
>   abstract override def put(x: Int) {
>     if (x >= 0) super.put(x)
>   }
> }


val q = new BasicIntQueue with Doubling
这样在q中存入10,则会取回20。

val q = new BasicIntQueue with Incrementing with Doubling
这样在q中存入14,则会取回29(double一次再增一)。

标签: