2011/08/23

MoonScript幾支小程式

這篇要寫幾支MoonScript程式。

關於MoonScript的概觀介紹請看這篇如何安裝看這篇,完整的MoonScript語言參考手冊在這裡(英文)。

注意:我假設你已經會Lua了。

首先,向世界說聲哈囉吧。

寫一支沒有參數的函式並呼叫它。

func_hello = -> print "hello world"
func_hello!
func_hello()

輸出:
hello world
hello world

用->(箭頭)來產生函式,用=(等號)把產生出來的函式指派給func_hello,無參數的函式,除了可以用一般的()括號來呼叫,也可以用!(驚嘆號)來呼叫。而且這支函式的內容只有一個述句(statement),所以直接寫在->的後面即可。

如果函式內容多於一個述句,要縮排後一行一行放在->之下。

func_hi = ->
  name = "John Smith"
  print "Hi, I am " .. name

接下來,有參數的函式,要把參數宣告在->之前,以,(逗號)分開,以()括號包起來。

func_add = (x, y) -> return x + y
func_add2 = (x, y) -> x + y
print(func_add(1, 2))
print(func_add2(3, 4))

輸出:
3
7

func_add有return,func_add2沒有,但兩者是一樣的,這叫做implicit return,函式執行時最後的運算式,會回傳給呼叫者。

函式可以回傳多個值。

power_of_2_3 = (x) -> x*x, x*x*x
a, b = power_of_2_3 5
print a
print b

輸出:
25
125

函式呼叫可省略掉括號。

print(func_add(7, 8))
print func_add 7, 8

輸出:
15
15

省略括號後,參數會被傳進離它最近的函式,所以上面的7跟8會被傳進func_add。

當情況比較複雜會搞混時,當你想控制誰是誰的參數時,就用()括號來區分。

print "9 + 10 =", func_add(9, 10), "11 + 12 =", func_add(11, 12)


可以讓參數有預設值。

func_inc = (a, b=1) -> a + b
print func_inc 10, 5
print func_inc 10

輸出:
15
11

參數預設值的運算式,會一個跟著一個被執行,所以,後面參數可以使用前面參數。

foo = (x=100, y=x+5) -> x + y
print foo!
print foo 95

輸出:
205
195

接下來,介紹非常好用的table comprehension。過去,第一次看到Python的list comprehension時, 就覺得這玩意兒怎麼這麼強啊,真是太好用了,程式碼會變得更簡短,光一行就能完成超多事情。comprehension就好像把資料結構、迴圈、條件判斷 式通通攪和在一起,是個高階的程式概念,底下,就讓我用MoonScript來練習寫幾支table comprehension的程式吧。

若scores裡面是學生的成績,考的太差了,只好開根號乘以10,調整一下成績,以免當太多人,下次開課會教室不夠大。

scores = {64, 33, 25, 38, 48}

scores_modified = [math.sqrt(s) * 10 for i, s in ipairs scores]

print s - s%1 for i, s in ipairs scores_modified

首先,宣告變數scores,用{}大括號產生出table,裡面放了五個人的分數,在這裡,我們把table當做array或list來用,也就是說,裡面的項目是逐一排列的。然後,我們要把這個table裡面的項目,一個一個拿出來,也就是for i, s in ipairs scores這段所做的事情,i會是index,從1到5,在這裡沒有用到,s會是那五個成績,拿到成績後,開根號乘以10,算出調整後的成績,然後用[]方括號包起來,這就是comprehension,結果會是一個table,指派給scores_modified。

然後,用迴圈輸出成績,其中s - s%1是取出整數的部分,小數我們就不要了。

讓我們算一算調整前後各有幾個人被當。

stemp = [s for s in *scores_modified when s < 60]

print #stemp

同樣使用comprehension,因為逐一取出項目這個動作很常見,所以MoonScript加入一個語法,請看for s in *scores_modified,這個*星號,會等於for i, s in ipairs scores_modified,還有,我們用when來篩選出想要的項目,在這裡是小於60的成績,也就是不及格的,然後把結果放進table stemp裡。

#井號接table,表示table的大小,不過,這只有在我們把table當做array來用時,這個運算子#才會得到正確的結果。

