原文。
https://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/First_Steps
首先,你需要安裝一個ghc。在Linux上,它常常被預先安裝好了或者能夠通過apt-get或者yum命令來輕松搞定。你也可以從官網直接下載它。不過除非你確信你想從源碼去編譯它,否則下載一個二進制包就可以了。像安裝其他的軟件包一樣下載和安裝它既可。這個教程是在Linux下完成的,但是只要你會使用相關的命令行操作,它在Windows或是Mac環境下也一樣能工作。
對UNIX或者Windows Emacs用戶來說,這里有一個很棒的Emacs mode,包括了語法高亮和自動縮進的功能。Windows用戶則能夠直接使用記事本或者其他文本編輯器:Haskell的語法對記事本相當友好,盡管你仍要小心處理縮進。Eclipse用戶建議使用eclipsefp插件。
現在,是時候寫你的第一個Haskell程序了。這個程序將通過命令行讀入一個名字然后輸出一個問候語句。建立一個以 ".hs” 結尾的文件并輸入下列代碼。當心縮進,否則你的程序可能沒法通過編譯。
module Main where
import System.Environment
main :: IO ()
main = do
args <- getArgs
putStrLn ("Hello, " ++ args !! 0)
我們來看下這段代碼。前兩行表示我們講創建一個名叫Main的模塊,并且導入了System這個模塊。所有的Haskell程序都會從Main模塊里的一個叫做main函數的地方開始運行。你可以在這個模塊中導入其他的模塊,但是如果沒有了它,編譯器就無法生成可執行文件供用戶運行。此外Haskell是大小寫的敏感的:模塊名稱需要是大寫開頭的,而函數聲明則必須是非大寫開頭的。
main :: IO ()
這行是函數的類型聲明:它表示main函數的類型是IO()
,是一個返回Unit類型()
的IO操作。一個Unit類型僅會包含一個值,而()
,它表示什么也沒有。類型聲明在Haskell里是可選的:編譯器能夠自動的識別它們,當你的聲明和編譯器自動識別發生沖突的時候它則會報錯。在這個教程中,為了更清晰的說明,所有的類型都是顯式聲明的。而你在家里跟著做的時候,你可能更愿意選擇忽略,因為在編寫這個程序的時候其實并不太需要去在意它們。
IO類型是Monad類型類的一個實例,Monad是一種抽象的概念,如果滿足以下這兩個條件,那我們就會說這樣的值是Monad:
- 這個值包含了一些特定類型的附加信息;
- 大多數函數不需要去關心這些附加的信息。
在這個例子里,
- 這個附加的信息就是將被執行的IO操作;
- 而這個附加信息的值是不存在的,表示成
()
。
IO[String]
和IO()
都同樣屬于IO Monad類型,但它們有著不同的基本類型。它們作用于(或是傳遞)不同類型的值,[String]
和()
。
那些包含了附加信息的值則被稱作“Monadic”。
Monadic值常被稱作“操作” ,因為最容易的思考IO Monad用途的方法就是把它當做一系列可能會影響外界世界的動作。這一系列動作會傳遞一些基礎的數值,然后在這個過程中每個動作都會對這些值進行影響。
Haskell是一個函數式的語言:與給出計算機一系列指令從而讓它執行不同,你需要給Haskell一系列定義來告訴它每一個函數來如何處理。這些定義會將各種動作和函數組合在一起。而編譯器會識別出將它們組織在一起的執行方式。
要寫出這樣一個定義,你首先需要建立一個等式。等式的左邊是一個名稱,可能還會帶有若干個與變量綁定的模式(后面會解釋)。右邊的話,會給出一些由其他定義組合而成的式子,從而告訴計算機如何遇到該定義時如何進行計算。這些等式就和一般的代數表達式一樣:你總是可以在程序中用等式右邊的部分來替代左邊的名字,并且得到與之前相同的結果。這種行為被稱作“引用透明”,而這種性質使得Haskell代碼比其它的語言更加易于理解。
那我們應該怎么定義我們的main函數呢?我們知道它必須是一個能夠從命令行讀入參數,然后從打印出一些輸出,最終返回()
(空值)的IO()
操作。
這里有兩種方法創建一個IO操作:
- 使用return函數提升一個普通值進入IO Monad。
- 連接兩個已經存在的IO操作。
因為我們接下來要做兩件事情,所以我們選擇第二種方法。我們通過內建函數getArgs讀入命令行參數并把它們存入一個字符串列表。而內建函數putStrLn則能夠讀入一個字符串然后將它輸出到終端。
我們使用一個do代碼塊來連接這兩個操作。一個do代碼塊包括很多行,所有的行按照第一個非空白字符在do后面排列,并且每行都可能是如下兩種形式之一:
- name <- action1
- action2
第一種形式將action1的結果和name綁定,從而你可以在下一個操作中使用它。例如,如果有action1的類型是IO[String]
(一個會返回一個字符串列表的IO操作,就和getArgs一樣),那name就會在接下來的一系列操作里和這個返回的字符串列表通過綁定操作符>>=
綁定在一起。第二種情況僅僅執行這個action2,并通過>>
操作符同下一行連結在一起。綁定操作符在處理不同Monad的情況下有不同的語義:在IO Monad中,它會連續執行所有的操作,然后對外部世界產生這些操作帶來的副作用。由于這個綁定符號的語義依賴你具體使用的Monad類型,所以你并不能在同一個do代碼塊里把不同類型的Monad類型的操作糅雜在一起---在這里只有IO Monad是可用的(在同一個管道中)。
當然,這些操作可能自己會調用其他函數或是復雜的表達式,然后繼續傳遞它們的計算結果(通過調用return或是其他最終調用了return的函數)。
在這個例子里,我們首先取出參數列表中的第一個元素(args !! 0)
,然后把它拼接到字符串"Hello,"的后面("Hello," ++
),最后把結果傳給putStrLn。
就這樣,一個包含了之前所說的讀取和打印操作的新的操作就這樣創建完畢并存到了main這個返回值為IO()
的標識符中。這樣Haskell系統就能夠識別并運行它了。
Haskell中,字符串即是字符的列表形式,所以你可以對它使用任何的
列表函數或是操作符。以下是一個完整的標準操作符列表和它們對應的優先級:
接下來編譯和運行這個程序:
debian:/home/jdtang/haskell_tutorial/code# ghc -o hello_you --make listing2.hs
debian:/home/jdtang/haskell_tutorial/code# ./hello_you Jonathan
Hello, Jonathan
-o 選項指定了你想編譯出來的可執行文件的路徑,然后你需要指定Haskell源代碼的路徑。