[关闭]
@MiloXia 2015-04-21T15:31:19.000000Z 字数 6030 阅读 1992

Functors, Applicative Functors, Monoids, Monad (Haskell 描述)

函数式编程


Functors

Functors 是可以被 map over 的对象,像是 lists,Maybe(Scala的Option),trees 等等
Haskell中用Functor typeclass来描述 这个 typeclass 只有一个 method fmap

  1. class Functor f where
  2. fmap :: (a -> b) -> f a -> f b
  3. -- f Functorinstance Maybe List

要成为这个typeclass的instance必须是只带一个type parameter的,如Maybe a List a

  1. instance Functor Maybe where
  2. fmap f (Just x) = Just (f x)
  3. fmap f Nothing = Nothing
  4. instance Functor [] where
  5. fmap = map

那么Either a b 这种接受两个type parameter的,必须把 fmap 限缩成只是 Either a

  1. instance Functor (Either a) where
  2. fmap :: (b -> c) -> Either a b -> Either a c

别的实例: IO

  1. instance Functor IO where
  2. fmap f action = do
  3. result <- action
  4. return (f result)
  5. main = do line <- getLine
  6. let line' = reverse line
  7. putStrLn $ "You said " ++ line' ++ " backwards!"
  8. --等价于
  9. main = do line <- fmap reverse getLine
  10. putStrLn $ "You said " ++ line ++ " backwards!"

别的实例: (->) r

  1. instance Functor ((->) r) where
  2. fmap f g = (\x -> f (g x))

将一个 function map over 一个 function 会得到另一个 function,就如 map over 一个 function 到 Maybe 会得到一个 Maybe
这根本就是 function composition,把 r -> a 的输出接到 a -> b 的输入

  1. instance Functor ((->) r) where
  2. fmap = (.)
  3. ghci> :t fmap (*3) (+100)
  4. fmap (*3) (+100) :: (Num a) => a -> a
  5. ghci> fmap (*3) (+100) 1
  6. 303
  7. ghci> (*3) . (+100) $ 1
  8. 303

你可以把 fmap 想做是一个函数(a->b),他接受另一个函数跟一个 functor (a),然
后把函数对 functor 每一个元素(a)做映射,或你可以想做他是一个函数,他接
受一个函数并把他 lift 到可以在 functors 上面操作

functor laws

  1. fmap id = id
  2. fmap (f . g) F = fmap f (fmap g F)

第一定律:对 functor 做 map id,那得到的新的 functor 应该要跟原来的一样,不多做任何事情
例子:

  1. ghci> fmap id (Just 3)
  2. Just 3
  3. ghci> id (Just 3)
  4. Just 3
  5. ghci> fmap id [1..5]
  6. [1,2,3,4,5]
  7. ghci> id [1..5]
  8. [1,2,3,4,5]
  9. ghci> fmap id []
  10. []
  11. ghci> fmap id Nothing
  12. Nothing

第二定律:先将两个函数合成并将结果 map over 一个 functor 的结果,应该跟先将第一个函数 map over 一个 functor,再将第二个函数 map over 那个 functor 的结果是一样的 (“.”为 function composition)

functor的意义

我们可以把 functor 看作输出具有 context 的值。
(例如说 Just 3 就是输出 3,但他又带有一个可能没有值的 context。[1,2,3] 输出三个值,1,2跟 3,同时也带有可能有多个值或没有值的 context。(+3) 则会带有一个依赖于参数的 context)

如果你把 functor 想做是输出值这件事,那你可以把 map over 一个functor 这件事想成在 functor 输出的后面再多加一层转换。
(当我们做 fmap(+3) [1,2,3]的时候,我们是把 (+3) 接到 [1,2,3] 后面,所以当我们监视任何一个 list 的输出的时候,(+3) 也会被套用在上面。另一个例子是对函数做 map over。当我们做 fmap (+3) (*3),我们是把 (+3) 这个转换套用在 (*3) 后面。这样想的话会很自然就会把 fmap 跟函数合成关联起来(fmap (+3) (*3) 等价于 (+3) . (*3),也等价于 \x -> ((x*3)+3)),毕竟我们是接受一个函数 (*3) 然后套用 (+3) 转换。最后的结果仍然是一个函数,只是当我们喂给他一个数字的时候,他会先乘上三然后做转换加上三。这基本上就是函数合成在做的事)

