來到隔壁的建築,我發現這裡幾乎跟剛才那棟非常類似,房間的格局、配置、擺設。但不同的是,這裡的每一間房間,都有一面鏡子。
我們可以發現,Semigroup 的 <>
跟 Monoid 的 mappend
型別一樣。事實上,這兩個是同樣的東西。
-- Haskell 語法
class Semigroup a where
(<>) :: a -> a -> a
...
infixr 6 <>
-----------
class Semigroup a => Monoid a where
...
mappend :: a -> a -> a
...
-- 試試看
[1, 2, 3] <> [4, 5, 6] -- => [1, 2, 3, 4, 5, 6]
mappend [1, 2, 3] [4, 5, 6] -- => [1, 2, 3, 4, 5, 6]
在定義裡,我們可以看到在 <>
的底下有寫著 infixr
,表示這是一種右結合的中綴函式。中綴函式,顧名思義,就是寫在中間的。例如我們常用的 +
,-
,*
,/
,都算是中綴函式,像是這樣 1 + 2
。
而與中綴函式不同的,則是前綴函式,用法則是放在前面的,例如 add 1 2
。在大多數的程式語言的慣例裡,用英文字母定義的,大多是前綴函式。而用符號定義的接受兩個參數的函式,也就是二元函式,大多是中綴的。
在這個例子裡,mappand
及 <>
的功用一模一樣,只是前綴跟中綴的區別而己。
而右結合,代表的是如果有 Sum 1 <> Sum 2 <> Sum 3
的程式碼,那麼在沒有括號的情況下,會先加右邊兩個,也就是 Sum 2
與 Sum 3
,最後再與左邊的 Sum 1
相加。
而定義裡中間那個 6
,則是這個中綴函式的與其它函式一起使用時的優先度 (priority),數字愈高愈早進行處理。例如乘法 *
的優先度是 7,而加法 +
的優先度是 6。
在 Haskell 中,你可以把一個中綴函式當做前綴使用。方法是在它的兩側加上圓括號 ()
:
-- Haskell 語法
(<>) (Sum 1) (Sum 2) -- => Sum {getSum = 3}
而當然也可以把二元的前綴函式當做中綴使用,用法則是在前綴函式的兩側加上 backtick:```
-- Haskell 語法
Product 2 `mappend` Product 3 -- => Product {getProduct = 6}
而在我們確定了 Monoid 的法則之後,我們來思考一下有哪些東西可以算是 Monoid:
字串是一種 Monoid。根據 Semigroup 的法則把兩個字串相接在一起的二元運算是 ++
,而字串的單位元素,那個讓任何字串加上它,都還是本身的字串,就是…空字串 ""
。
""
-- Haskell 語法
"foo" ++ "" == "" ++ "foo" == "foo"
串列也是一種 Monoid。把兩個字串相接在一起的二元運算是 ++
。單位元素是空陣列:[]
-- Haskell 語法
[1, 2, 3] ++ [] == [] ++ [1, 2, 3] == [1, 2, 3]
是的。整數加法的單位元素是 Sum 0
是的。整數乘法的單位元素是 Product 1
喔嗯。不好笑。
-- Haskell 語法
import Data.Monoid -- Data.Monoid 裡也有 Sum 及 Product
-- 在變數或值的後面加上 :: 來手動指定型別
mempty :: Sum Integer -- => Sum {getSum = 0}
mempty :: Product Integer -- => Product {getProduct = 1}
mempty :: [a] -- => []
mempty :: String -- => ""
用來合併兩個值成為一個同型別值的二元函式,在所有的程式碼裡都非常常見。
就算在使用迴圈的指令式思考中,還是常常需要一個用來把結果放在一起的累加器。而我們選擇用什麼當累加器,其實就預告了這個迴圈最後一步是什麼。例如若選擇 0
當累加器,那麼這個迴圈,或迭代,或遞迴的最後一步,非常可能是個加法。而若是個空串列,那麼就會是把東西放到串列裡(append
、push
之類的)。
當意識到合併這個行為也可以抽象這件事之後,我們可以寫出這樣的程式碼:
-- 只在型別宣告標記這是個整數乘法,進行合併
foldr mappend mempty ([1, 3, 5] :: [Product Int]) -- => Product {getProduct = 15}
-- 型別為整數加法,進行合併
foldr mappend mempty ([1, 3, 5] :: [Sum Int]) -- => Sum {getSum = 9}
-- 字串的合併
foldr mappend mempty (["Hello", "World", "Haskell", "Rocks"]) -- => "HelloWorldHaskellRocks"
而我們之後會看到其它的 typeclass 裡,也會使用到類似的,合併兩個同型別的東西成為一個的概念。
這個字看起來不太像一般的英文。有可能是從數學上的廣群 groupoid 這個字變換過來的。不過在中文翻譯裡,有人叫它四分之三群 (比半群再多符合一條),亞群,或么半群。
[to be continue]