首页 > 技术文章 > Haskell笔记

shanxieng 2020-11-10 00:36 原文

Haskell笔记

总论

Haskell是一种函数式语言,其运行的过程就是给函数传递参数,然后进行执行。

这里的函数指的是数学上的函数,如

\[f(x)=x^2 \]

下面是这个函数对应的haskell代码:

sqr x = x * x

在GHCi环境中,我们将这行代码输入,即完成了该函数的定义。然后我们输入

sqr 5

即可获得25。

haskell的最大的优点在于简洁,易读,如以下为一段C++代码:

int ans=0;
for(int i=1;i<=n;i++)
    ans=ans+i;

而用haskell实现就极为简洁易懂:

sum [] = 0
sum (x:xs) = x + sum xs

再如以下为haskell实现的快排:

qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
               where
               smaller = [y|y<-xs,y<x]
               larger = [y|y<-xs,y>=x]

基础语法

变量和函数

作为函数式语言,haskell中的函数是一等公民(first class citizen),在任何地方都能够定义函数,并将函数作为一个参数和返回值。

作为函数式语言,haskell中的变量一经赋值即不能改变。

用于条件判断的语法

if&else

haskell中if和else必须同时出现。if后要加then。

abs :: (Num a) => a -> a
abs x = if x > 0 then x
        else -x

pattern matching

通过写出一个函数在不同情况下的值来定义一个函数。

sum :: (Num a) => [a] -> a
sum [] = 0
sum (x:xs) = x + sum xs

fact :: (Integeral a) => a -> a
fact 0 = 1
fact n = n * fact (n - 1)

guards

类似于switch。

sgn :: (Num a) => a -> Int
sgn x | x > 0     = 1
      | x < 0     = -1
      | otherwise = 0

case_of

就是switch。

sum ys = case ys of
         []     -> 0
         (x:xs) -> x + sum xs

let_in & where

有时,我们需要一些临时变量和函数,这时就要用let_in和where。

merge_sort :: (Ord a) => [a] -> [a]
merge_sort [x] = [x]
merge_sort xs = merge (merge_sort l) (merge_sort r) -- 假装有merge
    where
        (l,r) = splitAt (length xs `div` 2) xs

merge_sort [x] = [x]
merge_sort xs = let (l,r) = splitAt (length xs `div` 2) xs in
                merge (merge_sort l) (merge_sort r)

Haskell 类型

在Haskell中,每个表达式,自变量,函数都有自己的类型。Haskell语言中用::表示类型,如:

True :: Bool
not False :: Bool
not :: Bool -> Bool

基本数据类型

Int 表示整数,范围与long long同。

Integer 表示的整数无取值范围。

Int & Integer 统称为 Integral

Float 单精度浮点型

Double 双精度浮点型

Float & Double 统称为Floating

Char 表示字符

Bool 用于判断

类型类

可以理解为一个类型的集合。在一个类型类(类族)中的类型都必须支持某一个或某些函数。

Eq 所有能判断相等的类型

Ord 所有有序的类型

Show 所有能输出的类型

Read 所有能输入的类型

Num 所有数字

Integral 所有整数

Floating 所有浮点数

定义类型和类族

最简单的定义是type定义,作用类似于typedef。最典型的例子:

type String = [Char]

如果要定义新的数据类型,要用到data,如:

data Bool = True | False

这表示Bool类型是由TrueFalse两种情况构成的。TrueFalse称为constructor,类似于函数,这两个constructor都是没有参数的,下面是两个有参数的例子:

data Maybe a = Nothing | Just a
data Tree a  = Leaf a | Node (Leaf a) (Leaf a) a

其中,Tree a的定义中运用了它本身,即为递归数据类型。而type定义时不能构造递归数据类型。

data的功能强大,但是会影响效率,而type功能一般,但效率较好,于是有一个折中的解决方案:newtype。

