十分鐘學會 Haskell

1 概要

Haskell 是函數式(一切通過函數調用來完成)、靜態、隱式類型(類型由編譯器檢測,類型聲明不是必需的)、惰性(除非必要,否則什麼也不做)的語言。其最大衆化的近親大概是 ML 族語言(不過不是惰性的)。

最流行(common)的 Haskell 編譯器是 GHC下載地址。GHC 在 GNU/LinuxFreeBSDMacOSWindows 以及 Solaris 平臺上都有可供使用的二進制包。安裝 GHC,即獲得 ghc 和 ghci。前者用於將 Haskell 程序庫或應用程序編譯成二進制碼。後者爲解釋器,可在編寫 Haskell 代碼後立即得到反饋.

2 表達式

大部份數學表達式都可以輸入 ghci 直接解答。Prelude> 是 GHCi 默認提示符。

Prelude> 
3 * 5
 15
Prelude> 
4 ^ 2 - 1
 15
Prelude> 
(1 - 5)^(3 * 2 - 4)
 16
字符串需要雙引號引用,以 
++
 連接。 Prelude> 
"Hello"
 "Hello"
Prelude> 
"Hello" ++ ", Haskell"
 "Hello, Haskell"

調用函數時,參數緊接函數即可,其間無須添加括號。

Prelude> 
succ 5
 6
Prelude> 
truncate 6.59
 6
Prelude> 
round 6.59
 7
Prelude> 
sqrt 2
 1.4142135623730951
Prelude> 
not (5 < 3)
 True
Prelude> 
gcd 21 14
 7

3 控制檯

調用 I/O actions 進行控制檯輸入和輸出。如:

Prelude> 
putStrLn "Hello, Haskell"
 Hello, Haskell
Prelude> 
putStr "No newline"
 No newlinePrelude> 
print (5 + 4)
 9
Prelude> 
print (1 < 2)
 True
putStr
 和 
putStrLn
 輸出字符串到終端。
print
 輸出任意類型的值。(如果 
print
 作用於字符串,輸出將用引號引用。) 複雜的 I/O acttions 需要 
do
 語句塊,以分號間隔。 Prelude> 
do { putStr "2 + 2 = " ; print (2 + 2) }
 2 + 2 = 4
Prelude> 
do { putStrLn "ABCDE" ; putStrLn "12345" }
 ABCDE
 12345
通過 
getLine
(返回字符串)或 
readLn
(返回任意你需要的類型)獲得輸入。用
 <-
 符號給 I/O action 的結果命名。 Prelude> 
do { n <- readLn ; print (n^2) }
 4
 16

(4 是輸入。16 是結果。)

do
 語句塊的另一種方式,以縮進取代花括號和分號。雖然在 ghci 中未能獲得完美支持,但是可以把它們塞進源文件(如 Test.hs)裏然後編譯:
main = do putStrLn "What is 2 + 2?"
          x <- readLn
          if x == 4
              then putStrLn "You're right!"
              else putStrLn "You're wrong!"

運行 ghc --make Test.hs,得到 Test(Windows 上是 Test.exe)。順便接觸了 if 語句。

do 之後首個非空白字符,如上例 
putStrLn
 的 p,是特別的。每新起一行,行首與之對齊,即視爲同一 do 塊之新句;縮進較之多則繼續前句;較之少則結束此 do 塊。是爲頁面佈局(layout),Haskell 以之迴避語句結束標記和花括號。(故then 和 else 子句務必縮進,否則將脫離 if 語句,導致錯誤。)

(注意:切勿使用製表符。從技術上講,八格製表符可以正常工作,但不是個好主意。也不要使用非等寬字體——顯然,有些人在編程的時候會犯此糊塗!)

4 類型

到目前爲止,我們一直沒有提到過類型聲明。那是因爲 Haskell 暗中推斷,不必聲明之。如果非要聲明類型,可用 
::
 符號明確指出,如: Prelude> 
5 :: Int
 5
Prelude> 
5 :: Double
 5.0

