函數式程式設計(Functional programming)已經歷經時代的考驗,這年頭做為一個開發者,或多或少都有聽過函數式程式設計這個名詞,不少主流語言中,也已經或逐步出現函數式程式設計的基礎元素,就連 Java 這個保守的語言,在 Java 8 中,除了 Lambda 語法本身具有一級函式(First class function)概念之外,也突然出現了不少函數式概念的 API。
這類主流語言中,不少本身並不是以函數式為主要典範(Paradigm),為了讓函數式元素在其本身中不至於過於突兀,這類元素多多少少都有經過一些調整,這類調整是必要的,這也是函數式程式設計得以逐漸為開發者接受的主因之一,經過調整之後,才使得讓這類元素得以成為開發者使用的選項之一。
然而,也正因為經過調整,在試圖從這類語言中探討函數式概念時,總有種朦朦朧朧看不清楚真貌的感覺,那麼,來學習一門純函數式語言如何?這就成了我想撰寫 Haskell Tutorial 一開始的動機。
實際上,已經有不少 Haskell 的好書,像是《Learn You a Haskell for Great Good!》,線上觀看是免費的,如果想購買電子書或實體書也行,中文翻譯為《Haskell 趣學指南》;其他書籍像是《Real World Haskell》也有線上版、電子書、實體書的選擇。
自己想來寫寫看,無非就是想整理一下這幾年的心得,用自己的順序來構築一個路徑。
安裝 GHC
那麼,就不閒話了,來看看如何用 Haskell 寫個「哈囉!世界!」吧!首先,安裝 Haskell 編譯器,這邊是在 Ubuntu Linux 下進行:
sudo apt-get install ghc
這邊使用的是 GHC(Glasgow Haskell Compiler),安裝好之後,實際上也會有個 GHCI,也就是直譯環境可以使用,直接輸入 ghci
指令就可以進入,先來幾個簡單的指令:
在這邊基本上可以看到,Haskell 是強型別語言,數值 5 不能與字串 “10” 直接進行運算。要離開 GHCI,可以輸入 :q
或按下 Ctrl+D。實際上,在 GHCI 環境中時,你可以輸入 :?
來取得許多環境中可使用的指令說明,讓我們再度進入 GHCI:
來試著用 :set prompt
改一下 GHCI 中的提示文字:
哈囉!世界!
一般來說,不少文件或書籍在介紹 Haskell 相關元素時,會有不少篇幅是使用 GHCI 來介紹的,因為這樣可以不用一開始接觸那麼多觀念,不過,我還是想先寫個原始碼、編譯、執行介紹,以便在一開始就能稍微瞭解這個流程。
那麼,該是來寫個「哈囉!世界!」的時候了,使用你慣用的編輯器,寫個 hello.hs:
main = putStrLn "哈囉!世界!"
原始碼記得用 UTF-8,這是 Ubuntu Linux 下預設的文字編碼,如果你要使用中文,這是最簡單的方式。Haskell 的程式進入點是 main
函式,putStrLn
會輸出指定文字之後換行,至於那個 =
,在 Haskell 中,每個函式都必須有傳回值,=
表示putStrLn
的傳回值(IO ()
,現在不用太去管它)會指定給 main
作為其傳回值。
在存檔之後,使用 ghc
進行編譯:
可以看到,編譯成功之後,會產生兩個檔案,.hi 是介面檔(interface file),包括了 hello 揭露(export)的函式訊息,.o 是目的碼(object code),執行之後,就可以看到「哈囉!世界!」。
互動版「哈囉!世界!」
還沒結束,通常我的第一個「哈囉!世界!」不會那麼簡單,至少要有個能與使用者互動的過程,那麼以下是第二個版本的「哈囉!世界!」:
請輸入你的名稱:
main = do
putStrLn "請輸入你的名稱:"
name <- getLine
putStrLn ("哈囉!" ++ name ++ "!")
寫個能顯示中文、有基本互動的「哈囉!世界!」,會比在那比哪個程式語言的「哈囉!世界!」可以最短來得有意義,通常可以稍微揭露一門語言背後的複雜度。像是在這邊,就看到了幾個之後都還會詳加探討的元素。
先探討幾個簡單的元素,第一是縮排,Haskell 對縮排有嚴格的要求,同一層次的程式碼必須擁有相同縮排,這點與 Python 類似,但稍微寬鬆一些,例如,以下也是可以的:
main = do putStrLn "請輸入你的名稱:"
name <- getLine
putStrLn ("哈囉!" ++ name ++ "!")
因為 do
包括的區塊是位於同一層縮排,但以下就不行:
main = do putStrLn "請輸入你的名稱:"
name <- getLine
putStrLn ("哈囉!" ++ name ++ "!")
接下來可以看到 ++
,這可以用來串接字串,實際上,之後會看到,++
實際上是個函式,事實上,Haskell 中幾乎都是函式,像 +
、-
、*
、/
運算都是函式。
在編譯與執行之後,你會看到以下結果:
嗯?提示輸入名稱可以不換行嗎?可以是可以,可以將 putStrLn
改為 putStr
就不會換行了,不過編譯後執行時反而更怪了:
提示文字怎麼會在輸入之後才出來,預設情況下,輸出會被緩衝,直到遇到換行符號,或者是緩衝區滿了才會輸出,你可以直接出清(flush)緩衝區:
import System.IO
main = do putStr "請輸入你的名稱:"
hFlush stdout
name <- getLine
putStrLn ("哈囉!" ++ name ++ "!")
hFlush
位於 System.IO
模組,在這邊使用 import
將之匯入,這樣一來就順眼多了:
更多細節
以上的解釋,作為之後範例程式的基礎已經足夠,不過,我還有幾個細節沒解釋,例如 do
與 <-
的作用,以下作個簡單解釋,不過有點複雜,你可以跳過沒關係,之後有機會就會解釋的!
在第一個「哈囉!世界!」中,putStrLn
會有 I/O 動作,I/O 動作實際執行是在被指定給 main
時,如果有多個 I/O 動作呢?你沒辦法將多個 I/O 動作同時指定給一個 main
,那麼就把這些 I/O 動作串在一起,成為一個大的 I/O 動作,那就是 do
的作用。
至於 <-
,嗯!從 IO Monad 中取出東西然後指定給 name
!Monad?一聽就很嚇人,其實,Monad 就只是個模式,只是相對來說,這個模式比較不容易觀察與抽取出來,我在〈Java 8 Patterns〉中,試著用白話文以及 Java 來解釋過 Monad 模式,有興趣的話,可以看看!
至於 IO Monad 的作用,在 Haskell 中是作為無副作用(side effect)的解套工具 … 嗯!越來越遠了 … 喂喂喂!你還在嗎? … XD
一步一步來吧!之後的文章看過之後,你就慢慢會瞭解的!