balbit說模考前應該耍廢,所以我就暫停做題然後熬夜打了這篇。(等等好像哪裡不太對
因為想要趕快進入有趣的地方,所以把入門的東西通通塞進來了,不過對初學者而言這樣應該就足夠玩很久了?
預設是下一篇把拉里拉雜的函式語法講完,以及在Prelude[1]裡面的各種神奇資料結構。
之後就可以開始玩有趣的東西了><
不過依照一年一篇的速度不知道那是什麼時候了Q
環境設定
想在本地編譯Haskell程式請安裝 ghc。內有編譯器(ghc)以及直譯器(ghci)。
型別
Haskell是個強型別語言,以下是幾種常見的型別:
Int
: 有界的整數。一般是\(-2^{31}\) ~ \(2^{31}-1\)。Integer
: 大數Char
: 字元Float
Double
: 浮點數Bool
: 布林值(True
與False
)(a, b)
: tuple,其中a
,b
都是型別。當然也有更長的tuple。()
: 可以視為長度為\(0\)的tuple,也可以視為void
。[a]
: 串列(list)。只支援\(O(k)\) 存取第\(k\)個元素。String
: 字串。事實上是[Char]
的語法糖。a -> b
:從型別a
打到型別b
的函數。
以上應該包含了大部份預設宣告的型別。而在下下一篇會提到,如何在haskell中定義型別。
若想在ghci中看到某個expression的型別
可以打:t Expression
。
直譯器會顯示Expression :: Type
型別類別(Typeclass)
當只打一個數字2
時,Haskell並不知道它代表的是Int
還是Integer
或甚至是Float
。所以Haskell引入型別類別的概念。透過型別類別可以在強型別語言中達到重載(overload)的效果。
以下舉例一些預設宣告的型別類別:
Num
:可以做加法減法乘法的東西Show
:可以變成字串印出的東西Fractional
:可以做除法的東西Integral
:整數Floating
:浮點數Ord
:可以比大小的東西Real
:又是Num
又是Ord
的東西
讀者應該發現了,不同的型別類別有著從屬的關係。
詳細的從屬關係可以看這張haskell官網的圖片:
這些就是所有預設宣告的型別與型別類別。
尚未提及的型別與型別類別應該會在下幾篇提到。
以下來看幾個ghci的範例
1 | Prelude> :t 2 |
Num t => t
表示2
的型別是t
,且t
必須是Num
的一種
而4/2
不但是一個Num
,它還要是一個可以做除法的Fractional
。
當想要直接指定型別時,可以使用::
接在表達式的後面。
max
的型別是a -> a -> a
,表示他是一個接收兩個a
並回傳一個a
的函數(請見 前篇 的Currying),並要求a
要是可以比大小的Ord
。
而當一個型別被要求屬於不只一個型別類別時,會用逗點隔開並用小括號括住。
常見函式介紹
記得去ghci把每個都用用看喔,也可以看看他看他的型別,看這些函式是定義在哪些型別/型別類別上
運算相關
(+)
(-)
(*)
:加、減、乘法。(/)
:小數除法。div
:整數除法。div a b
表示a/b
。 另外haskell有提供語法糖讓a `div` b
表示div a b
mod
:整數除法取餘。 大部分寫作a `mod` b
(^)
:次方運算。(==)
(/=)
:等於與不等於。注意不等於與其他語言的寫法不同(>=)
(>)
(<=)
(<)
:不等號們。(&&)
(||)
:和、或。not
:邏輯非。min
max
:取較小或較大者。succ
pred
:下一個或上一個(定義在Enum
的型別類別。對大部分型別而言是+1
與-1
)。fromIntegral
:把Integral
的東西換成其他型別
串列相關
length
:串列長度。null
:檢查串列是否為空。(!!)
:取第k
個元素(x !! k
)。elem
:判斷a
是否在x
中(a `elem` x
)。(++)
:連接兩個串列(:)
: 將元素接到串列的前端(1:[2,3] = [1,2,3]
)。head
:第一個元素。tail
:第一個以外的元素。last
:最後一個元素。init
:最後一個以外的元素。偷learn you a haskell的圖來說明就是
reverse
:反轉串列。take n x
:取x
的前n
項。drop n x
:丟掉x
的前n
項並回傳剩下的。map f x
:將f
作用在x
的每個元素並回傳結果串列。filter p x
:將x
中所有滿足p
的留下replicate n x
:將x
重複n
次形成的串列
其他
fst
snd
:長度為\(2\)的tuple的第一與第二項。curry
uncurry
:將(a, b) -> c
與a -> b -> c
互相轉換。show
read
:轉成字串與從字串轉回來。lines
words
:對\n
或分割字串。
unlines
unwords
:用\n
或合併字串串列。
id
:恆等函數(id x = x
)。const a
:恆回傳a
的常數函數(const a b = a
)。flip f
:交換二元函數的兩個參數(flip f a b = f b a
)($)
:函數使用。f $ x = f x
[2]。(.)
:函數合成。(f.g) x = f (g x) = f $ g x
折疊
折疊(fold)是函式編成的一個重要特性,因此單獨抓出來講。
所謂折疊只是依序對串列中的每個元素做運算,得到的結果再與下一個元素做運算。
例如foldl (+) 0 [1,2,3,4]
就會執行((((0+1)+2)+3)+4)
得到10
。
其中foldl
的l指的是從左到右。當然還有從右到左的foldr
。若想直接從串列的前兩個或後兩個開始運算而不傳入初始值,可以使用foldl1
與foldr1
。
而若想要過程中的每個結果,可以使用scanl
與scanr
。
讀者可以看看以上每個函數的型別,是否跟想像一樣?
除此之外, Haskell也有預設定義一些fold
,例如
sum = foldl1 (+)
product = foldl1 (*)
and = foldl1 (&&)
or = foldl1 (||)
concat = foldl1 (++)
maximum = foldl1 max
minimum = foldl1 min
更多語言特性
透過上一篇文章提到的語言特性,我們可以作到各式各樣的事情。
部份使用(Partial application)
所謂部份使用,就是將一個多參數函式的某幾個參數固定,類似JS的bind
。
例如如果f x = max 0 x
,那f
就是吃進一個數字,回傳它和0
中較大的那個,也就是將max
部份使用到0
身上。
而由於Haskell的函式是Currified的,因此f = max 0
即可,畢竟max
本質上就是一個a -> (a -> a)
的函式。
讀者可以檢查一下max 0
的型別。它的確是一個Ord a => a -> a
的函式。
而Haskell又提供對於運算子的語法糖,使(+3)
、(2*)
和(`mod` 3 + 2)
可以直接代表部份使用後的運算子,稱為部份運算子(operator section),這些都會是一個Num a => a -> a
的函式。
範圍表達式(Range expression)
haskell對屬於Enum
這個型別類別的型別提供了兩種(包含後面的無限串列共四種)範圍表達式。
[1..10]
代表[1,2,3,4,5,6,7,8,9,10]
,而[1,3..10]
代表[1,3,5,7,9]
。
縱使Float
屬於Enum
,但因為浮點數誤差,還是不建議使用範圍表達式,否則會造成意想不到的結果,例如讀者可以在ghci試試看[0.1, 0.3 ..1.0]
會是什麼
串列生成(List comprehension)
串列生成是一個能夠簡單生出一個串列的語法糖。
例如[i*j | i <- [1..10], j <- [1..10]]
就是concat $ map (\i -> map (\j -> i*j) [1..10]) [1..10]
的語法糖。
而在串列生成時也可以做篩選(filter
),例如
1 | Prelude> [(i, j)|i<-[1..10], j<-[1..10], i + j == 10] |
問題:請寫出所有三邊長小於100的畢式直角三角形。
無限串列
由於Haskell的懶惰求值,我們可以定義無限串列。
當使用範圍表答式時,其實可以不定義範圍的結束在哪裡,也就是寫[1..]
。
但記得不要在ghci打上這個。當在ghci打上這串時,事實上就是對它呼叫show
並把結果印出。對無限串列呼叫show
的結果就是它會塞爆你的螢幕。
而無限串列能讓程式碼簡單許多。若要取得前\(10\)個平方數,就只要take 10 (map (^2) [1..])
就好。是不是比結構式程式設計來的簡單許多呢><
另外,Haskell也有預設定義一些能夠生成無限串列的函式。例如
iterate f x
:[x, f x, f (f x), ...]
repeat x
:[x, x, x, ...]
cycle
:將某個串列不斷重複。
後語
基本上這次就這樣啦。基礎介紹的很大一部份是參考learn you a haskell for great good,畢竟我的Haskell也是在上面學的。如果想深入看看Prelude有哪些東西,也可以上hackage看看。
讀者試一試會發現,上面的函式已經能夠弄出很複雜很炫泡的東西了,例如
問題:什麼是flip const const
?
問題:什麼又是curry fst
?
問題:什麼又是((not.).(null.).filter.(==))
?
從型別應該就可以略知一二了。