作者brianhsu (坟墓)
看板PLT
标题[比较] 我为何锺情於用 Scala 做为兵刃(三)
时间Sat Jan 15 18:12:04 2011
三、物件导向与 Functional Programming 的甜蜜相遇
选择的先决条件
--------------
继续来说为何 Scala 让我着迷不已,以至於我锺情於选择他做为我的兵刃
吧?
Scala 之父 Martin Odersky 曾经将 Scala 归类为 Postfunctional 的程
式语言,也在众多的演讲中,将 Scala 型容成一个 Unifier,是一个将物
件导向与 Functional Programing 这两种编程典范结合的程式语言。
其实这样宣称的程式语言并不少见--就光谱上比较靠近物件导向和程序导
向这边的,像是 Python 还有 Ruby 这些程式语言,都支援了一些 Functional
Programming 的功能;而光谱另一边,比较倾向於以 Functional Programming
一端的则有 OCaml 以及微软的 F# 等。
由於我一开始接触的程式语言就是 C / Pascal / C++ / Java 这类程序式
导向及物件导向的程式语言,所以在挑选上述众多的程式语言时,我很自然
地是在物件导向和程序导向这边寻找。
也因为如此,我不敢说光谱另一端的情况如何。但我必须承认,在靠近物件
导向和程序导向的这一端中,Scala 是我到目前为止,看过最令我惊讶的一
个。
我从来没想过 Functional Programming 竟然可以和物件导向如此合拍,甚
至,竟然可以透过物件导向的方式让我去了解 Functional Programming。
毕竟,我当初在学 Haskell 的时候,脑筋根本就转不过来,一堆东西都无
法理解啊。
为何说我会认为 Scala 是物件导向和 Functional Programming 的完美结
合,以及他可以让我用物件导向的观点来看 Functional Programming 呢?
纯物件导向与超方便的 Singleton
------------------------------
首先,Scala 是一个纯物件导向的程式语言,甚至让我有点讶异的纯--他
竟然没有 static / class 变数和方法!
相对的,他提供了超方便的 singleton 物件:
====================== 我是 Scala 程式分隔线 =========================
object MyObject {
var count = 0
def addCount() {
count += 1
}
}
MyObject.addCount()
MyObject.addCount()
println (MyObject.count)
======================================================================
猜猜看,MyObject 是什麽东西呢?!答案是--物件!而且是 Singleton
物件哟,也就是说整个执行期只会存在一份而已。
其实这样的做法和原先的 Java 有一些语意的不同,以致於有时会有小小的
不方便,我不知道是不是还有其他的缺点,但我知道的是他有两项立即的优
点:
- 不用再告诉学生什麽是 static 变数和方法了,因为我发现我当助教的
时候,好多人根本无法理解到底什麽是 static 变数。XD
- 你不用再试着实作出正确无误的 Singleton 模式了,只要一个 object
关键字 Scala 通通帮你搞定罗。
与物件超级合拍的 High Order Function
------------------------------------
High Order Function 在 Functional Programming 中是非常核心且常用的
技巧,几乎所有的 Functional Programming 程式中都可以见到这样的用法
--将某个函数当做另一个函数的参数。
举例来说,在 Functional Programming 中,如果我们要试着对 (-1, -2,
-3, 0, 1, 2, 3) 这个数列里的每一个原素做平方,最後再找出大於 5 的
有几个时,做法大致如下:
- 宣告一个数列 (-1, -2, -3, 0, 1, 2, 3)
- 宣告一个函数,这个函数接受一个整数 n 做为参数,返回整数 n + 1 为结果
- 宣告一个函数,这个函数接受一个整数 n 的参数,n > 5 时为 true,否则为 false
- 依序使用 map、filter 这两个 High Order Function,最後找出结果的数列有多长
以下是 Haskell 版的程式码(看不懂没关系,下面有 Python 版的):
====================== 我是 Haskell 程式分隔线 =========================
let xs = [-1, -2, -3, 0, 1, 2, 3]
let square n = n * n
let isGreaterThan5 n = n > 5
-- 使用 High order function 求解
let result = length (filter isGreaterThan5 (map square xs))
======================================================================
当然,既然号称引入了 Functional Programming 的编程典范,这点小事对
於 Python 也不算是什麽,也可以用类似的方式来求解:
====================== 我是 Python 程式分隔线 =========================
xs = [-1, -2, -3, 0, 1, 2, 3]
def square(x):
return x*x
def isGreaterThan5(x):
return x > 5
print len(filter(isGreaterThan5, map(square, xs)))
======================================================================
如果仔细注意的话,会发现这两个方式都是以函式为主体,其中的每个函式
都至少有一个参数是要处理的数列,而阅读最後一行的方式,是必须先找出
最内层的函式呼叫以及他所丢入的参数,再往外一层一层的分析。
或许习惯了 Haskell 之类的 Functional Programming 程式语言的人,可
以很轻松的在一长串的函式呼叫中抽丝剥茧,但我发现自己并不善长这样的
分析。
也因为如此,Scala 的做法是让我比较习惯的:map、filter、length 这些
东西,是 List 物件的方法。
换句话说,上面的程式,在 Scala 中会长得像下面这样:
====================== 我是 Scala 程式分隔线 =========================
val xs = List(-1, -2, -3, 0, 1, 2, 3)
val square = (n: Int) => n * n
val isGreaterThan5 = (n: Int) => n > 5
val result = xs.map(square).filter(isGreaterThan5).length
// 上面那行和下面这行等价
// val result = xs.map(n => n * n).filter(n => n > 5).length
println (result1)
======================================================================
如何,如果你也和我已经习惯了物件导向,是不是可以很精楚地说出 result
是怎麽计算出来的呢?!没错,直接从左边往右读:result 等於 xs 对应
sqaure 再滤出 isGreaterThan5 的东西,最後取得该数列的长度。
这里值得注意的地方,是你会发现,在 Scala 中 square 和 isGreaterThan5
其实和 Haskell 的版本比较接近--宣告的方式和变数一样,而不像 Python
版本,是使用宣告函式的语法。
会这样做的原因其实很简单--在 Scala 中,函数和 method 是不一样的,
Scala 中的 method 就是单纯的 Java method,没什麽特别的,不过函数的部
份就有趣了,我们等下会仔细研究这部份,现在先占且搁下。
再继续前,为了公平起见,我必须提一下。其实不只 Scala 是用这个方式,
Ruby 也是将这些常用的 High order function 当做阵列之类的物件的 method,
上面的程式用 Ruby 写起来可能像这样:
====================== 我是 Ruby 程式分隔线 =========================
x = [-1, -2, -3, 0, 1, 2, 3]
result = x.map{|n| n * n}.select{|x| x > 5}.length
puts(result)
======================================================================
在这边,因为我不熟 Ruby,所以我只会写将函数内嵌的版本,但我确定 Ruby
也能写出和 Scala 一样将函数放在外面的版本,我曾经看过,不过我忘记怎麽
写了。
函数也是物件?咁有影?!
------------------------
刚刚我们有提到,Scala 里的函数和方法是不一样的东西,在 Scala 中,
方法就是一般的 Java 物件的方法,那函数又是什麽呢?!
答案是:物件!
是滴,你没看错,在 Scala 中函数是单单纯纯的物件,是一个实做了
FunctionN[T1..TN+1] 这个介面(用 Scala 的术语来讲是 Trait)的物件。
上面那个的程式其实可以写成下面这样:
====================== 我是 Scala 程式分隔线 =========================
// new 一个这个 class 出来就是函数了,别怀疑
class Square extends Function1[Int, Int] {
override def apply(n: Int) = n * n
}
class IsGreaterThan5 extends Function[Int, Boolean] {
override def apply(n: Int) = n > 5
}
val xs = List(-1, -2, -3, 0, 1, 2, 3)
val result = xs.map(new Square).filter(new IsGreaterThan5).length
println (result)
======================================================================
看见了吧?我觉得这真的是 Scala 让我吓到了的一点,没料到他竟然会用
物件来实作出函数这个东西,而有了上面这样的对应之後,其实我发现
High order function 忽然之间就变得超级好理解了--不过就是呼叫了参
数物件的 apply 方法嘛!
如何,现在有没有想到如何自己实作出一个类似 map 的东西呢?其实很简单:
====================== 我是 Scala 程式分隔线 =========================
/**
* 将阵列中的每一个元素都套用 func 一次
*
* @param xs 要处理的阵列
* @param func 要套用到阵列上的函数
*/
def myMap(xs: Array[Int], func: Function[Int, Int]): Array[Int] =
{
val result = new Array[Int](xs.length)
for (i <- 0 until result.length) {
result(i) = func.apply(xs(i))
}
return result
}
val xs = Array(1, 2, 3)
val square = (n: Int) => n * n
val result = myMap(xs, square)
======================================================================
怎样,不知道你有没有发现,没想到 High Order Function 这个东西,竟
然可以用物件导向的概念解释的一清二楚呢?!
至少,我想我到接触到了 Scala 之後,这才开始了解 High Order Function
到底是怎麽一回事,到底是怎样运做的。
最後,在这一篇中我只提到了 High Order Function 这一点,不过就如同我
之前所说过的一样,Scala 是一个纯物件导向的程式语言,所以几乎 Scala
中的所有 Functional Programming 的概念,都是透过物件导向来当做实作
方式的。
除了 High Order Function 外,像是下面几个在 Functional Programming
中常用的东西,也都是使用物件导向的法式来实现的:
- Tuple
- Pattern Matching
- Monads
总而言之对我而言,Scala 扮演了一个很重要的角色--他用我所习惯的东
西,去解释另一个我不习惯的世界是如何运作的,而我发现,这个做法对我
来说真的超有效的!
如果你和我一样始终搞不懂 Functional Programming 的世界到底如何运作,
不妨也来试着玩玩看 Scala,也许也会和我一样有所斩获哟!
--
~
白马带着她一步步地回到中原。白马已经老了,只能慢慢地走,
'v'
Brian Hsu 但终是能回到中原的。江南有杨柳、桃花,有燕子、金鱼……
// \\
( 坟 墓 )
/( )\
但这个美丽的姑娘就像古高昌国人那样固执。 【白马啸西风】
^`~'^
http://bone.twbbs.org.tw/blog 『那都是很好很好的,可我偏不喜欢。』
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 114.32.42.198
1F:推 yauhh:最後有地方看不懂,你觉得"不过就呼叫apply嘛"来实现高阶函数 01/22 00:22
2F:→ yauhh:但是这句看起来好难理解. 01/22 00:23
3F:→ ihower:ruby的话, 改用 @zipCode.fetch("abc") 就会吐KeyError了 08/11 12:43