Hello World
一個基本的 Hello World!
Haskell 中的程式可以用一兩行簡潔地表達出來:
main::IO ()
main = putStrLn "Hello, World!"
第一行是可選型別註釋,表示 main
是 IO ()
型別的值,表示 I / O 動作計算()
型別的值(讀取 unit
;空元組不傳達資訊)除了執行某些操作對外界的副作用(這裡,在終端列印一個字串)。main
通常省略此型別註釋,因為它是唯一可能的型別。
將它放入 helloworld.hs
檔案並使用 Haskell 編譯器(如 GHC)編譯它:
ghc helloworld.hs
執行編譯的檔案將導致輸出 Hello, World!
被列印到螢幕:
./helloworld
Hello, World!
或者,runhaskell
或 runghc
使得可以在解釋模式下執行程式而無需編譯它:
runhaskell helloworld.hs
也可以使用互動式 REPL 而不是編譯。它隨大多數 Haskell 環境一起提供,例如 GHC 編譯器附帶的 ghci
:
ghci> putStrLn "Hello World!"
Hello, World!
ghci>
或者,使用 load
(或:l
)從檔案載入指令碼到 ghci:
ghci> :load helloworld
:reload
(或:r
)重新載入 ghci 中的所有內容:
Prelude> :l helloworld.hs
[1 of 1] Compiling Main ( helloworld.hs, interpreted )
<some time later after some edits>
*Main> :r
Ok, modules loaded: Main.
說明:
第一行是型別簽名,宣告瞭 main
的型別:
main::IO ()
IO ()
型別的值描述了可以與外部世界互動的行為。
因為 Haskell 有一個完全成熟的 Hindley-Milner 型別系統允許自動型別推斷,型別簽名在技術上是可選的:如果你只是省略 main::IO ()
,編譯器將能夠通過分析 main
的定義自己推斷出型別。但是,如果不為頂級定義編寫型別簽名,則被認為是不好的樣式。原因包括:
-
Haskell 中的型別簽名是一個非常有用的文件,因為型別系統是如此富有表現力,以至於你通常只需檢視其型別就可以看到函式有什麼好處。可以使用 GHCi 等工具方便地訪問此文件。與普通文件不同,編譯器的型別檢查器將確保它實際匹配函式定義!
-
型別簽名將 bug 保持為本地。如果你在定義中犯了一個錯誤而沒有提供它的型別簽名,編譯器可能不會立即報告錯誤,而是簡單地為它推斷出一個無意義的型別,實際上它可以用來檢測它。然後,你可能會在使用該值時收到一條神祕的錯誤訊息。通過簽名,編譯器非常善於發現錯誤。
第二行是實際工作:
main = putStrLn "Hello, World!"
如果你來自命令式語言,請注意此定義也可以寫成:
main = do {
putStrLn "Hello, World!" ;
return ()
}
或者等價(Haskell 具有基於佈局的解析;但要注意混合製表符和空格不一致會混淆這種機制):
main = do
putStrLn "Hello, World!"
return ()
在一個 do
塊的每一行代表一些一元 (在此,I / O) 計算,使得整個 do
塊表示通過將它們在具體給定的單子的方式結合由這些子步驟的總體操作(I / O 這意味著一個接一個地執行它們。
do
語法本身就是 monad 的語法糖,就像 IO
這裡一樣,return
是一個無操作動作,產生它的引數而不會執行任何副作用或額外的計算,這可能是特定 monad 定義的一部分。
以上與定義 main = putStrLn "Hello, World!"
相同,因為值 putStrLn "Hello, World!"
已經具有 IO ()
型別。作為一個宣告,putStrLn "Hello, World!"
可以看作是一個完整的程式,你只需定義 main
來引用這個程式。
你可以線上檢視 putStrLn
的簽名 :
putStrLn::String -> IO ()
-- thus,
putStrLn (v::String) :: IO ()
putStrLn
是一個函式,它接受一個字串作為引數並輸出一個 I / O 動作(即表示執行時可以執行的程式的值)。執行時始終執行名為 main
的操作,因此我們只需將其定義為等於 putStrLn "Hello, World!"
。