Aug 7 2010

Scala: 方法名约定

Scala中没有操作符但是支持运算符重载,方法的最后一个字符也对优先级有影响,它决定了方法调用的目标。

Scala的这个约定在熟悉之后你会发现它提高了流畅性,例如,如果想要将一个值添加到一list中,可以写作:value::list,尽管可以读为:“value被附加到list上”,这个方法的目标实际上list而参数是value,也就是说list.::(value)。

如果方法名以冒号结尾,那调用目标是操作符后面的实例,在下一个例子中,^() 是一个定义在类Cow中的方法,而 ^:() 是一个定义在Moon中的方法:

class Cow {
	def ^(moon: Moon) = println("Cow jumped over the moon")
}
class Moon {
	def ^:(cow: Cow) = println("This cow jumped over the moon too")
}

调用如下:

val cow = new Cow
val moon = new Moon
cow ^ moon
cow ^: moon

Continue reading


Apr 26 2010

Scala : “运算符” 重载

运算符带了双引号,这是因为从技术上来说,Scala 并没有所谓的“运算符”,在这里是指重载符号,如+ ,+- 等等。
在 Scala 中,事实上这些是方法的名字。但是由于 Scala 并不需要点来分隔对象引用和方法名,因而看起来像是运算符。调用 ref1+ref2 其实与 ref1.+(ref2)是等同的,调用的其实是 ref1 的+()方法。

举例如下:

class Complex(val real: Int, val imaginary: Int) {
    def +(operand: Complex) : Complex = {
        new Complex(real + operand.real, imaginary + operand.imaginary)
    }
    override def toString() : String = {
        real + (if (imaginary < 0) "" else "+") + imaginary + "i"
    }
}
val c1 = new Complex(1, 2)
val c2 = new Complex(2, -3)
val sum = c1 + c2
println("(" + c1 + ") + (" + c2 + ") = " + sum)

运行结果:

(1+2i) + (2-3i) = 3-1i

再次强调,Scala 并不存在运算符,所以没有定义运算符优先级,但是同时又定义了方法的优先级。方法的第一个字符用来决定它们的优先级,如果两个同等优先级的字符在同个表达式中出现,那左边的优先。

Continue reading


Apr 26 2010

Scala : 交互式应用程序,函数组合

函数和交互式应用程序

回调(callback)在交互式应用程序中是很常见的。例如一个按钮被点击,并进行一个特定的动作。在Web 应用程序中创建回调是尤其困难的,除非你拥有像Scala这样强大的工具。下面创建一个方法,它生成一个随机的String,以作为一个全局唯一的标识符(GUID)。

scala> def randomName() = "I"+ scala.util.Random.nextLong.abs
randomName: ()java.lang.String

然后定义一个通用的JavaScript trait, 我们不需要刷新它,假设它包含可以在浏览器中运行的JavaScript 命令。

scala> trait JavaScript
defined trait JavaScript

下面用一个能生成 JavaScript的函数来创建一个Map 以关联GUID:

