原文舍败。
https://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/First_Steps
首先,你需要安裝一個(gè)ghc。在Linux上,它常常被預(yù)先安裝好了或者能夠通過apt-get或者yum命令來輕松搞定。你也可以從官網(wǎng)直接下載它食铐。不過除非你確信你想從源碼去編譯它,否則下載一個(gè)二進(jìn)制包就可以了僧鲁。像安裝其他的軟件包一樣下載和安裝它既可虐呻。這個(gè)教程是在Linux下完成的,但是只要你會使用相關(guān)的命令行操作寞秃,它在Windows或是Mac環(huán)境下也一樣能工作斟叼。
對UNIX或者Windows Emacs用戶來說,這里有一個(gè)很棒的Emacs mode春寿,包括了語法高亮和自動縮進(jìn)的功能朗涩。Windows用戶則能夠直接使用記事本或者其他文本編輯器:Haskell的語法對記事本相當(dāng)友好,盡管你仍要小心處理縮進(jìn)绑改。Eclipse用戶建議使用eclipsefp插件谢床。
現(xiàn)在,是時(shí)候?qū)懩愕牡谝粋€(gè)Haskell程序了厘线。這個(gè)程序?qū)⑼ㄟ^命令行讀入一個(gè)名字然后輸出一個(gè)問候語句识腿。建立一個(gè)以 ".hs” 結(jié)尾的文件并輸入下列代碼。當(dāng)心縮進(jìn)皆的,否則你的程序可能沒法通過編譯覆履。
module Main where
import System.Environment
main :: IO ()
main = do
args <- getArgs
putStrLn ("Hello, " ++ args !! 0)
我們來看下這段代碼。前兩行表示我們講創(chuàng)建一個(gè)名叫Main的模塊费薄,并且導(dǎo)入了System這個(gè)模塊硝全。所有的Haskell程序都會從Main模塊里的一個(gè)叫做main函數(shù)的地方開始運(yùn)行。你可以在這個(gè)模塊中導(dǎo)入其他的模塊楞抡,但是如果沒有了它伟众,編譯器就無法生成可執(zhí)行文件供用戶運(yùn)行。此外Haskell是大小寫的敏感的:模塊名稱需要是大寫開頭的召廷,而函數(shù)聲明則必須是非大寫開頭的凳厢。
main :: IO ()
這行是函數(shù)的類型聲明:它表示main函數(shù)的類型是IO()
,是一個(gè)返回Unit類型()
的IO操作竞慢。一個(gè)Unit類型僅會包含一個(gè)值先紫,而()
,它表示什么也沒有筹煮。類型聲明在Haskell里是可選的:編譯器能夠自動的識別它們遮精,當(dāng)你的聲明和編譯器自動識別發(fā)生沖突的時(shí)候它則會報(bào)錯(cuò)。在這個(gè)教程中败潦,為了更清晰的說明本冲,所有的類型都是顯式聲明的。而你在家里跟著做的時(shí)候劫扒,你可能更愿意選擇忽略檬洞,因?yàn)樵诰帉戇@個(gè)程序的時(shí)候其實(shí)并不太需要去在意它們。
IO類型是Monad類型類的一個(gè)實(shí)例沟饥,Monad是一種抽象的概念添怔,如果滿足以下這兩個(gè)條件,那我們就會說這樣的值是Monad:
- 這個(gè)值包含了一些特定類型的附加信息贤旷;
- 大多數(shù)函數(shù)不需要去關(guān)心這些附加的信息广料。
在這個(gè)例子里,
- 這個(gè)附加的信息就是將被執(zhí)行的IO操作遮晚;
- 而這個(gè)附加信息的值是不存在的性昭,表示成
()
。
IO[String]
和IO()
都同樣屬于IO Monad類型县遣,但它們有著不同的基本類型糜颠。它們作用于(或是傳遞)不同類型的值,[String]
和()
萧求。
那些包含了附加信息的值則被稱作“Monadic”其兴。
Monadic值常被稱作“操作” ,因?yàn)樽钊菀椎乃伎糏O Monad用途的方法就是把它當(dāng)做一系列可能會影響外界世界的動作夸政。這一系列動作會傳遞一些基礎(chǔ)的數(shù)值元旬,然后在這個(gè)過程中每個(gè)動作都會對這些值進(jìn)行影響。
Haskell是一個(gè)函數(shù)式的語言:與給出計(jì)算機(jī)一系列指令從而讓它執(zhí)行不同,你需要給Haskell一系列定義來告訴它每一個(gè)函數(shù)來如何處理匀归。這些定義會將各種動作和函數(shù)組合在一起坑资。而編譯器會識別出將它們組織在一起的執(zhí)行方式。
要寫出這樣一個(gè)定義穆端,你首先需要建立一個(gè)等式袱贮。等式的左邊是一個(gè)名稱,可能還會帶有若干個(gè)與變量綁定的模式(后面會解釋)体啰。右邊的話攒巍,會給出一些由其他定義組合而成的式子,從而告訴計(jì)算機(jī)如何遇到該定義時(shí)如何進(jìn)行計(jì)算荒勇。這些等式就和一般的代數(shù)表達(dá)式一樣:你總是可以在程序中用等式右邊的部分來替代左邊的名字柒莉,并且得到與之前相同的結(jié)果。這種行為被稱作“引用透明”沽翔,而這種性質(zhì)使得Haskell代碼比其它的語言更加易于理解兢孝。
那我們應(yīng)該怎么定義我們的main函數(shù)呢?我們知道它必須是一個(gè)能夠從命令行讀入?yún)?shù)搀擂,然后從打印出一些輸出西潘,最終返回()
(空值)的IO()
操作。
這里有兩種方法創(chuàng)建一個(gè)IO操作:
- 使用return函數(shù)提升一個(gè)普通值進(jìn)入IO Monad哨颂。
- 連接兩個(gè)已經(jīng)存在的IO操作喷市。
因?yàn)槲覀兘酉聛硪鰞杉虑椋晕覀冞x擇第二種方法威恼。我們通過內(nèi)建函數(shù)getArgs讀入命令行參數(shù)并把它們存入一個(gè)字符串列表品姓。而內(nèi)建函數(shù)putStrLn則能夠讀入一個(gè)字符串然后將它輸出到終端。
我們使用一個(gè)do代碼塊來連接這兩個(gè)操作箫措。一個(gè)do代碼塊包括很多行腹备,所有的行按照第一個(gè)非空白字符在do后面排列,并且每行都可能是如下兩種形式之一:
- name <- action1
- action2
第一種形式將action1的結(jié)果和name綁定斤蔓,從而你可以在下一個(gè)操作中使用它植酥。例如,如果有action1的類型是IO[String]
(一個(gè)會返回一個(gè)字符串列表的IO操作弦牡,就和getArgs一樣)友驮,那name就會在接下來的一系列操作里和這個(gè)返回的字符串列表通過綁定操作符>>=
綁定在一起。第二種情況僅僅執(zhí)行這個(gè)action2驾锰,并通過>>
操作符同下一行連結(jié)在一起卸留。綁定操作符在處理不同Monad的情況下有不同的語義:在IO Monad中,它會連續(xù)執(zhí)行所有的操作椭豫,然后對外部世界產(chǎn)生這些操作帶來的副作用耻瑟。由于這個(gè)綁定符號的語義依賴你具體使用的Monad類型旨指,所以你并不能在同一個(gè)do代碼塊里把不同類型的Monad類型的操作糅雜在一起---在這里只有IO Monad是可用的(在同一個(gè)管道中)。
當(dāng)然喳整,這些操作可能自己會調(diào)用其他函數(shù)或是復(fù)雜的表達(dá)式谆构,然后繼續(xù)傳遞它們的計(jì)算結(jié)果(通過調(diào)用return或是其他最終調(diào)用了return的函數(shù))。
在這個(gè)例子里算柳,我們首先取出參數(shù)列表中的第一個(gè)元素(args !! 0)
低淡,然后把它拼接到字符串"Hello,"的后面("Hello," ++
)姓言,最后把結(jié)果傳給putStrLn瞬项。
就這樣,一個(gè)包含了之前所說的讀取和打印操作的新的操作就這樣創(chuàng)建完畢并存到了main這個(gè)返回值為IO()
的標(biāo)識符中何荚。這樣Haskell系統(tǒng)就能夠識別并運(yùn)行它了囱淋。
Haskell中,字符串即是字符的列表形式餐塘,所以你可以對它使用任何的
列表函數(shù)或是操作符妥衣。以下是一個(gè)完整的標(biāo)準(zhǔn)操作符列表和它們對應(yīng)的優(yōu)先級:
接下來編譯和運(yùn)行這個(gè)程序:
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 選項(xiàng)指定了你想編譯出來的可執(zhí)行文件的路徑,然后你需要指定Haskell源代碼的路徑戒傻。