newtype使用时与前两者相似,不同点在于,编译时其被视为data,而运行时其与type较为接近,这样既保留了data的一部分功能,又兼有type的效率。

定义类族:

定义类族时只要定义一些所有类型都要满足的函数即可,如:

class Myclass a where
    foo1 :: a -> a
    foo2 :: a -> [a]
    ...

如果要使一种数据类型被包含在该类族中,则要给出这些函数的实现,如:

instance Myclass Int where
    foo1 = ...
    foo2 = ...
    ...

类族中的类型也可以是一些含参数的类型,如:

class Myclass f where
    foo1 :: f a -> f b
    foo2 :: Num a => f a -> b
    ...
    
instance Myclass Maybe where
    foo1 = ...
    foo2 = ...
    ...

定义类族时,可以限定该类族中的类型必须属于某一个类族:

class Myclass1 a => Myclass2 b where
    ...
    
class (Myclass1 a, Myclass3 f) => Myclass4 f a where
    ...

此外,定义的类型可以自动加载某些类族,如:

data Tree a = Leaf a | Node (Tree a) (Tree a) a
    deriving (Show,Eq)

这样,Tree a类型就属于Show和Eq类族了。

列表和元组

List为列表,写做[a],如,[1,1,2,3,5]、[1..100]类型都是[Int],['a','b','c']类型是[Char]/这个也写作"abc"/,[True,True,False]类型是[Bool]。一个列表内的元素类型必须相同。

列表还可以嵌套,如[[1,1,2,3,5],[],[1..100]]类型就是[[Int]]。这里列表中的元素类型是[Int],而[]类型为[a],其中的a可以是任何元素,因此这里的写法是没有问题的。

同时,两个列表可以进行连接,如['a','b'] ++ ['c','d','e'] => ['a','b','c','d','e']

还可以将一个元素加到列表的开头,如a:[b,c,...] => [a,b,c,...]

其实列表定义就是a:b:c:...:[],即列表的构造方式。

haskell中的列表是一个链表,每一个元素都会记三个东西:构造方法(这里是:)、值或值的指针、下一个元素的地址。

haskell中的另一个特色就是list comprehension。考虑集合的记法:\(\{x|x\in A,f(x) = True\}\),其中f(x)是一个Bool型的函数,这个记法可以自然地扩展到列表上:[x | x <- A, f x]

例如:

[x | x <- [1..], even x]
[x | x <- [1..], odd  x]
-- 分别构造了偶数和奇数列表
[x | x <- [1..n], n `mod` x == 0]
-- n的所有因子
primes = filterPrime [2..]
    where filterPrime (p:xs) =
        p : filterPrime [x | x <- xs, x `mod` p /=0]
-- O(n^2)的素数筛法

实际上,list comprehension是一个syntax sugar,实际上可以转化为一个do表达式,而这个do表达式实际上是list Monad的一个syntax sugar,其存在的意义并不是引入新的东西,而是让程序更加简洁,提高易读性。

Tuple为元组,写作(a,b,..),如,(1,2)类型是(Int,Int),而(True,'a')类型是(Bool,Char)。这里元组中的元素类型可以是不一样的,元组中的元素可以是元组和列表,同样,列表中的元素也可以是元组。

函数类型

Haskell中,函数也被认为是一个值,这个值也有自己的类型,比如下面函数的类型:

sqrt :: Floating -> Floating  --接受一个Floating,返回一个Floating
length :: [a] -> Int          --接受一个List,返回一个Int
take :: Int -> [a] -> [a]     --接受一个Int和一个List,返回和前面的List类型相同的List

注意最后一个函数可以理解为接受一个Int,返回一个[a]->[a]的函数。这里函数作为返回值返回。

比如我们可以这样定义:take3 = take 3,这样,take3就是一个[a]->[a]的函数。

与其他的值一样,函数也可以放在列表和元组中,放在列表中的函数类型也必须相同,如。