scala> var callbacks: Map[String, () => JavaScript] = Map()
callbacks: Map[String,() => JavaScript] = Map()
scala> def register(f: () => JavaScript) = {
| val name = randomName
| callbacks += name -> f
| <button onclick={"invokeServerCall("""+name+""")"}>ClickMe</button>
| }
register: (f: () => JavaScript)scala.xml.Elem

当用户点击按钮时,会用 GUID 创建一个Ajax HTTP 请求,Servlet 在map中查找GUID,如果GUID存在,这个函数就会被调用并将JavaScript则返回给浏览器,这个功能的代码类型于:

Scala> def handleAjax(guid: String): HttpResponse =
functionMap.get(guid).map(f => f()) match {
case Some(javaScript) => JavaScriptResponse(javaScript)
case _ => Http404Response()
}

上面的代码是 Lift 框架的简化版本,可以看到,这种写法可以让程序员更关注于业务逻辑。

Continue reading


Apr 25 2010

Scala : 作用域中的变量,容器

Functions Bound to Variables in Scope

函数被绑定到创建函数时所在的作用域内中的变量,这样就允许承载状态,例如首先声明一个名为 foo 的变量:

scala> val foo = "dog"
foo: java.lang.String = dog

下面创建一个引用了这个变量的函数:

scala> val whoTo = (s: String) => s+" "+foo
whoTo: (String) => java.lang.String = <function1>

调用这个函数:

scala> whoTo("I love my")
res0: java.lang.String = I love my dog

scala> whoTo("I walk my")
res1: java.lang.String = I walk my dog

函数可以绑定于vars 或 vals ,甚至可以修改vars,首先,创建一个var strs,它是一个 List[String]:

然后创建一个以String为参数并返回String的函数,并且它能修改strs:

scala> var strs: List[String] = Nil
strs: List[String] = List()

scala> val strF = (s: String) => {strs ::= s; s+" Registered"}
strF: (String) => java.lang.String = <function1>

调用这个函数:

scala>  strF("a")
res2: java.lang.String = a Registered

scala> strF("b")
res3: java.lang.String = b Registered

scala> strs
res4: List[String] = List(b, a)

把这个函数作为参数:

scala> List("p", "q", "r").map(strF)
res5: List[java.lang.String] = List(p Registered, q Registered, r Registered)

scala> strs
res6: List[String] = List(r, q, p, b, a)

可以看到每一步都正确地更新了 strs。

Continue reading


Apr 21 2010

Scala : Partial Functions 和 Type Parameters

方法和函数是不同的,在Scala中,除了方法以外,一切都是一个实例。方法被附加到实例之上,并且被实例所调用。Function是实现了 FunctionNN trait (NN是指参数的个数,在 Scala 2.8 的文档中可知,NN 的取值范围是 1~22)的实例。在运行时函数没有什么特别的地方,但在编译阶段,有一些方式可以让编写一个函数使用很少的代码。

Partial Functions

Partial function 是只有部份参数被 apply 的函数,在Scala中,可以从方法中来构建 partially applied function:

scala> def plus(a: Int, b: Int) = "Result is: "+(a + b)
plus: (a: Int,b: Int)java.lang.String

scala> val p = (b: Int) => plus(42, b)
p: (Int) => java.lang.String =

partial function 允许你基于已有的方法或函数来构建新的函数,参数可以用不同的括号来分组:

scala> def add(a: Int)(b: Int) = "Result is: "+(a + b)
add: (a: Int)(b: Int)java.lang.String

这样,就可以用下面的語法来调用:

scala> add(1)(2)
res1: java.lang.String = Result is: 3

通过这个語法,你可以传递一个独立于其它参数的代码快作为参数:

scala> add(1){
| val r = new java.util.Random
| r.nextInt(100)
| }
res2: java.lang.String = Result is: 15

同时,也允许很容易地将一个方法提升为一个partially applied function:

scala> def w42(f: Int => String) = f(42)
w42: (f: (Int) => String)String

scala> w42(add(1))
res5: String = Result is: 43

最后,也可以通过partially applying一个方法将它转换为一个函数:

scala> def f2 = add(1) _
f2: (Int) => java.lang.String

此时,这个函数也能被传递给另一个方法:

scala> w42(f2)
res6: String = Result is: 43

Type Parameters

方法可以拥有类型参数:类型参数定义了其它的类型或方法的返回值类型。但注意函数不能接收类型参数,一个的参数和返回值必须在函数定义时创建。

Continue reading


Apr 20 2010

Scala : Functions and Parameters

一个Function 是一个实例

在scala中,function是类的实例,所以在一个function上进行和类的实例一样的操作,下面创建了一个function并将它赋值给一个变量:

scala> val f:Int => String = x => "Dude: "+x
f: (Int) => String = <function1>

实际上它相当于:

scala> val f:(Int => String) = {  x=> "Dude :" +x }
f: (Int) => String = <function1>

可以在 f上调用方法:

scala> f.toString
res0: java.lang.String = <function1>

scala> f == f
res1: Boolean = true

scala> f == 4
res2: Boolean = false

用一个参数来apply function:

scala> f(44)
res3: String = Dude: 44

functions 作为参数传递

function能像任何其它参数那样被传递给方法。下面定义一个w42 方法,它接收一个输入为Int返回为String的function,在这里w42方法apply 42给函数,并返回結果。

scala> def w42(f: Int => String) = f(42)
w42: (f: (Int) => String)String

用函数变量来调用它:

scala> w42(f)
res11: String = Dude: 42

Continue reading


Apr 19 2010

Scala 集合框架:Tuple,Map[K,V],Option[T]

Tuple

Tuple 能让一个方法返回两个或三个值,例如下面的代码输入一个 List[Double],返回元素个数,元素的和及其平方和:

def sumSq(in: List[Double]): (Int, Double, Double) =
in.foldLeft((0, 0d, 0d))((t, v) => (t._1 + 1, t._2 + v, t._3 + v * v))

这里,编译器会将在括号中的集合元素看作是一个Tuple,上面的代码会翻译成:Tuple3[Int,Double,Double],这个函数接收两个参数:t和 v,t是一个Tuple3,而v是一个Double。
使用模式匹配可以让上面的代码更易理解:

def sumSq(in: List[Double]) : (Int, Double, Double) =
in.foldLeft((0, 0d, 0d)){
case ((cnt, sum, sq), v) => (cnt + 1, sum + v, sq + v * v)}

当然,也可以用其它方式来创建一个Tuple:

scala> Tuple2(1,2) == Pair(1,2)
scala> Pair(1,2) == (1,2)
scala> (1,2) == 1 -> 2

其中最后一种写法 1 -> 2 对于传递pairs 是很有用的一种方式。Pairs经常出现在代码中,包括用来创建 Maps 的 name/value 对。

对于Tuple元素的读取,应该使用下标 _n,例如 t._1, t._2 ,t._3 ,注意这里起始为1,并且对元素的引用不能越界。

Map[K,V]

Map 是一个 key/value 对的集合, key 是唯一的,但value 可以不唯一,Scala中的默认Map实现是不可变的,可以用于多线程环境,而且性能与Java中的HashMap相当。
创建一个 Map:

scala> var p = Map(1 -> "David", 9 -> "Elwood")
p: scala.collection.immutable.Map[Int,java.lang.String] = Map((1,David), (9,Elwood))

添加一个元素:

scala> p += 8 -> "Archer"

scala>  p = p + (9 -> "Archer")
p: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> David, 8 -> Archer, 9 -> Archer)

如果想取出一个不存在的元素:

scala> p(88)
java.util.NoSuchElementException: key not found: 88

这里会抛出异常,虽然看起来并不方便,因为Java的Map能够通过返回null来处理这个情况,但这样做有两个缺点:首先,必须对每个Map的访问进行 null-test,其次,这代表着不能在一个Map中存储null值。对此Scala有一个更好的机制,即Map的get()方法将返回一个包含了結果的Option(Some 或none):

scala> p.get(88)
res10: Option[java.lang.String] = None

如果key没有找到,可以返回一个默认值:

scala> p.getOrElse(99, "Nobody")
res55: java.lang.String = Nobody
scala> p.getOrElse(1, "Nobody")
res56: java.lang.String = David

也可以使用flatMap来返回key在1-5之间的值:

scala> 1 to 5 flatMap(p.get)
res13: scala.collection.immutable.IndexedSeq[java.lang.String] = Vector(David)

注意,表面上来看这里p.get 只是一个方法,但是Scala正期待着一个拥有特定类型的参数,而你传递了一个接收一个参数的方法,这时Scala会将这个方法和它未写的参数提升为一个函数。

Continue reading


Apr 19 2010

Scala 集合框架:List

本文的示例代码来自 <Beginning Scala> ,由于Scala中的集合框架已经重写, 这里的代码输出都是在 Scala 2.8.0RC1 下运行得出的,跟原书中有一定的差距。

1. Range

collections 可以拥有任意数量的元素,或者局限于零个或一个元素(如 Option ),Collections 可以是strict 或是 lazy 的。Lazy Collection 可以拥有不消耗内存的元素,直到它们被访问。例如一个 Range 类:

scala> 0 until 10 by 3
res7: scala.collection.immutable.Range = Range(0, 3, 6, 9)

在这里,Range 中的元素在被访问之前并不会被实例化,下面代码只访问了前5个元素:

scala> (1 to Integer.MAX_VALUE - 1).take(5)
res8: scala.collection.immutable.Range = Range(1, 2, 3, 4, 5)

这个例子中不会消耗很多内存,因为只有需要的元素才被创建。

2. 构造一个列表

Collection 可能是可变的(即引用的内容可以被改变),也可能是不可变的(即一个引用所指向的东西不可以被改变),但注意的是不可变的Collections 可以包含可变的项目。

Scala 的list[T]是一个linked list 类型的T,代表它可以是任意类型的顺序列表。在内部,List 是由 :: 构造的, 即Scala 的 :: 类,例如:

scala> 1::2::3::Nil
res9: List[Int] = List(1, 2, 3)

实际上,它和下面的代码是相同的:

scala> new ::(1, new ::(2, new ::(3, Nil)))
res10: scala.collection.immutable.::[Int] = List(1, 2, 3)

3. 添加元素

:: 接收 head 为单个元素,而tail 则是另外一个 list , 其中 :: 左边的是 head,而 :: 右边则是tail。

也可以利用 apply 方法生成一个list :

def apply[T](param: T*)

添加一个元素:

scala> val x = List(1,2,3,4)
x: List[Int] = List(1, 2, 3, 4)
scala> 99::x
res12: List[Int] = List(99, 1, 2, 3, 4)

注意在这里 x 并没有改变,而是创建了一个有新的head 和老的tail 的list。这个过程是很快的,是一个复杂度为O(1)的操作。

4. 归并列表

可以归并两个列表从而生成一个新的List,这个操作也是O(n),其中 n 是第一个List中的元素的个数。

scala> val x = List(1,2,3)
scala> val y = List(99, 98, 97)
scala> x ::: y
res3: List[Int] = List(1, 2, 3, 99, 98, 97)

过滤filter:

scala> List(1,2,3).filter(x => x % 2 == 1)

Continue reading


Apr 18 2010

Scala : Option 和 foldLeft

下面这段代码来自Beginning Scala,它的主要功能是读取输入的字符串,把其中的数字字符串转为整数,并相加求和,很简单的一段代码,但包含了很多語法细节。

import scala.io._

def toInt(in: String): Option[Int] =
  try {
    Some(Integer.parseInt(in.trim))
  } catch {
    case e: NumberFormatException => None
  }
def sum(in: Seq[String]) = {
  val ints = in.flatMap(s => toInt(s))
  ints.foldLeft(0)((a, b) => a + b)
}

val lines = Seq("1","2","3","str")
println("Sum is :"+sum(lines))

在这里,首先看 Option[Int],Option是一个很有趣的容器,它只包含零个或一个元素,如果是零个元素,则类型为 None,None 是一个 singleton ; 如果拥有一个元素,则类型为 Some( theElement),Option的一个重要作用是避免了空指针异常和显式的null 测试,如果Option是None,则逻辑代码遍历的是零个元素;如果是Some,则逻辑代码只应用于一个元素。本例中如果输入的字符串不能转化为整数,会抛出异常并返回 None。

至于 Seq 则是一个trait,它被许多集合类所继承,是Array,List 及其它一些序列容器的 supertrait。trait 类似于java 中的interface,可以将多个trait融合到一个类中,但不同的地方是trait中可以有实现的方法。Trait不能接受构造方法参数,除此之外它的行为和类是大致相同的,它和interface一样,实现多重继承的同时也避免了菱形问题。

最后看这一行实现了求和的的函数代码:

ints.foldLeft(0)((a, b) => a + b)

在这里,foldLeft 接收一个seed 值 0,并且将函数应用于这个seed和序列ints中的第一个元素,然后它取应用了函数之后的计算結果和序列中的下一个元素进行函数计算,如此一直迭代,直到序列中没有元素为止。foldLeft在计算累积值时显得很有用,数学中sum,prod,min,max 函数等可以很容易地用foldLeft来进行实现。(a,b) =>a+b 则是定义了一个简单的函数,返回相加的和。注意这里没有为a和b 定义类型,因为Scala的编译器会自动推导它们都是Int类型。

从这个例子也可以看到 Scala 的函数代码相当简约的,一行代码有时候包含信息量也很大。


Jan 27 2010

《Programming Scala》:Scala 的特性

本文是《Programming Scala》第一章的笔记,Scala 是一门很有趣也很简练的语言。

Scala为并行,简洁,可伸缩性而设计,它是一种函数式和面向对象的混合式语言,提供了基于actor的消息传递模型以去除并行带来的复杂性。使用这个模型,可以编写简洁的多线程代码(synchoroize关键字可以从词汇中消除了),而不用担心线程之间的数据连接以及处理锁和释放所带来的问题。

然而Scala也能用来编写单线程的应用程序,或者作为多线程应用的单线程模块,它的函数式风格有利于单线程和多线程的应用。Scala的并行模型与Erlang的风格很相似,但Scala的优点是:它是强类型而Erlang是弱类型,能在JVM上运行,与Java很好的进行交互。

下图展示了Scala在企业应用中适合的地方:

scala-in-enterprise

Scala in Enterprise Environment

注意到,可以将Java代码包装在Scala的actor库中提供线程的隔离,如果要在线程之间通讯,则使用Scala轻量级API来传递信息。在Scala中不用在创建线程时马上用同步来限制其并行,而是在不使用锁的情况下进行真正的并行操作,这就能更关注应用逻辑而不是低层次的线程。

Scala是静态类型的,但与Java 不同的是,不必明确反复地指定类型,很多情况下编译器可以进行类型推断,这一特性在简化函数的参数和返回值时很有用。

Scala的内核是很小的,其它包含操作符,Actor,都仅仅只是Scala库的一部份,可扩展性是极强,完全可以自行写一个实现。函数式编程是一种声明式的风格,在这种风格中指明的是“做什么”而不是“应该怎样做”,在XSLT,规则引擎,或者ANTLR中使用的就是函数式风格。

在一个数组中寻找最大值的函数式编程如下:

def findMax(temperatures : List[Int]) = {
	temperatures.foldLeft(Integer.MIN_VALUE) {
  		Math.max
	}
}

将列表中的每个元素乘以2:

val values = List(1, 2, 3, 4, 5)
val doubleValues = values.map( _ * 2)

计算一个数的阶乘:

def fact(n: Int) = 1 to n reduceLeft (_*_)

Continue reading