二元函数的共性, 参数与返回值类型相同, 且存在一个值在操作前后不变如1
21 * N = N, N * 1 = N
[] ++ List = List, List ++ [] = List
monoid定义
Monoid是一个type class1
2
3
4
5class Monoid m where
mempty :: m
mappend :: m -> m -> m
mconcat :: [m] -> m
mconcat = foldr mappend mempty
mappend,命名来自++这种append,但是像乘法*这样的,并不是append,理解为参数与返回值类型的二元函数就可以
mconcat函数可以重写(某些情况下效率比默认的实现好),但大多数情况下用默认的实现就很好.
monoid法则
1 | mempty `mappend` x = x |
newtype
1 | newtype ZipList a = ZipList { getZipList :: [a] } |
使用现有类型定义一个新类型
newtype会比用data包装旧类型的性能要好
另外,举例说明,如果我们想把tuple定义成符合functor,
且fmap (+3) (1, 1)的结果是(4, 1),
由于functor都是对后一个type parameter做处理(如Either a b)
想让tuple的第一个元素被fmap作用就有点难
看newtype怎么解决这个问题:1
newtype Pair b a = Pair { getPair :: (a, b) }
newtype Laziness
because Haskell knows that types made with the newtype keyword can have only one constructor,
it doesn’t need to evaluate the value passed to the function to
make sure that the value conforms to the (CoolBool _) pattern,
because newtype types can have only one possible value constructor and one field
Pattern matching on newtype values isn’t like taking something out of a box (as it is with data ),
but more about making a direct conversion from one type to another.
ghci
导入package
import Data.Monoid
一些monoid的例子
List
1 | instance Monoid [a] where |
Num
由于数字有加法和乘法均满足monoid, 此处就可以使用newtype分别实现Monoid type class1
2
3
4
5
6
7
8
9newtype Product a = Product { getProduct :: a }
deriving (Eq, Ord, Read, Show, Bounded)
instance Num a => Monoid (Product a) where
mempty = Product 1
Product x `mappend` Product y = Product (x * y)
ghci> getProduct $ Product 3 `mappend` Product 4 `mappend` Product 2
24
加法类似1
2ghci> getSum . mconcat . map Sum $ [1,2,3]
6
Bool
1 | newtype Any = Any { getAny :: Bool } |
Ordering
1 | instance Monoid Ordering where |
1 | lengthCompare :: String -> String -> Ordering |
可以改写为1
2
3lengthCompare :: String -> String -> Ordering
lengthCompare x y = (length x `compare` length y) `mappend`
(x `compare` y)
比第一种写法省一个if,且省a, b变量定义
且带有自动短路功能
Maybe
1 | instance Monoid a => Monoid (Maybe a) where |
1 | ghci> Just (Sum 3) `mappend` Just (Sum 4) |
考虑到Maybe的内容的类型不是monoid的情况(Maybe的mappend定义要求m1和m2都是monoid类型),
于是有了First类型1
2
3
4
5
6
7
8
9newtype First a = First { getFirst :: Maybe a }
deriving (Eq, Ord, Read, Show)
instance Monoid (First a) where
mempty = First Nothing
First (Just x) `mappend` _ = First (Just x)
First Nothing `mappend` x = x
ghci> getFirst $ First (Just 'a') `mappend` First (Just 'b')
Just 'a'
First is useful when we have a bunch of Maybe values and we just want to know if any of them is a Just.
与First相应的,还有一个Last类型
Folding with Monoids
1 | Foldable type class |
以Tree为例1
2
3
4
5
6
7
8
9
10
11data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show)
instance F.Foldable Tree where
foldMap f EmptyTree = mempty
foldMap f (Node x l r) = F.foldMap f l `mappend`
f x `mappend`
F.foldMap f r
ghci> getAny $ F.foldMap (\x -> Any $ x == 3) testTree
True
ghci> F.foldMap (\x -> [x]) testTree
[1,3,6,5,8,9,10]