是的,我已然熟知各種辯證的法則
騰挪與算計之後
得出人生是牆角一條
很有歷史感的枯藤
最終的弧線折成一道
帶來龐沛記憶的鋒面
憂傷降臨,城市彷彿是
被一場雨狠狠埋葬的夜樹
顏色皆倉皇而退
...---何雅雯, 秋轉, 抒情考古學
那件事發生已經一個月了。城市崩毀後我待了很久,然後延著長路往下走想找看能不能再次遇到那座城,而舉目所見皆是荒涼。月昇月落,直到再也撐不住找了個樹下靠著睡著。醒來時我趴在書桌上,電腦的終端機開著 vim 寫著許多依稀有點印象的程式碼。
之後我還有進入那世界幾次,目前看來都是在夜裡寫 code 的時候。依然是那條看不見盡頭的路,我應該回頭看看莊園在不在,還是繼續往下走呢?
決定繼續往下走。後來我有再遇到幾個那種浮在光流上的城市,有些題目裡給了一堆大寫逗號分號,有些問我什麼 otp 什麼的,都答不出來所以沒辦法進去。只有一個問了 immutable 迴圈,答對進去了之後,發現跟似乎不是上次那個城市,建築的感覺不太一樣。我試著四處問有沒有人看過那隻小動物,但能跟我互動的各種人跟東西裡沒有知道的。
孩子出生了。臉好嘟啊,應該算可愛吧?在 fb 上打了 IO.puts "hello world"
。他長大以後也可以教他寫程式吧。他會想學嗎?
終於比較能睡過夜了,我跟寶寶都是。晚上不太有自己做事的餘裕,所以也沒什麼機會再去那個世界。不過前天有再去一次,往下走的景物開始變得不太一樣了。
我覺得我可能找到那個神領 Haskell 了。
說是可能,因為那邊其實什麼都沒有,但是有一種不太一樣的氛圍,而草地上散佈著大量小小的透明的文字,拿起來沒什麼反應,也看不太清楚寫什麼。不過只要我走到某一個點,其中有很多文字會聚集起來,變成一個透明的城門樣子的東西,門楣上有個符號,有點像是 但是比較方一點。
去了好幾次,每次都是同一個問題,問我說「五十度的水加熱五十度是幾度?」怎麼想都是一百度吧?但是每次講完門就消失了,然後什麼也沒有發生。這答案有錯嗎?是說這也不太像跟程式有關的題目吧?
「寶寶你還要蘋果嗎?要幾片?…我教你喔,這樣是一片、然後兩片、再來是三片…可以說說看嗎?1……2……然後 3」
「你應該還聽不懂吧?不過我跟你說喔,以前哪…曾經有一隻尾巴膨膨的棕紅色小動物跟我說過…」
是…兩年了嗎?再想到就好像有些情緒湧上來,但是我還是繼續往下說:
「更久更久以前人們學會把單位拿掉,就可以抽象的思考數字跟…」
等等。
單位,那時我們把單位拿掉了…
data
我們在乎的已不單單只是計算而已了。數字跟數字當然可以相加,但是許多時候的重點,在於這個計算有沒有意義。我們可以用型別 (Type) 來加以限制,只讓有意義的計算發生。
在 Haskell 裡,用 data
關鍵字來定義一個資料型別。例如我們可以這樣模擬出布林這個只有兩種可能性的型別:
-- Haskell 語法
data MyBool = Yes | No
bool = Yes
我們先宣告了一個型別,名稱叫 MyBool
,而它的值會是 Yes
或是 No
。語法中的 |
代表"或者",在 Haskell 中我們稱這種有"或者"的概念的型別為 Sum type。Sum 是總合,或是加的意思,意味著 MyBool
這個型別的值是 Yes
這一種,加上 No
這一種,總共只有這兩種。
接著我們在第二行宣告了一個變數叫 bool
,它的值綁定成 Yes
。在沒有標註型別的情況下, Haskell 會試著去推導出這個變數的型別 (type inference)。
(註*: Haskell 內建的布林值型別叫 Bool
,其值為 True
或 False
)
當我們想要定義一種東西叫做溫度,它有可能是攝氏,也有可能是華氏,而它的值會是一個浮點數 (帶有小數點的數) 時,我們會這樣做:
-- Haskell 語法
data Temp = Celsius Float | Fahrenheit Float
deriving (Show)
temp1 = Celsius 50.0
temp2 = Fahrenheit 50.0
Temp
是這個型別的名字,而 Celsius
跟 Fahrenheit
稱為這個型別的建構函式。(不過如果你之前在其它語言聽過這個字的話,請暫時學著失憶一下,這裡跟那個是相當不同的東西。)
這個型別說明了 Temp
的值 有兩類可能性。一類是 Celsius
後面帶著一個浮點數,另一類是 Fahrenheit
後面帶著一個浮點數。而因為浮點數本身有無限多種可能,所以 Temp
也會有無限多種,但不是任意的數字都可以當做 Temp
,一定要是浮點數,且前方要帶有 Celsius
或 Fahrenheit
其中一個。
另外為了要讓溫度能夠印出來,我們加上了 deriving (Show)
這個語法,不過暫時就先照抄,之後會更詳細的說明這部份。
然後我們宣告了兩個變數 temp1
及 temp2
。分別做成攝氏及華氏的 50 度。
接著我們要來實做溫度用的計算函式了。我們先規定只有同為華氏或同為攝氏的溫度能夠相加,像是這樣:
-- Haskell 語法
add :: Temp -> Temp -> Temp
add (Celsius t1) (Celsius t2) = Celsius t1 + t2
add (Fahrenheit t1) (Fahrenheit t2) = Fahrenheit t1 + t2
我們宣告了 add
函式。第一行是型別標注,說明 add
是接收兩個 Temp,回傳一個 Temp 的函式。而第二行跟第三行是兩種實做。第二行用了 pattern matching,將接收到的攝氏溫度 Celsius t1
及 Celsius t2
拆開取出數字的部份 t1
及 t2
,相加之後,再用 Celsius
做出一個新的溫度。
那麼我們就可以試著使用這個函式了:
-- Haskell 語法
temp3 = add temp1 temp1
t3 -- > Celsius 100.0
temp4 = add temp1 temp2
t4 -- > *** Exception: test.hs:(15,1)-(16,58): Non-exhaustive patterns in function add
當兩個溫度都是攝氏時,可以正確的相加。而因為我們沒有實做兩個溫度的單位不同時的加法,所以回傳了錯誤。另外它也告訴你這個函式的實做是 "Non-exhaustive",意思是你沒有考慮到這個函式會接收到的引數的所有可能性,因為我們沒有處理到華氏加攝氏,以及攝氏加華氏的情況。
我走到那個地點,地上的字很快的匯聚成門,問說:「知道了嗎?五十度的水加熱五十度?」
「不知道,因為你沒有說單位。兩個 50 度都是攝氏或都是華氏嗎?」
從城門左右兩邊開始,城牆迅速成形、延展、圍繞。半透明的牆與其上的城垛充滿著古典而嚴謹的氣息。
「歡迎。」城門開啟,我走了進去。
[to be continue]