Applicative Functors

Curried

Haskell 中函数缺省就是 Curried 的,那代表接受多个参数的函数实际上是接受一个参数然后返回一个接受剩余参数的函数
(如果一个函数的类型是 a -> b -> c, 通常会说这个函数接受两个参数并返回 c, 但他实际上是接受 a 并返回一个 b -> c 的函数)
这个机制让我们可以 partially apply 一个函数,可以用比较少的参数调用他们。可以做成一个函数再喂给其他函数。

包含了函数的 functor

functor map over 一个函数的时候,用的函数都是只接受一个参数的。但如果要 map 一个接受两个参数的函数呢?

  1. ghci> :t fmap (++) (Just "hey")
  2. fmap (++) (Just "hey") :: Maybe ([Char] -> [Char])
  3. ghci> :t fmap compare (Just 'a')
  4. fmap compare (Just 'a') :: Maybe (Char -> Ordering)
  5. ghci> :t fmap compare "A LIST OF CHARS"
  6. fmap compare "A LIST OF CHARS" :: [Char -> Ordering]
  7. ghci> :t fmap (\x y z -> x + y / z) [3,4,5,6]
  8. fmap (\x y z -> x + y / z) [3,4,5,6] :: (Fractional a) => [a -> a -> a]

fmap () (Just 3) 会得到 Just (() 3),也可以写做 Just (* 3)
用一个多参数的函数来 map functor,我们会得到一个包含了(partially apply 的)函数的 functor
我们能用一个吃这些函数的函数(lamda)来 map over 这个 functor,这些在 functor
中的函数都会被当作参数丢给我们的函数。

  1. ghci> let a = fmap (*) [1,2,3,4]
  2. ghci> :t a
  3. a :: [Integer -> Integer]
  4. ghci> fmap (\f -> f 9) a
  5. [9,18,27,36]

(\f -> f 9)为lamda

functor解不掉的问题

想要把第一个 Just (3 *) map over Just 5 呢?
也就是一个包含function的functor map over 一个functor

Applicative functor

用来functor间做高阶运算的,不用解functor就实现计算

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

Applicative必须也是 Functor
pure :: a -> f a
(f 代表applicative functor 的 instance)
pure接受一个值并返回一个 applicative functor,里面装有结果

pure 把一个普通值放到一个缺省的 context 下, 一个最小的 context 但仍然包含这个值。

<* > :: f (a -> b) -> f a -> f b
(唉,a,b是类型)
fmap类型为 (a -> b) -> f a -> f b
<* >为加强版的 fmap
例子

  1. instance Applicative Maybe where
  2. pure = Just
  3. Nothing <*> _ = Nothing
  4. (Just f) <*> something = fmap f something
  5. --test
  6. ghci> Just (+3) <*> Just 9
  7. Just 12
  8. ghci> pure (+3) <*> Just 10
  9. Just 13
  10. ghci> pure (+3) <*> Just 9
  11. Just 12
  12. ghci> Just (++"hahah") <*> Nothing
  13. Nothing
  14. ghci> Nothing <*> Just "woot"
  15. Nothing

对于普通的 functors,你可以用一个函数 map over 一个 functors,但你
可能没办法拿到结果。而 applicative functors 则让你可以用单一一个函数操
作好几个 functors

  1. ghci> pure (+) <*> Just 3 <*> Just 5
  2. Just 8
  3. ghci> pure (+) <*> Just 3 <*> Nothing
  4. Nothing
  5. ghci> pure (+) <*> Nothing <*> Just 5
  6. Nothing

applicative style