執行看看吧,調整成績後,還有多少人會被當呢?

接下來,MoonScript與Lua有個不一樣的地方,就是if與for不僅是statement,也是expression,意思是說,它們都能回傳值。先看看if。

寫一個函式,給定一個成績參數,判斷是否過關或被當,第二個參數可有可無,若有,表示過關的最低成績,預設值是60。

pass_or_fail = (score, score_to_pass = 60) -> score >= score_to_pass

result = ""
if pass_or_fail 60
  result = "Pass"
else
  result = "Fail"

result = if pass_or_fail 60
  "Pass"
else
  "Fail"

如果if只是statement,不是expression的話,我們就必須要用上半部那種寫法;但MoonScript是expression,所以就能用下半部那種寫法。這兩種寫法的結果result會是相同的。

接下來看看for可以怎麼用。假設從1到10,奇數就保留,偶數就乘以2,怎麼寫呢?

doubled_evens = {}
for i=1, 10
  if i % 2 == 0
    doubled_evens[i] = i * 2
  else
    doubled_evens[i] = i

doubled_evens = for i=1, 10
  if i % 2 == 0
    i * 2
  else
    i

如果for只是statement,那就要用上半部的寫法,但MoonScript的for也是expression,所以可以用下半部的寫法,for每次迴圈的值,會被收集起來,放進table裡,最後被指配給doubled_evens。所以,這兩種寫法的結果相同。

另外,雖然Lua也可以寫OOP,但沒有直接的語法支援,總是會有礙手礙腳的地方,MoonScript加入了class、inheritance等支援,請你自己看看吧。

2011/08/22

MoonScript概觀介紹

這一篇把MoonScript的首頁,概略翻譯出來。

MoonScript是個動態腳本語言,以CoffeeScript的語法為基礎,不過,編譯後的語言是Lua

成功安裝MoonScript後,你會得到:兩支執行檔moonmoonc,以及Lua模組moonscript。使用moon可以直接執行一支MoonScript程式檔;使用moonc可以把MoonScript程式檔編譯轉成Lua程式檔,之後再執行;在Lua程式裡加上require "moonscript",就能看懂、載入、執行MoonScript檔案了。

因為編譯後的結果是Lua程式碼,所以相容於各種Lua實作,包括LuaJIT,也相容於所有已經寫好的的Lua程式庫。

完整語言的介紹,請見MoonScript語言參考手冊(英文),我也寫了幾支小小的程式。接下來要概略介紹一下,因為我們可把MoonScript想像成為Lua披上並擴充較好看的語法外衣,所以你需要對Lua具有一定程度的了解。


概觀

MoonScript提供簡潔的語法,利用縮排來判斷並分割程式碼的各部分,而不是像Lua一樣使用囉嗦的關鍵字,也不是像C語言一樣使用大括號,利用縮排來定義語法的程式語言,有名的有Python。底下是MoonScript的一些述句構成。

export my_func
x = 2323

collection =
   height: 32434
   hats: {"tophat", "bball", "bowler"}

my_func = (a) -> x + a

print my_func 100

其了較簡潔的語法外,還加入了其他特色,包括table comprehensions、函式裡的implicit return、類別(class)、繼承(inheritance)、scope的管理述句import與export、以及很便利的物件建構述句with。

import concat, insert from table

double_args = (...) ->
   [x * 2 for x in *{...}]

tuples = [{k, v} for k,v in ipairs my_table]

當有錯誤發生時,它還能指出是在原先檔案裡的哪一行出錯,而不僅是編譯後的檔案。


安裝

最容易的方式是利用LuaRocks,以底下提供的rockspec來安裝:
> luarocks build http://moonscript.org/rocks/moonscript-0.1.0-1.rockspec

詳細安裝過程,我寫在另外一篇

選用功能

如果你使用Linux,並且想用watch模式,此模式會監視.moon檔,當有變動時就自動編譯成.lua檔。你需要安裝linotify


原始碼

專案原始碼放在GitHub上:https://github.com/leafo/moonscript

有任何問題,請到這裡回報:https://github.com/leafo/moonscript/issues

最新的開發版本(或許不能動喔)可以用底下的rockspec安裝:
> luarocks build http://moonscript.org/rocks/moonscript-dev-1.rockspec