類型 types(以及類型類 type classes,稍後提及)總是以大寫開頭。變量(variables)總是以小寫開頭。這是語言規則,而不只是命名習慣

你也可以讓 ghci 告訴你選擇的內容的類型,這種方法很有用,因爲類型聲明並不是必需的。

Prelude> :t 
True
 
True :: Bool
 Prelude> :t 
'X'
 
'X' :: Char
 Prelude> :t 
"Hello, Haskell"
 
"Hello, Haskell" ::[Char]
 (在這個例子中,
[Char]
 是 
String
 的另外一種表達方式。參見後面的 section on lists 。)

有關數字的例子則更加有趣:

Prelude> :t 
42
 
42 :: (Num t) => t
 Prelude> :t 
42.0
 
42.0 :: (Fractional t) => t
 Prelude> :t 
gcd 15 20
 
gcd 15 20:: (Integral t) => t

這些類型用到了 "類型類(type classes)" 含義如下:

  • 42
     可作爲任意數字(numeric)類型。(這就是爲什麼我既可以把
    5
    聲明爲
    Int
    類型,也可以聲明爲
    Double
    類型的原因。)
  • 42.0
     可作爲任意分數(fractional)類型,但不能是整數(integral)類型。
  • gcd 15 20
     (此爲函數調用) 可作爲任意整數(integral)類型,但不能是分數類型。

在Haskell "Prelude"(你不需要import任何東西就能使用的那部分庫)中有五種數字(numeric)類型:

  • Int
     是一個至少30位(bit)精度的整數。
  • Integer
     是一個無限精度的整數。
  • Float
     是一個單精度浮點數。
  • Double
     是一個雙精度浮點數。
  • Rational
     是一個沒有舍入誤差的分數/小數類型。
上面5個都是
Num
類型的實例(instances)。其中前兩個是
Integral
類型的實例,後面三種是
Fractional
類型的實例

總的一塊來看一下,

Prelude> 
gcd 42 35 :: Int
 7
Prelude> 
gcd 42 35 :: Double
 <interactive>:1:0:
     No instance for (Integral Double)
最後值得一提的類型是
()
,念做"unit"。 它只有一個取值,也寫作
()
並念做"unit"。 Prelude> 
()
 
()
 Prelude> :t 
()
() :: ()
 你可以把它看作類似C語言中的void關鍵字。在一個I/O動作中,如果你不想返回任何東西,你可以返回
()

5 有結構的數據

基本數據類型可以很容易的通過兩種方式組合在一起:通過 [方括號] 組合的列表(lists),和通過 (圓括號) 組合的元組(tuples)

列表可以用來儲存多個相同類型的值:

Prelude> 
[123]
 [1,2,3]
Prelude> 
[1 .. 5]
 [1,2,3,4,5]
Prelude> 
[13 .. 10]
 [1,3,5,7,9]
Prelude> 
[True, False, True]
 [True,False,True]

Haskell 中的字符串(String)其實只不過是字符(Character)類型的 List:

Prelude> 
['H', 'e', 'l', 'l', 'o']
 "Hello"
冒號 
:
 運算符用來把一個項(item)添加到列表的開始處。(相當於LISP語言中的cons函數) Prelude> 
'C' :['H', 'e', 'l', 'l', 'o']
 "CHello"

元組則和列表不同,它用來儲存固定個數,但類型不同的值。【譯者注:列表是類型相同,但個數不固定,甚至還可以是無限個數】

Prelude> 
(1, True)
 (1,True)
