作者brianhsu (坟墓)
看板PLT
标题[比较] 我为何锺情於用 Scala 做为兵刃(一、二)
时间Sat Jan 15 17:52:54 2011
一、前言(废话)
就像我之前所提到的,我觉得程式语言对於写程式的人来说,就像是兵器一
样,而行走江湖,有一件趁手的兵刃,往往可以事半功倍。
而兵刃没有好坏之分,端看用的人如何使用,有的人喜欢大刀的迫力,也有
人锺情於长剑的轻灵,而不论刀也好,剑也罢,最终的目的都是克敌致胜,
不论用什麽样的兵器,只有能打退敌人才是真的。
程式语言其实也差不了多少,不论是动态型别的程式语言或静态型别的程式
语言,不论是物件导向或是 Functional Programming ,最终的目标都是要
解决问题。
只要能解决问题,能克敌致胜,靠他赚得到钱(?),用什麽样的程式语言
并不是什麽大不了的事情。
所以虽很爱看大家嘴炮各种程式语言的优劣,但其实一直以来都觉得,程式
语言并没有什麽好坏之分,只不过是每个人的喜好不同罢了。
但另一方面,就像行走江湖的人都有各自偏好的兵刃一般,甚至即使同样是
长剑,都还有惯用的长短轻重等,我相信写程式的人也都有自己的偏好以及
喜欢的编程典范。
同时不论是兵器或程式语言,如果手上拿的是自己趁手的东西,往往会对其
产生不同的情感,甚至觉得如果失去了手上的东西,就像断了手脚一般不自
在。
而这一系列的文章,就是叙述我身为一个写程式的人,为何在众多的程式语
言之中,挑选了 Scala 做为了我的兵刃,并且渐渐地爱上了他。
二、静态型别的动态语言
就如同武林人士一样,要找到一把适合自己的兵刃,往往不是一天两天的事
情,我在找到 Scala 之前,也经常去看各种的程式语言的教学之类的,总
希望找到自己中意的程式语言。
在这一系列中,经常会将 Scala 与其他语言做比较,用以说明我为何喜欢
上了 Scala,但其实我并没有真正在使用这些用来举例的语言,所以如果其
中有什麽谬误,还请见谅。
另一方面,我也说过了,我觉得程式语言没有什麽好坏之分,在这边的举例
只是单纯用来说明我喜欢 Scala 的缘由,如果批评到你喜欢的程式语言,
同样也请见谅,我想这只是大家的偏好不同罢了。
首先,我爱上 Scala 的一点,就在於他有着『静态型别的动态程式语言』
这个称号。
这里举一个简单的例子,我们将会用 Java / Ruby / Python / Scala 四种
不同的程式语言来实作,并观察 Scala 到底有什麽令我爱不释手的地方。
下面是程式的规格:
- 宣告一个名为 zipCode 的对应表,这个对应表是邮递区号(整数)到
地区(字串)的对应
- 宣告一个 getArea221(shouldCrash) 的函式,这个函式会取得 221 这
个邮递区号的地区
- 如果 shouldCrash 为 false,用整数 221 来当做索引取出 zipCode
相对应的值
- 如果 shouldCrash 为 true,用字串 "abc" 当做索引取出 zipCode
相对应的值
- 当然,既然是邮递区号,用字串 "abc" 来查询是完全不合理的
- 在主程式中执行下列下三个步骤
- 印出 Hello World
- 呼叫并印出 getArea221(false) 的值
- 呼叫并印出 getArea221(true) 的值
首先来看一下 Java 版的实作:
====================== 我是 Java 程式分隔线 =========================
import java.util.HashMap;
public class Test
{
static HashMap<Integer, String> zipCode =
new HashMap<Integer, String>();
static String getArea221 (boolean shouldCrash)
{
if (shouldCrash) {
return zipCode.get("abc");
} else {
return zipCode.get(221);
}
}
public static void main (String [] args)
{
zipCode.put(221, "汐止");
zipCode.put(115, "南港");
zipCode.put(545, "埔里");
System.out.println("Hello World");
System.out.println(getArea221(false));
System.out.println(getArea221(true));
}
}
=====================================================================
嗯,看起来就是相当的,嗯……Java,一堆罗哩八唆的规举和型别,看了就
讨人厌!
试着编译一下,可以过关,执行一下,产生了下列的输出:
====================== 我是执行结果分隔线 ===========================
brianhsu@NBGentoo ~/test $ java Test
Hello World
汐止
null
brianhsu@NBGentoo ~/test $
=====================================================================
等等!最後一个 null 是怎麽回事咧?我以为 Java 是静态型别加上强型别
的程式语言,应该可以在编译时期就告诉我,我的程式有型别不符的地方不
是吗?又臭又长却又没什麽用,难怪大家讨厌 Java,都跑去拥抱 Ruby 和
Python 了。(泪)
好,那我们来看一下最近的後起之秀 Ruby 的表现如何呗!
====================== 我是 Ruby 程式分隔线 =========================
#!/usr/bin/ruby
@zipCode = {221 => "汐止", 115 => "南港", 545 => "埔里"}
def getArea221(shouldCrash)
if (shouldCrash)
@zipCode["abc"]
else
@zipCode[221]
end
end
puts("Hello World")
puts(getArea221(false))
puts(getArea221(true))
=====================================================================
嗯,果然不负众望,整整减少了近十行的程式码,在简洁方面,彻彻底底地
打败了 Java 啊!
那在抓出错误方面比之 Java 又如何呢?我们来执行这只程式看看:
====================== 我是执行结果分隔线 ============================
brianhsu@NBGentoo ~/test $ ruby test.rb
Hello World
汐止
nil
brianhsu@NBGentoo ~/test $
=====================================================================
喔喔!出现 nil 了!听说 nil 在 Ruby 里的地位大概就和 Java 中的 null
一样,所以看来在这个例子里,Ruby 在简洁方面的表现胜出,但抓错误方
面好像半斤八两。
那麽这个月冲上 TIOBE Index 第五名,大家都说相当简洁易学的 Python 的
表现又如何呢,咱们来看看:
====================== 我是 Python 程式分隔线 =======================
#!/usr/bin/python
#coding=utf-8
zipCode = {221: "汐止", 115: "南港", 545: "埔里"}
def getArea221(shouldCrash):
if shouldCrash:
return zipCode["abc"]
else:
return zipCode[221]
print ("Hello World")
print (getArea221(False))
print (getArea221(True))
=====================================================================
大家的说法不是没有道理的,基本上看起来和 Ruby 长得差不多,都非常简
洁,所以在简洁性方面也是大胜 Java。
不过不知道 Python 是不是能抓出这个程式码不合理的地方呢?来试试看好
了:
====================== 我是执行结果分隔线 ============================
Hello World
汐止
Traceback (most recent call last):
File "test.py", line 16, in <module>
print (getArea221(True))
File "test.py", line 9, in getArea221
return zipCode["abc"]
KeyError: 'abc'
======================================================================
太棒了,Python 告诉我们有 KeyError 发生,也就是说字串 abc 不能当做
zipCode 的索引。
但是等一下,为什麽前面还是有 Hello World 和『汐止』被印了出来呢?
嗯……因为这是『执行期错误』,也就是说如果我们把程式改成下面这样:
====================== 我是 Python 程式分隔线 =======================
#!/usr/bin/python
#coding=utf-8
zipCode = {221: "汐止", 115: "南港", 545: "埔里"}
def getArea221(shouldCrash):
if shouldCrash:
return zipCode["abc"]
else:
return zipCode[221]
print ("Hello World")
print (getArea221(False))
======================================================================
很好,什麽事都不会发生,虽然这份程式码实际上是有问题的,只要有人呼
叫了 getArea221(True) 他就会在执行的时候毫不留情的爆炸给你看。
不过,会爆炸给你看,似乎总比给你一个 null 或 nil 好得多的样子?!
最後,我们来看看我最爱的 Scala 长得怎样:
====================== 我是 Scala 程式分隔线 =========================
val zipCode = Map(221 -> "汐止", 115 -> "南港", 545 -> "埔里")
def getArea221(shouldCrash: Boolean) = {
if (shouldCrash) {
zipCode("abc")
} else {
zipCode(221)
}
}
println ("Hello World")
println (getArea221(false))
println (getArea221(true))
======================================================================
『呃……十五行?!你真的确定这是静态型别的程式语言吗?!』
没错,看起来很不可思议吧?!和 Ruby / Python 版的看起来差不多,唯
一看到的型别好像也只有 Boolean 而已咧?!
如果不相信,我们来执行一下:
====================== 我是执行结果分隔线 ============================
brianhsu@NBGentoo ~/test $ scala test.scala
/home/brianhsu/test/test.scala:7: error: type mismatch;
found : java.lang.String("abc")
required: Int
zipCode("abc")
^
one error found
brianhsu@NBGentoo ~/test $
======================================================================
呃,test.scala 第七行有型别错误,找到的是 java.lang.String,但需求
的是 Int,这一行程式码是 zipCode("abc")!
注意!Hello World 没有被印出来,也就是说,这只程式在还没执行的时候
就被发现错误了!而不像 Python 是要等到执行到那一行的时候才爆炸给你
看。
事实上,就算你把最後一行拿掉,变成下面这样:
====================== 我是 Scala 程式分隔线 =========================
val zipCode = Map(221 -> "汐止", 115 -> "南港", 545 -> "埔里")
def getArea221(shouldCrash: Boolean) = {
if (shouldCrash) {
zipCode("abc")
} else {
zipCode(221)
}
}
println ("Hello World")
println (getArea221(false))
======================================================================
他一样会爆给你看,证明了 Scala 确实是静态型别的程式语言--在执行
前,会先进行型别检查,确定型别都没问题之後,才会真的开始执行。
====================== 我是执行结果分隔线 ============================
brianhsu@NBGentoo ~/test $ scala test.scala
/home/brianhsu/test/test.scala:7: error: type mismatch;
found : java.lang.String("abc")
required: Int
zipCode("abc")
^
one error found
brianhsu@NBGentoo ~/test $
======================================================================
但如果我把上面的程式码中的 abc 改成整数 115 ,那程式就可以正常执行
了,同时执行的结果也会一如预期的是分别印出 Hello World、汐止和南港
这三行字,如下所示:
====================== 我是 Scala 程式分隔线 =========================
val zipCode = Map(221 -> "汐止", 115 -> "南港", 545 -> "埔里")
def getArea221(shouldCrash: Boolean) = {
if (shouldCrash) {
zipCode(115)
} else {
zipCode(221)
}
}
println ("Hello World")
println (getArea221(false))
println (getArea221(true))
======================================================================
====================== 我是执行结果分隔线 ============================
brianhsu@NBGentoo ~/test $ scala test.scala
Hello World
汐止
南港
brianhsu@NBGentoo ~/test $
======================================================================
如何,是不是很神奇呢?这也是我喜欢上 Scala 的一个原因--他或许比
Ruby / Python 这些动态语言多了一些型别标示(主要是在函式宣告的部份),
但看起来仍然比 Java 简洁很多,但同时又是静态型别与强型别的程式语言。
这对我来说是相当重要的一点,因为我很清楚我是个粗心的人,之前写 PHP 时
常常 debug 半天,结果却发现只是因为我把变数或函数的名称打错,而 Scala
可以在维持程式码简短的同时,又帮我抓出类似的错误,怎麽能让我不欣赏他
呢?!
後话:
後来有朋友提出来 Java 会有这个现象是为了保留弹性,虽然我不清楚这边所谓
的弹性指的是什麽,不过这提醒了我,上面的例子其实没有完全表现出 Scala
的有趣之处。毕竟 zipCode 就是一个整数对应到字串的 Hash Table 嘛!
而爱用 Ruby / Python 的朋友可能也会想到,Ruby / Python 允许你在同一个
Hash Table 中用不同型态的东西当 Key。
那麽,Scala 又是如何呢?事实上,让我对 Scala 感到惊艳的地方,就在於他
的行为通常都合理到让你觉得不可思议,常常就是你要的东西。
例如,下列的 Scala 程式码是合法的:
====================== 我是 Scala 程式分隔线 =========================
val map = Map(1 -> "一", 2 -> "二", 2.1 -> "二点一")
println (map(1))
println(map(2))
println(map(2.1))
======================================================================
执行结果:
====================== 我是执行结果分隔线 ============================
brianhsu@NBGentoo ~ $ scala test.scala
一
二
二点一
brianhsu@NBGentoo ~ $
======================================================================
看吧!Scala 也可以用不同的型态当做键值哟!那如果在这个范例中,试着用字
串来取出 map 里的东西又会如何呢?!
====================== 我是 Scala 程式分隔线 =========================
val map = Map(1 -> "一", 2 -> "二", 2.1 -> "二点一")
println (map(1))
println (map("我是字串"))
======================================================================
他会告诉你……
====================== 我是执行结果分隔线 ============================
brianhsu@NBGentoo ~ $ scala test.scala
/home/brianhsu/test.scala:3: error: type mismatch;
found : java.lang.String("我是字串")
required: AnyVal
println (map("我是字串"))
^
one error found
brianhsu@NBGentoo ~ $
======================================================================
编译时期的型态错误,如何,神奇吧?!这样的行为真的是深得我心啊,叫我怎
麽能不爱上他呢?!
最後的补充:
後来有一点我发现我忘记提了,为了公平起见还是要提一下--Scala 的型别系
统还是有其极限的。
举例来说,在上述的例子中,Scala 是试着去找类别树上能够包含所有 Map 里
的键的型态的类别,在这个例子是 AnyVal--类似於 Java 的基础资料型别。
而换句话说,AnyVal 包含了 Boolean 这个型别,也就造成了你可以正确地编译
下列的程式,但他会在执行期产生错误。
====================== 我是 Scala 程式分隔线 =========================
val map = Map(1 -> "一", 2 -> "二", 2.1 -> "二点一")
println (map(1))
println (map(true)) // Runtime Error!!
======================================================================
--
~
白马带着她一步步地回到中原。白马已经老了,只能慢慢地走,
'v'
Brian Hsu 但终是能回到中原的。江南有杨柳、桃花,有燕子、金鱼……
// \\
( 坟 墓 )
/( )\
但这个美丽的姑娘就像古高昌国人那样固执。 【白马啸西风】
^`~'^
http://bone.twbbs.org.tw/blog 『那都是很好很好的,可我偏不喜欢。』
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 114.32.42.198
※ 编辑: brianhsu 来自: 114.32.42.198 (01/15 17:55)
1F:推 Ntst0:看到第 8/20 页 就 1) 01/21 04:09
2F:→ Ntst0:不过 gentoo给我很乱的感觉 0 0 ;; 01/21 04:10
3F:推 yauhh:原来如此,真是很有深度的讨论 01/22 00:07
4F:推 rexkimta:那如果是型别符合,但是没有这个key值呢?例如最後例的3 02/04 22:32
5F:推 teslare:伤当有意思的讨论! 02/13 01:40