相依於其他軟體套件

除了Lua 5.1外,MoonScript還需要底下的Lua模組:
  • linotify(在Linux上的選用功能)

若你使用LuaRocks來安裝MoonScript,這些套件都會被自動取回並安裝。


學習

完整的語言參考手冊(英文)在此。


其他外掛

差異處的概略介紹
  • 利用縮排與空白字元來定義出程式區塊
  • 所有變數宣告,預設為區域變數。
  • export關鍵字來宣告全域變數,用import關鍵字來匯入table裡的東西,也就是取得一份區域性的拷貝。
  • 函式呼叫時,括號是可有可無的,類似於Ruby。
  • 胖箭頭,=>,用來產生具有self參數的函式。
  • 在名稱之前加上@(小老鼠),用來指稱它是個self裡的東西。
  • 運算子!(驚嘆號),可用來呼叫無參數的函式。
  • 根據函式裡最後一個述句的型別,自動加上implicit return。
  • 使用:(冒號)來分開table裡的鍵與值,而不是用=(等號)。
  • 換行(newline)可用來區分開table裡的每一項目,逗號(,)也可以。
  • \(反斜線)來呼叫物件的方法,而不是用:(冒號)。
  • 支援+=-=/=*=%=運算子。
  • !=~=的別名。
  • table comprehension,很便利的slicing與iterator語法。
  • 程式碼若一行,可以在後面加上迴圈與if述句。
  • if述句,可當做運算式使用。
  • 具有繼承的類別系統,建構在metatable __index屬性之上。
  • 建構子的參數,若以@開頭,會自動指定給物件。
  • 魔法般的super函式,將同名的類別方法對應到父類別的方法。
  • with述句,讓你以較短的語法存取無名的物件。


關於

MoonScript的語法,有很大程度是被CoffeeScript激發而來的。

沒有LPeg這個超棒超強的語法解析工具,MoonScript是不可能誕生的。

2011/08/21

MoonScript安裝

這一篇要講如何安裝MoonScript,至於概觀介紹可看這一篇

我先試著在Windows XP與Cygwin上安裝,但都沒有成功,MoonScript作者也說他主要的開發平台是Linux,所以我才在我的小白Mac OS X 10.6.8上安裝,滿順利的,後來又在Windows XP裡的VirtualBox裝Ubuntu 11.04,也滿順利的。


概略步驟如下:
1. 安裝Lua
2. 安裝LuaRocks
3. 安裝MoonScript(其中某部分需要安裝git)


在Windows上的安裝過程:
我沒有成功,你可以參考這一篇,看看眾多複雜的過程,以及最後出現的問題。


在Ubuntu上的安裝過程:
因為軟體都以套件打包好了,所以安裝很順利。

1. 安裝Lua
sudo apt-get install lua5.1

2. 安裝LuaRocks
sudo apt-get install luarocks

3. 安裝MoonScript(其中某部分需要安裝git)
sudo apt-get install git(若你沒有安裝過git的話)
然後以LuaRocks安裝MoonScript(底下這行指令是MoonScript官方網站寫的):
luarocks build http://moonscript.org/rocks/moonscript-0.1.0-1.rockspec

MoonScript需要LPeg、LuaFileSystem、alt-getopt這三個套件,所以會先安裝它們。

安裝成功後,moon跟moonc這兩個執行檔會在~/.luarocks/bin/下。


在Mac OS X上的安裝過程:
1. 安裝Lua
官方網站的下載區下載原始碼,我下載安裝的版本是lua-5.1.4.tar.gz。
tar zxvf lua-5.1.4.tar.gz,解壓縮。
cd lua-5.1.4,進解壓縮後的目錄裡。
make macosx,建構。
make install (sudo make install),安裝。

預設值會安裝到/usr/local下,執行檔(lua與luac)在bin底下,其他檔案散佈在include、lib、man、share底下。

2. 安裝LuaRocks
官方網站下載,我下載的是luarocks-2.0.5.tar.gz,

tar zxvf luarocks-2.0.5.tar.gz,解壓縮。
cd luarocks-2.0.5,進解壓縮後的目錄裡。
./configure
make
sudo make install