Prelude> 
zip [1 .. 5] ['a' .. 'e']
 [(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e')]
上面這個例子用到了 
zip
 函數,它可以把兩個列表組合成一個元組的列表。

類型一般都符合你的期望:

Prelude> :t 
['a' .. 'c']
 
['a' .. 'c'] :: [Char]
 Prelude> :t 
[('x', True)('y', False)]
 
[('x', True)('y', False)] :: [(CharBool)]

列表在Haskell中被大量使用。有些函數能夠很好地對列表進行運算:

Prelude> 
[1 .. 5]
 
[1,2,3,4,5]
 Prelude> 
map (+ 2) [1 .. 5]
 
[3,4,5,6,7]
 Prelude> 
filter (> 2) [1 .. 5]
 
[3,4,5]

有兩個作用於雙元素元組的優美函數:

Prelude> 
fst (12)
 
1
 Prelude> 
snd (12)
 
2
 Prelude> 
map fst [(12)(34)(56)]
 
[1,3,5]

詳見如何使用列表

函數定義

我們已經定義了一個名爲
main
IO動作函數:
main = do putStrLn "What is 2 + 2?"
          x <- readLn
          if x == 4
              then putStrLn "You're right!"
              else putStrLn "You're wrong!"
現在,我們定義一個名爲
factorial
(階乘)的函數對上面的函數做一點補充。我添加了一個模塊頭部,這是一種良好的風格。
module Main where
 
factorial n = if n == 0 then 1 else n * factorial (n - 1)
 
main = do putStrLn "What is 5! ?"
          x <- readLn
          if x == factorial 5
              then putStrLn "You're right!"
              else putStrLn "You're wrong!"

使用命令ghc --make Test.hs重新編譯。並用下面的命令執行

 $ ./Test
 What is 5! ?
 120
 You're right!
這是一個函數。表現得就和內建函數一樣,可以通過
factorial 5
來調用。 現在向ghci詢問函數
factorial
類型.
 $ ghci Test.hs
 << GHCi banner >>
 Ok, modules loaded: Main.
Prelude Main> :t 
factorial
 
factorial :: (Num a) => a -> a
 Function types are written with the argument type, then 
 ->
, then the result type. (This also has the type class 
Num
.) 
factorial
函數可以通過case得以簡化:
factorial 0 = 1
factorial n = n * factorial (n - 1)

7 語法規則

查看語法以瞭解更多有用的語法。

secsToWeeks secs = let perMinute = 60
                       perHour   = 60 * perMinute
                       perDay    = 24 * perHour
                       perWeek   =  7 * perday
                   in  secs / perWeek
let
表達式定義了臨時名稱。(This is using layout again. You could use {braces}, and separate the names with semicolons, if you prefer.)
classify age = case age of 0 -> "newborn"
                           1 -> "infant"
                           2 -> "toddler"
                           _ -> "senior citizen"
case
表達式實現了多路分支。符號
_
表示"任何東西"。

8 Using libraries

Everything used so far in this tutorial is part of the Prelude, which is the set of Haskell functions that are always there in any program.

The best road from here to becoming a very productive Haskell programmer (aside from practice!) is becoming familiar with other libraries that do the things you need. Documentation on the standard libraries is at http://haskell.org/ghc/docs/latest/html/libraries/. There are modules there with:

module Main where
 
import qualified Data.Map as M
 
errorsPerLine = M.fromList
    [ ("Chris", 472), ("Don", 100), ("Simon", -5) ]
 
main = do putStrLn "Who are you?"
          name <- getLine
          case M.lookup name errorsPerLine of
              Nothing -> putStrLn "I don't know you"
              Just n  -> do putStr "Errors per line: "
                            print n
The 
import
 says to use code from 
Data.Map
 and that it will be prefixed by 
M
. (That's necessary because some of the functions have the same names as functions from the prelude. Most libraries don't need the 
as
 part.)

If you want something that's not in the standard library, try looking at http://hackage.haskell.org/packages/hackage.html or this wiki's applications and libraries page. This is a collection of many different libraries written by a lot of people for Haskell. Once you've got a library, extract it and switch into that directory and do this:

 runhaskell Setup configure
 runhaskell Setup build
 runhaskell Setup install

On a UNIX system, you may need to be root for that last part.

9 Topics that don't fit in 10 minute limit

語言: English 簡體中文

發佈了142 篇原創文章 · 獲贊 337 · 訪問量 224萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章