[tail,take 3,drop 5]     --正确,列表中的元素类型都是[a]->[a]
[head,sum,reverse]       --错误,head、sum类型是[a]->Int,而reverse类型是[a]->[a]

如果一个函数有两个参数,那么就能将其看做一个运算符,例如:

div x y     --这是函数
x `div` y   --这是运算符

同样,运算符也能转化为函数,如

a + b       --这是运算符
(+) a b     --这是函数

高阶函数

高阶函数是指以函数为参数或以函数为返回值的函数,如函数map f xs表示将f作用于序列xs的各个元素上;又如下面的函数:

fix :: ((a,b) -> c) -> a -> b -> c
fix f x y = f (x,y)

new_fst = fix fst

可以将一个作用于Tuple上的函数变成一个作用于两个变量上的函数,而fix f返回的是一个函数。

有两个重要的高阶函数:foldr , foldl

要求一个序列之和,可以这样:

sum :: (Num a) => [a] -> a
sum [] = 0
sum (x:xs) = x + sum xs

这样就得到了一个sum函数,我们可以用foldr进行改写:

sum = foldr (+) 0

foldl与foldr恰好相反,foldr为右结合,foldl为左结合。使用foldl可以达到与for loop类似的效果。

foldl和foldr作用的对象必须属于Foldable类。

foldl :: (Foldable t) => (a -> b -> a) -> a -> t b -> a
foldr :: (Foldable t) => (a -> b -> b) -> b -> t a -> b
-- 下面是foldl和foldr作用到list上的实例

-- foldl :: (Foldable t) => (a -> b -> a) -> a -> [b] -> a
foldl f v [] = v
foldl f v (x:xs) = foldl f (f v x) xs

-- foldr :: (Foldable t) => (a -> b -> b) -> b -> [a] -> b
foldr f v [] = v
foldr f v (x:xs) = f x (foldr f v xs)

其他

有理数,即分数,由两个Integral组成,叫做Ratio Integer,表示法如

\[\mathbb{a\%b}\ \ \ \ =>\ \ \ \ \frac{a}{b} \]

使用时要import Data.Ratio

重要内置函数

head x             --取序列x的第一个元素                
tail x             --去除x中第一个元素并返回剩余序列        
take x y           --取序列y的前x项                        
drop x y           --去除序列y的前x项并返回剩余序列        
(!!) x y           --取序列x中的第y个元素        
length x           --返回序列x的长度                
sum x              --返回序列x中所有元素之和                  
product x          --返回序列x中所有元素之积
reverse x          --翻转序列
(++) x y           --将x,y两个序列连接成一个
fst (x,y)          --返回x
snd (x,y)          --返回y
zip x y            --将两个序列x,y转为Tuple的序列
concat x           --将一个序列的序列中的元素合并为一个序列
map f x            --对序列x中的每一个元素应用函数f,返回结果序列
filter f x         --取出序列x中使f为True的元素组成序列

fromIntegral  x    --Integral x -> Floating x
float2Double x     --Float x -> Double x
double2Float x     --Double x -> Float x
ceilint x          --下取整 Integral -> Floating
floor x            --上取整
round x            --四舍五入
truncate x         --去掉小数部分

(/=) x y           --不等于,==、>、>=、<、<=均不变
(^) x y            --x^y,+、-、*均不变。注意这里x和y是整数,实数时写作x**y
div x y            --x/y,这里的xy均为整数,直接写x/y时x、y必须是实数
mod x y            --x%y

-- import Data.Char
chr x              --将数字转为字符
ord x              --将字符转为数字
isAscii x          --判断一个字符是否是ASCII字符
isAlpha x          --判断一个字符是否是字母
isLatin x          --判断一个字符是否属于Latin1
isLower x          --判断一个字符是否是小写字母
isUpper x          --判断一个字符是否是大写字母
isDigit x          --判断一个字符是否是数字
isOctDigit x       --判断一个字符是否是八进制数字
isHexDigit x       --判断一个字符是否是十六进制数字
isAlphaNum x       --isAlpha || isDigit
isControl x        --判断一个字符是否是'\n'、'\t'等
isSpace x          --判断一个字符是否是空白字符
isPrint x          --判断一个字符能否打印
toUpper x          --小写转大写
toLower x          --大写转小写
intToDigit x       --一位数字转为一个字符
digitToInt x       --一个字符转为一位数字