預設值會安裝到/usr/local下,執行檔(luarocks與luarocks-admin)在bin底下。


3. 安裝MoonScript(其中某部分需要安裝git)
這裡抓取Mac OS X的git安裝程式,按照下載後的dmg檔裡面的README.txt的指示安裝。

然後以LuaRocks安裝MoonScript(底下這行指令是MoonScript官方網站寫的):
luarocks build http://moonscript.org/rocks/moonscript-0.1.0-1.rockspec

MoonScript相依於LPeg、LuaFileSystem、alt-getopt這三個套件,所以會先安裝它們。


安裝成功的話,最後應該會出現如下的訊息:
moonscript 0.1.0-1 is now built and installed in /usr/local/ (license: MIT)

moon a.moon to run moonscript script file
moonc a.moon to compile it to lua code

moon跟moonc這兩個執行檔會在/usr/local/bin/下。


接下來就是寫MoonScript的程式,附檔名用.moon,以"moon xyz.moon"來直接執行,或是以"moonc xyz.moon"把MoonScript程式轉成Lua程式。請確定moon與moonc這兩支執行檔有在你的執行路徑PATH下。

完整的語言參考手冊在這裡(英文)。

2011/08/01

神偷天下(by 鄭丰)讀後感

注意:內有惡犬(劇情),請慎入。
注意:基本上這是一篇寫給自己的讀後感,拜讀鄭丰三部作品後把想法記錄下來;但這不是為了推薦此小說所寫的介紹文章,所以,如果你還沒看過此書,我想,還是不要往下看這篇文章比較好

書名:神偷天下(共三卷)
作者:鄭丰
出版社:奇幻基地

昨日到書局走走逛逛,要啟程回家之時,才突然瞥見這套鄭丰的新作,心中覺得奇怪,怎麼沒在其他家書店發現呢,一看書後的出版日期,7月28日初版一刷,這可是熱騰騰的新書啊,或許其他書店尚未到貨上架吧,話不多說,立刻帶走(喂,小子,要先付錢啊)。


主角是個小偷,不過從書名可知,必定不會單純是個雞鳴狗盜之輩,內文一開始也引用了莊子的“竊鉤者誅,竊國者侯”詞句,隱隱指出主角會扮演影響整個國家舉足輕重的角色。

本書的年代設定在靈劍之前,所以間或提及在天觀雙俠與靈劍中出現的前輩人物,諸如醫仙、文風流、神卜子、虎俠、雪豔、胡兒、百花仙子、丐幫趙漫、青幫成傲理等等,譬如醫仙為主角治傷、百花仙子取走萬蟲噬心蠱等情結,但出現篇幅不算多,情節描寫概略敘述帶過,說到底,主角其實不算個武林人物,共三本的頁數裡,第一本前半花在主角的出身,以偷盜為業的三家村,後半開始,主角便在東廠、皇宮、錦衣衛四處周旋,第二本作者讓主角遠離京城,跑到了邊蠻之地,瑤族、苗族、蛇族、大越國(交趾)等地,第三本再回到京城,主角打交道的角色,都是皇宮朝廷的人物,太監、貴妃、皇帝、太子、將軍、錦衣衛等等,雖然有虎俠這個武林中人對主角與情節發展有重要的影響,但畢竟不是重點所在,主角的所作所為,最終將會盜取天下,但又不是給自己,而是幫賢明的太子在爾虞我詐危機四伏的處境裡登上皇位,最後還犧牲自己的性命換取太子的壽命。老實說,我覺得,這真的是個很奇怪很奇怪的設定。