用 applicative style 的方式来使用 applicative functors。
像是 pure f <* > x <* > y <* > ... 就让我们可以拿一个接受多个参数
的函数,而且这些参数不一定是被包在 functor 中。就这样来套用在多个
在 functor context 的值。这个函数可以吃任意多的参数,毕竟 <*> 只是做
partial application 而已

pure f <* > x 等于 fmap f x ,也是 applicative laws 的其中一条
Aaplicative 会 export 一个函数 ,他基本上就是中缀版的
fmap

  1. (<$>) :: (Functor f) => (a -> b) -> f a -> f b
  2. f <$> x = fmap f x

的使用显示了 applicative style 的好处: 统一
如果我们想要将 f 套用三个 applicative functor。我们可以写成 f x <* > y <*> z。
如果参数不是 applicative functor 而是普通值的话。我们则写成 f x y z

  1. ghci> (++) <$> Just "johntra" <*> Just "volta"
  2. Just "johntravolta"
  3. ghci> (++) "johntra" "volta"
  4. "johntravolta"

只 要稍 微 写 一 些 跟 <*> 就 可 以 把 函 数 变 成 applicative style,可 以 操 作applicatives 并返回 applicatives。
List 也是 applicative functor。 他的context是non-deterministic 的计算

  1. instance Applicative [] where
  2. pure x = [x]
  3. fs <*> xs = [f x | f <- fs, x <- xs]
  4. ghci> [(+),(*)] <*> [1,2] <*> [3,4]
  5. [4,5,5,6,3,4,6,8]

Monoids

定义

  1. class Monoid m where
  2. mempty :: m
  3. mappend :: m -> m -> m
  4. mconcat :: [m] -> m
  5. mconcat = foldr mappend mempty

mempty 表示一个特定 monoid 的 identity
mappend 接受两个 monoid 的值并返回另外一个 (二元操作)
mconcat 接受一串 monoid值,并将他们用 mappend 简化成单一的值

monoid law

  1. //mempty 相对于 mappend 必须要表现成 identity
  2. # `mempty `mappend` x = x`
  3. # `x `mappend` mempty = x`
  4. //遵守结合律
  5. # `(x `mappend` y) `mappend` z = x `mappend` (y `mappend` z)`

例子
Lists are monoids
Maybe the monoid

  1. instance Monoid [a] where
  2. mempty = []
  3. mappend = (++)
  4. instance Monoid a => Monoid (Maybe a) where
  5. mempty = Nothing
  6. Nothing `mappend` m = m
  7. m `mappend` Nothing = m
  8. Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)

意义

fold
另一种有趣的 monoid 使用方式就是让他来帮助我们 fold 一些数据结构
并发
半群里的“结合律”特性,使得我们可以对一些任务拆分采用并行处理,只要这些任务的结果类型符合“结合律”(即没有先后依赖)
[ http://hongjiang.info/semigroup-and-parallel/ ]

Monad

基本上他是一种加强版的 Applicative Functor
数学解释:自函子范畴上的一个幺半群
自函子(范畴映射后还是属于原来的范畴,就是上面的context不变)
幺半群(monoid 满足封闭性、结合律并且有幺元 (mempty))
[ http://hongjiang.info/understand-monad-5-what-is-endofunctor/ ]

wiki
单子(monad,也译单体)是函数式编程中的一种抽象数据类型,其特别之处在于,它是用来表示计算而不是数据的。在以函数式风格编写的程序中,单子可以用来组织包含有序操作的过程,或者用来定义任意的控制流(比如处理并发、异常、延续)
单子的构造包括定义两个操作bind和return,还有一个必须满足若干性质的类型构造器M。

我的研究
[ https://www.zybuluo.com/MiloXia/note/87772 ]

  1. class Monad m where
  2. return :: a -> m a
  3. (>>=) :: m a -> (a -> m b) -> m b

return等价于 pure
bind: >>= 接受一个 monadic value(也就是具有 context 的值)并且
把他喂给一个接受普通值得函数,并返回一个 monadic value

总结

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注