Functor , Applicative , Monad

Functor

考虑要用映射,那么对于序列有:

map (+1) [1,3,4] = [2,4,5]

现在我们考虑扩展其范围,不仅限于map,于是就有了Functor:

class Functor f where
    fmap :: (a -> b) -> f a -> f b
	
instance Functor [] where
    fmap = map

data Tree a = Leaf a | Node (Tree a) (Tree a)

instance Functor Tree where
    -- fmap :: (a -> b) -> Tree a -> Tree b
    fmap f t = case t of
               Leaf x -> Leaf (f x)
               Node l r -> Node (fmap f l) (fmap f r)
               
data Maybe a = Nothing | Just a

instance Functor Maybe where
    -- fmap (a -> b) -> Maybe a -> Maybe b
    fmap f Nothing = Nothing
    fmap f (Just x) = Just (f x)
			   
-- Functor Laws: 
-- fmap id x = x
-- fmap (f.g) = fmap f . fmap g
-- notes:
-- f <$> x = fmap f x

Applicative

Applicative是Functor的扩展,用于处理一些要映射多个函数或者映射后仍为函数的情况。

考虑有下面这些函数:

fmap0 :: a -> f a
fmap1 :: (a -> b) -> f a -> f b
fmap2 :: (a -> b -> c) -> f a -> f b -> f c
fmap3 :: (a -> b -> c -> d) -> f a -> f b -> f c

要这样一个一个定义会非常麻烦,那么我们引入了Applicative:

class (Functor f) => Applicative f where
    pure :: a -> f a
    (<*>) :: f (a -> b) -> f a -> f b

那么上面原来的四个函数就能写成:

fmap0 = pure
fmap1 f a = pure f <*> a
fmap2 f a b = pure f <*> a <*> b
fmap3 f a b c = pure f <*> a <*> b <*> c

下面是几个Example:

instance Applicative [] where
    pure x = [x]
    fs <*> xs = [f x | f <- fs , x <- xs]
	
instance Applicative Tree where
    -- pure :: a -> Tree a
    pure x = Leaf x
	
    -- (<*>) :: Tree (a -> b) -> Tree a -> Tree b
    ft <*> xt = case ft of
                Leaf f -> fmap f xt
                Node l r -> Node (l <*> xt) (r <*> xt)
                