主角的能力,就是超高明的飛技與取技,也就是輕功與盜竊的技巧,不過武功就很普普通通了,書中主角最常做的事情,就是潛伏在他人宅邸,竊聽情報,不論是皇宮禁城還是監牢,來去自如無影無蹤,但這麼一來,就有別於一般武俠小說主角的行事與情節發展的過程了,譬如尋秦記的項少龍,作者會“偶爾”讓他偷聽到敵人的陰謀,以便扭轉情勢反將一軍,但本書卻是以竊聽為主軸,生於黑夜行跡隱密,倒掛屋樑絕無聲息,花上時間就能聽到敵方的詭計,一切都在主角的掌握之中,這會不會太方便了啊?段獨聖與凌霄有特異功能,那是種能顛覆整個世界的能力,所以靈劍要花上篇幅來介紹、處理(後來也把靈能毀去),本書主角有此超隱密的能力,而且無往不利,每每都能竊取到需要的情報,但到了最後的最後,卻沒能竊聽找出敵人的陰謀,以致於,主角要賠上自己的性命換取太子的壽命,這會不會太奇怪了啊,怎麼偏偏到了最後會竊聽不到呢?“偷聽”這回事,似乎在武俠小說裡在情節安排上免不了要出現,但如果要讓它常常出現,如我所說的,那就會跟一般武俠小說的基本設定不一樣,需要好好安排啊,不然會讓人(我)覺得不合理。

我看了尋秦記小說好幾遍,也看了電視劇,古天樂演的真不錯。

在天觀雙俠裡,有對於使毒精采的描寫,讓我眼睛一亮。本書中雖有對於偷盜的描寫,開鎖、陷阱、暗格、密室等等,但我沒有覺得太特別,難道是因為我看過鬼吹燈盜墓的小說嗎,所以,雖然本書有描寫了一些特殊練功的法門、各種寶物的來歷與介紹、竊取時的情境與過程、盜之有道的敘述,不過,或許是因為已經看過盜墓小說裡誇張有趣、天馬行空、荒誕不經的描寫,以致於看這本書時但並沒有讓我感到太特別。

另外,在第二本裡,主角跑進了邊荒之地,到一望無盡的靛海(叢林)裡去冒險了,有拜蜘蛛的瑤族與操控蛇群的蛇族、有天下奇物血翠杉,與老虎搏鬥、中了蜈蚣毒,還到了大越國(越南),幫國王黎灝攻下占城,還中了蠱毒,作了苗族苦力,參與了苗族巫王的爭鬥,雖然過程描寫的不錯,但我總有一種感覺:為什麼要有這些情節呢?為了要描寫主角與百里緞的情感糾結嗎,為了要詳細描寫主角的個性嗎,為了鋪下後文所以要介紹苗族的蠱毒嗎,為了除了描寫中原也寫寫邊疆地帶的風土民情嗎,種種問題在我腦里盤旋不休,對了,同前,因為看過了鬼吹燈到雲南、湘西、西域各地異想天開的冒險故事,以至於,這第二本並沒有讓我感到新奇有趣。

覺得神偷天下第二本裡的歷險故事很有趣嗎?可以看看鬼吹燈系列與其他相似的作品喔。

第三本,要開始進入結局了,主角得知他的身世卻又帶來給他甩脫不去的煩惱,亂七八糟的皇宮、東廠西廠權力傾軋勾心鬥角,主角為保太子不得不做盡壞事,差點被被虎俠所殺,不過,都不算是一般武俠小說的情結(啊,對了,是我搞錯了,這部作品不是武俠類的),愚昧的皇帝、擅權的貴妃、互別苗頭的太監們,我讀這些劇情,並沒有什麼感覺,讀著主角的發展,倒是總有一股很怪異的感覺,主角的個性,作者應該算是有描寫出來了,但我又覺得怪怪的,抓不住他到底是個怎麼樣的人啊,極度壓抑嗎,捨己為人嗎,因為備嘗艱辛所以一心要助太子登基讓這個世界更美好嗎,他的所做所有最後終於偷到了天下,但最後自己也死了,這是怎麼樣的一個人物啊,雖然作者從一開始就極細心地描寫,劇情人物與心境轉折都有寫出來,不過我卻不太能領略,或者是因為主角是個我不能代入的角色吧,不能將自己想像成主角,也就不能想像出那是個怎麼樣的情形,也就不能了解主角是作者所說的,是個活在無可奈何的情境下、身不由己的人物。

作者在後記中寫著,天觀雙俠是歡快的,靈劍是悲壯的,神偷天下是沉鬱的,我深有所感,神偷裡的人物,似乎沒有一個是能讓人高興快樂起來的,看著看著心情會變得很糟糕,作者又說,心境變了、作品也不斷轉型,以前,大家說金庸的鹿鼎記不是武俠小說,現在,我也認為神偷天下不像是武俠小說了。