instance Applicative Maybe where
    -- pure :: a -> Maybe a
    pure x = Just a
    
    -- (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
    fm <*> xm = case fm of
                Nothing -> Nothing
                (Just f) -> fmap f xm

-- Applicative Laws:
-- pure id <*> x = x
-- pure g <*> pure x = pure (g x)
-- x <*> pure y = pure (\g -> g y) <*> x
-- x <*> (y <*> z) = (pure (.) <*> x <*> y) <*> z

Monad

Monad是Applicative的加强版。

Functor解决的是一个将pure的函数作用到一个effective的数据上,并得到一个effective的结果,而Applicative是将Functor进行了一般化。

如果函数本身就是由pure到一个effective的函数,那么就要用Monad:

class Monad m where
    return :: a -> m a
    (>>=) :: m a -> (a -> m b) -> mb
    (>>) :: m a -> m b -> m b
    x >> y = x >>= \_ -> y
    fail :: String -> m a
    fail msg = error msg

instance Monad [] where
    return = pure
    xs >>= f = concat (fmap f xs)
    
instance Monad Tree where
    -- return :: a -> Tree a
    return = pure
    
    -- (>>=) :: Tree a -> (a -> Tree b) -> Tree b
    xs >>= f = case xs of
               Leaf x = f x
               Node l r = Node (l >>= f) (r >>= f)

instance Monad Maybe where
    -- return :: a -> Maybe a
    return = pure
    
    -- (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
    xm >>= f = case xm of
               Nothing -> Nothing
               Just x -> f x

-- Monad Laws:
-- return x >>= f = f x
-- mx >>= return = mx
-- (mx >>= f) >>= g = mx >>= (\x -> (f x >>= g))

运用Monad,haskell构建了一个pure的函数和effective的函数的桥梁,使两者能够在一定的限制下进行交换数据。

另外,有了Monad,就可以用do结构。实际上,do结构就是一个Monad的语法糖。

m1 >>= \x1 ->
m2 >>= \x2 ->
.
.
.
mn >>= \xn ->
f x1 x2 ... xn

do
    x1 <- m1
    x2 <- m2
    .
    .
    .
    xn <- mn
    f x1 x2 ... xn

有了Monad,我们还可以使用一些能够作用于所有的Monad上的Generic Function:

-- import Control.Monad
fmapM :: (Monad m) => (a -> m b) -> [a] -> m [b]
fmapM f [] = return []
fmapM f (x:xs) = do
    y <- f x
    ys <- fmapM f xs
    return (y:ys)
    
filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM p [] = return []
filterM p (x:xs) = do
    b <- p x
    ys <- filterM p xs
    return (if b then x:ys else ys)
    
join :: (Monad m) m (m a) -> m a
join xss = do
    xs <- xss
    x <- xs
    return x

Alternative

可以进行选择,在Control.Applicative中定义:

class Applicative f => Alternative f where
    empty :: f a
    (<|>) :: f a -> f a -> f a
    -- 3 laws:
    -- x <|> empty = x
    -- empty <|> x = x
    -- x <|> (y <|> z) = (x <|> y) <|> z
    some  :: f a -> f [a]
    some x = pure (:) <*> x <*> many x
    many  :: f a -> f [a]
    many x = some x <|> pure []
    -- 这显然是死循环。。。
    -- 至于具体为什么可以这样,下面就解释了

定义Maybe:

instance Alternative Maybe where
    empty = Nothing
    Nothing <|> x = x
    x <|> _ = x

不难想到,就像计算(||)时一样,haskell计算(<|>)时是惰性的,即如果计算出左边不是empty,那么就直接返回左边,不必再计算右边。这样就能解释前面的somemany了。

some表示至少要有一个x,而many则表示不一定有多少个x。但是在对x进行作用时,可能对x的值进行了改变,这显然是违背了函数式语言的原则,但是有时有的情况使得我们不得不如此,也正因此,haskell引入了Applicative和Monad,来将这一类的变量和纯的函数式分隔开,只能通过个别的函数从这一类函数中读入或输出。

下面是一个例子,也是一类极为重要的Monad。

State Monad

定义某个数据类型为State。

定义State Transformer为从一个State到另一个State的函数,简记为ST。

定义ST a = State -> (a, State)

type State = Int
newtype ST a = S (State -> (a, State))

app :: ST a -> State -> (a,State)
app (S st) x = st x 

instance到Functor,Applicative,Monad上。

instance Functor ST where
    -- fmap :: (a -> b) -> ST a -> ST b
    fmap f st = S (\s -> let (x, s') = app st s in (f x, s'))
    
instance Applicative ST where
    -- pure :: a -> ST a
    pure x = S (\s -> (x, s))
    
    -- (<*>) :: ST (a -> b) -> ST a -> ST b
    stf <*> stx = S (\s -> let (f, s') = app stf s
                               (x, s'') = app stx s' in
                               (f x, s''))
                               
instance Monoad ST where
    -- return :: a -> ST a
    return = pure
    
    -- (>>=) :: ST a -> (a -> ST b) -> ST b
    stx >>= f = S(\s -> let (x, s') = app stx s in app (f x) s')

这样,我们就完成了所有基本的定义。

而我们知道,haskell中的IO类型为IO a :: World -> (a world),就是一个ST Monad。

下面是另一个例子:

对树进行重新标注,即在树中遍历,第一个遇到的叶子标为一号,第二个标为二号……

我们可以很容易地写出如下程序:

data Tree a = Leaf a | Node (Tree a) (Tree a)
    deriving Show
    
relabel :: Tree a -> Int -> (Tree Int, Int)
relabel (Leaf x) n = (Leaf n, n + 1)
relabel (Node l r) n = (Node ll rr, n3)
    where
        (ll, n2) = relable l n
        (rr, n3) = relable r n2

而我们注意到,relabel的类型就是Tree a -> ST (Tree Int),所以我们可以用ST Monad进行改写:

fresh :: ST Int
fresh = (\x -> (x, x + 1))

alabel :: Tree a -> ST (Tree Int)
alabel (Leaf x) = pure Leaf <*> fresh
alabel (Node l r) = pure Node <*> alabel l <*> alabel r

relabel :: Tree a -> Tree Int
relabel t = fst (app (alabel t) 0)

上面的用到了Applicative,而我们还能用Monad:

alabel :: Tree a -> ST (Tree Int)
alabel (Leaf x) = do
    n <- fresh
    return (Leaf n)
alabel (Node l r) = do
    ll <- alabel l
    rr <- alabel r
    return (Node ll rr)

(是不是越来越像命令式语言了\xyx)

例子2: Monadic Parser

Parser即为句法分析器,用于编译过程中将各个部分分离出来。我们可以用ST Monad实现一个Parse:

newtype Parser a = P (String -> [(a, String)])

返回值用List是为了体现出不合法的情况(返回值为空)。

parse :: Parser a -> String -> [(a,String)]
parse (P p) inp = p inp

item :: Parser Char
item = P (\inp -> case inp of
                  [] -> []
                  (ch:cs) -> [(ch,cs)])

item为从一个字符串中取出第一个字符的Parser。

下面定义Functor,Applicative,Monad:

instance Functor Parser where
    -- fmap :: (a -> b) -> Parser a -> Parser b
    fmap f pa = P (\inp -> case parse pa inp in
                           [] -> []
                           [(x, inp')] -> [(f x, inp')])
                           
instance Applicative Parser where
    -- pure :: a -> Parser a
    pure x = P (\inp -> [(x, inp)])
    
    -- (<*>) :: Parser (a -> b) -> Parser a -> Parser b
    pf <*> pa = P (\inp -> case parse pf inp in
                           [] -> []
                           [(f, inp')] -> parse (fmap f pa) inp')
            
instance Monad Parser where
    -- return :: a -> Parser a
    return = pure
    
    -- (>>=) :: Parser a -> (a -> Parser b) -> Parser b
    pa >>= f = P (\inp -> case parse pa inp of
                          [] -> []
                          [(x, inp')] -> parse (f x) inp')

为了让Parser能发挥更大的功能,我们将它搞到Alternative上:

instance Alternative Parser where
    -- empty :: Parser a
    empty = return []
    
    -- (<|>) :: Parser a -> Parser a -> Parser a
    p <|> q = P (\inp -> case parse p inp in
                         [] -> parse q inp
                         [(x, inp')] -> [(x, inp')])

定义一个函数,判断一个字符串的首个字符是否符合一定条件:

sat :: (Char -> Bool) -> Parser Char
sat p = do
    ch <- item
    if p ch then
        return ch
    else
        empty
        
digit = sat isDigit
lower = sat isLower
upper = sat isUpper
alpha = sat isAlpha
alphanum = sat isAlphaNum
char x = sat (== x)

string :: String -> Parser String
string [] = empty
string (x:xs) = do
    char x
    string xs
    return (x:xs)
-- parse (string "abc") "abcdef" = [("abc","def")]
-- parse (string "abc") "abdef"  = []

space :: Parser ()
space = do
    many (sat isSpace)
    return ()
    
nat :: Parser Int
nat = do
    xs <- some digit
    return (read xs)

ident :: Parser String
ident = do
    x <- lower
    xs <- many alphanum
    return (x:xs)
    
int :: Parser Int
int = do
    char '-'
    n <- nat
    return (-n)
  <|> nat
  
token :: Parser a -> Parser a
token p = do
    space
    x <- p
    space
    return x
    
identifier = token ident
natural = token nat
integer = token int
symbol xs = token (string xs)

nats :: Parser [Int]
nats = do
    symbol "["
    x <- natural
    xs <- many (do symbol ","; natural)
    symbol "]"
    return (x:xs)

应用:表达式求值

简化问题:只有加,乘,括号的表达式进行求值:

expr -> term ("+" expr | empty)
term -> factor ("*" term | empty)

用前面定义过的东西可以这样写:

expr :: Parser Int
expr = do
    t <- term
    do
        symbol "+"
        e <- expr
        return (t + e)
      <|> return t
      
term :: Parser Int
term = do
    f <- factor
    do
        symbol "*"
        t <- term
        return (f * t)
      <|> return f
      
factor :: Parser Int
factor = do
    symbol "("
    e <- expr
    symbol ")"
    return e
  <|> integer
 
eval :: String -> Int
eval str = (fst . head) (parse expr str)

Monoid,Foldable,Traversal

Monoid(幺半群)

即有幺元(单位元)的半群。

半群是最简单的代数结构,由集合S和定义在集合S上满足结合律的一个二元运算组成。如果在一种数据上进行的一种运算满足结合律,且有单位元,那么我们可以将该数据和运算instance到Monoid上。

下面是Monoid的定义:

class Monoid a where
    mempty :: a -- 单位元
    mappend :: a -> a -> a -- 二元运算
    
    mconcat :: [a] -> a
    mconcat = foldr mappend mempty

List Monoid:

instance Monoid [a] where
    mempty = []
    mappend = (++)

Maybe Monoid:(一种实现)

instance (Monoid a) => Monoid Maybe a where
    mempty = Nothing
    Nothing `mappend` x = x
    x `mappend` Nothing = x
    (Just x) `mappend` (Just y) = Just (x `append` y)

另一种实现:

instance (Monoid a) => Monoid Maybe a where
    mempty = Just mempty
    Nothing `mappend` x = Nothing
    x `mappend` Nothing = Nothing
    (Just x) `mappend` (Just y) = Just (x `append` y)

可见,一种数据上定义的Monoid的并不是唯一的,满足条件的运算不同,定义出的Monoid也就不同。如果我们想同时定义多种Monoid,可以这样定义:

newtype Sum a = Sum a

instance (Num a) => Monoid (Sum a) where
    mempty = 0
    mappend = (+)

newtype Product a = Product a

intance (Num a) => Monoid (Product a) where
    mempty = 1
    mappend = (*)

`mappend`这种写法太麻烦了,于是可以写成<>,这两者是完全等价的。

notes:

实际上上面的Monoid的定义在现在的标准中是不对的,现在的标准中,Monoid的定义如下:

class Semigroup a => Monoid a where
    mempty :: a
    mappend :: a -> a -> a
    mappend = (<>)
    mconcat :: [a] -> a
    mconcat = foldr mappend empty

其中Semigroup意为半群,而(<>)则是半群上的运算符。

Foldable

前面提到过,foldl和foldr都是定义在Foldable上的,而Foldable的定义也和Monoid有关。

我们已经实现了Monoid,那么考虑如何将一个结构上的所有的数据通过mappend来连接起来,于是就有了fold:

class Foldable t where
    fold :: Monoid a => t a -> a
    foldMap :: Monoid b => (a -> b) -> t a -> b
    foldl :: (a -> b -> a) -> a -> t b -> a
    foldr :: (a -> b -> b) -> b -> t a -> b

List的定义显然:

instance Foldable [] where
    fold [] = mempty
    fold (x:xs) = x `mappend` fold xs
    
    foldMap _ [] =mempty
    foldMap f (x:xs) = f x `mappend` foldMap f xs
    
    foldr _ v [] = v
    foldr f v (x:xs) = f x (foldr f v xs)
    
    foldl _ v [] = v
    foldl f v (x:xs) = foldl f (f v x) xs

我们还能定义一个树:

data Tree a = Leaf a | Node (Tree a) (Tree a)

instance Foldable Tree where
    fold (Leaf x) = x
    fold (Node l r) = fold l `mappend` fold r
    
    foldMap f (Leaf x) = f x
    foldMap f (Node l r) = foldMap f l `mappend` foldMap f r
    
    foldl f v (Leaf x) = f v x
    foldl f v (Node l r) = foldl f (foldl f v l) r
    
    foldr f v (Leaf x) = f x v
    foldr f v (Node l r) = foldr f (foldr f v r) l

此外,Foldable上同样也有一些Generic Function:

null    :: t a -> Bool
length  :: t a -> Int
elem    :: Eq a => a -> t a -> Bool
maximum :: Ord a => t a -> a
minimum :: Ord a => t a -> a
sum     :: Num a => t a -> a
product :: Num a => t a -> a
foldr1  :: (a -> a -> a) -> t a -> a
foldl1  :: (a -> a -> a) -> t a -> a
toList  :: t a -> [a]

这些都可以用foldr来定义,甚至foldMap, fold, foldl也可以由foldr定义:

foldMap f = foldr (\x y -> f x `mappend` y) mempty
fold = foldr mappend mempty
foldl f v x = foldr (\x r -> (\v -> r (f v x))) id x v
toList = foldr (:) []

notes:

预加载的库中的Foldable没有fold函数,如要定义并使用该函数,需import Data.Foldable

Traversals

利用map和fmap,我们完成了将一个结构中的数据整体进行转换,而这种转换时使用的函数是pure的。如果我们需要一个effectful的转换,例如下例:

traverse :: (a -> Maybe b) -> [a] -> Maybe [b]
traverse g [] = pure []
traverse g (x:xs) = pure (:) <*> g x <*> traverse g xs

dec :: Int -> Maybe Int
dec 0 = Nothing
dec n = Just (n-1)

traverse dec [1,3,4] = Just [0,2,3]
traverse dec [2,1,0] = Nothing

那么我们定义一个class:

class (Functor t, Foldable t) => Traversable t where
    traverse :: Applicative f => (a -> f b) -> t a -> f (t b)

将List定义到这个class中:

instance Traversable [] where
    -- traverse :: Applicative f => (a -> f b) -> [a] -> f [b]
    traverse f [] = pure []
    traverse f (x:xs) = pure (:) <*> f x <*> traverse f xs

Tree:

instance Traversable Tree where
    -- traverse :: Applicative f => (a -> f b) -> Tree a -> f (Tree b)
    traverse f (Leaf x) = pure Leaf <*> f x
    traverse f (Node l r) = pure Node <*> traverse f l <*> traverse f r

另外,Traversable类中还有其他的函数:

sequenceA :: Applicative f => t (f a) -> f (t a)
sequenceA = traverse id

-- example : sequenceA [Just 1,Just 2] = Just [1,2]
--           sequenceA [Just 1,Nothing] = Nothing
-- traverse f = sequenceA . fmap g

mapM :: Monad m => (a -> m b) -> t a -> m (t b)
mapM = traverse
sequence :: Monad m => t (m a) -> m (t a)
sequence = sequenceA
-- 所有的Monad都是Applicative

推荐阅读