Haskell 中的 Functor Applicative Functor 和 Monad ·

时间:2023-01-06 18:16:27

整理一下 《Learn You a Haskell for Great Good !》 介绍的 FuntorApplicative FuntorMonad

并不打算写 Monad 相关的教程 ╮(╯▽╰)╭

##柯里化

柯里化(Currying)是将多个参数的函数化成一系列单个参数函数组合的技术。

多参函数柯里化后传递和使用都更加灵活。

很多语言支持手动柯里化。这个过程很简单。比如在 JavaScript 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function (a,b){
return a b;
}

// 对第一个参数柯里化
function OnePlus(b){
return 1 b;
}

// 对第二个参数柯里化
function plusOne(a){
return a 1;
}

在 Haskell 中多参函数接受参数不全时,会自动柯里化,返回一个接受剩余参数的函数。这个过程称为偏应用(Partial Apply,部分应用)

1
2
3
4
5
6
Prelude> ( 1) 2
3
-- ( 1) 是一个接受整形参数的函数,返回该参数 1 后的结果。
Prelude> (1 ) 2
3
-- (1 ) 是一个接受整形参数的函数,返回 1 该参数后的结果。

两个函数的类型是这样的

1
2
3
4
Prelude> :t ( 1)
( 1) :: Num a => a -> a
Prelude> :t (1 )
(1 ) :: Num a => a -> a

类型签名

类型签名,或者说类型声明,用于声明函数的类型,如参数的类型,接受几个参数,返回类型等。

上面 ( 1) 函数的类型是

1
2
Prelude> :t ( 1)
( 1) :: Num a => a -> a

从左往右看,( 1) 说的是函数名。 :: 类型说明操作符。可读为 「……的类型是」

Num a 是一种类型约束。这部分很像变量声明,Num 指的是数字类型。 a 是变量名,可以任意取,习惯上为了方便偷懒使用单个字母。所以 Num a 的意思是 a 的类型为 Num

=> 表示推出。用于分隔类型约束和函数签名的主体。

a 表示接受一个变量 a 型的参数

-> 表示返回

a 因为是在函数最后,所以就是返回一个 a 类型的参数。

Functor

Functor 是可以应用 fmap 的类型。

fmap 的类型是

1
2
Prelude> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b

这里的签名有括号。不影响结果。可以简单的认为把 (a -> b) 视为接受 a 返回 b 的函数。

从签名中可以看出,fmap 接受一个函数 a -> b ,再接受一个 Funtor f 类型的参数 a ,返回一个 Funtor f 类型的 b

把签名改一下,改成fmap :: Functor f => (a -> b) -> (f a -> f b)

这样理解为接受一个函数返回另外一个参数和返回值都是 Funtor f 类型的函数

看具体的例子。比如 Maybe 是一个 Funtor

1
2
3
instance Functor Maybe where  
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing

应用

1
2
3
4
Prelude> fmap ( 1) (Just 3)
Just 4
Prelude> fmap ( 1) Nothing
Nothing

并不是实现了 fmap 的实例都能称为 Funtor 。要想成为 Funtor 必须满足 Functor Law (函子律)。

1
2
fmap id = id
fmap f . g = fmap f . fmap g

第一条中 id 是一个返回自身的函数。也就是 x -> xfmap id = id 实际上是 point-free 写法,也就是无参写法。

写得完整一点是 fmap id x = id x 。意思是 fmap 应用返回自身的函数相当于直接应用返回自身的函数。

1
2
3
4
5
6
Prelude> fmap id (Just 3)
Just 3
-- fmap id (Just 3) = Just (id 3) = Just 3
Prelude> id (Just 3)
Just 3
-- id (Just 3) = (x -> x) (Just 3) = Just 3

第二条说的是分配律。也就是应用两个函数的组合等用于分别应用两个函数结果的组合。

写的完整一点是 fmap f . g x = fmap f (fmap g x)

1
2
3
4
Prelude> fmap ( ( 1) . (*2) ) (Just 3)
Just 7
Prelude> fmap ( 1) (fmap (*2) (Just 3))
Just 7

Applicative Functor

Applicative Functor 是可以应用 pure 和 apply (<*>) 的 Funtor

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

pure 的类型是 a -> f a ,用于将类型 a 转化为 Funtor f 类型的函数。

<*> 的类型是 f (a -> b) -> f a -> f b ,用于取出 Funtor f 类型中函数 a -> b ,再接受一个 Funtor f 类型的参数 a ,返回一个 Funtor f类型 b 。

Maybe 是一个 Applicative Funtor

1
2
3
4
instance Applicative Maybe where  
pure = Just
Nothing <*> _ = Nothing
(Just f) <*> something = fmap f something

应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Prelude> pure Nothing
Nothing
Prelude> pure ( 1) <*> Just 3
Just 4
Prelude> Just ( 1) <*> Just 3
Just 4
Prelude> pure ( ) <*> Just 1 <*> Just 3
Just 4
-- 类似于柯里化后的函数的应用过程
-- pure ( ) <*> Just 1 <*> Just 3
-- Just ( ) <*> Just 1 <*> Just 3
-- Just ( 1) <*> Just 3
-- Just ( 1 3)< 大专栏  Haskell 中的 Functor Applicative Functor 和 Monad · 拖鞋党的拖鞋摊/span>
-- Just 4

同样并不是实现了 pure<*> 的类型就是 Applicative Funtor

要想成为 Applicative funtor 必须满足 Applicative Functor Law 。

1
2
3
4
5
pure f <*> x = fmap f x
pure id <*> v = v
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
pure f <*> pure x = pure (f x)
u <*> pure y = pure ($ y) <*> u

在第一条 pure f <*> x = fmap f x 可以得到其他几条

对于 Maybe 来说是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Prelude> pure ( 1) <*> Just 3
Just 4
-- pure ( 1) <*> 3 = Just ( 1) <*> 3 = fmap ( 1) 3
Prelude> fmap ( 1) (Just 3)
Just 4
Prelude> pure id <*> Just 3
Just 3
-- pure id <*> Just 3 = fmap id (Just 3)
Prelude> fmap id (Just 3)
Just 3
Prelude> ((pure (.) <*> Just ( 1)) <*> Just (*2)) <*> Just 3
Just 7
-- ((pure (.) <*> Just ( 1)) <*> Just (*2)) <*> Just 3
-- ((Just (.) <*> Just ( 1)) <*> Just (*2)) <*> Just 3
-- (Just ((.) ( 1)) <*> Just (*2)) <*> Just 3
-- (Just (( 1) . )) <*> Just (*2)) <*> Just 3
-- (Just (( 1 . ) (*2))) <*> Just 3
-- (Just (( 1) . (*2))) <*> Just 3
-- fmap (( 1) . (*2)) (Just 3)
-- fmap ( 1) (fmap (*2) (Just 3))
-- fmap ( 1) (Just (*2) <*> (Just 3))
-- Just ( 1) <*> (Just (*2) <*> Just 3)
Prelude> Just ( 1) <*> (Just (*2) <*> Just 3)
Just 7
Prelude> pure ( 1) <*> pure 3 :: Maybe Int
Just 4
-- pure ( 1) <*> pure 3
-- Just ( 1) <*> Just 3
-- Just (( 1) 3)
-- pure (( 1) 3)
Prelude> pure ( ( 1) 3) :: Maybe Int
Just 4
Prelude> Just ( 1) <*> pure 3
Just 4
-- Just ( 1) <*> pure 3
-- Just ( 1) <*> Just 3
-- Just (( 1) (3))
-- Just (($ 3) ( 1))
-- Just ($ 3) <*> Just ( 1)
-- pure ($ 3) <*> Just ( 1)
Prelude> pure ($ 3) <*> Just ( 1)
Just 4

Monad

Monad 是可以应用 bind (>>= )的 Applicative Funtor

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

这里的 return 相当于 Applicative Funtorpure

不出所料的 Maybe 也是一个 Monad

1
2
3
4
instance Monad Maybe where  
return = Just
Nothing >>= f = Nothing
(Just x) >>= f = f x

应用

1
2
3
4
5
6
7
8
Prelude> return 1 :: Maybe Int
Just 1
Prelude> return Nothing
Nothing
Prelude> Nothing >>= (x -> return (1 x))
Nothing
Prelude> Just 3 >>= (x -> return (1 x))
Just 4

同样并不是实现了 >>= 就是 Monad 。要想成为 Monad 必须满足 Monad Law (单子律)

1
2
3
return x >>= f = f x
m >>= return = m
(m >>= f) >>= g = m >>= (x -> f x >>= g)

对于 Maybe 来说是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Prelude> return 3 >>= (x -> return (x 1)) ::Maybe Int
Just 4
-- return 3 >>= (x -> return (x 1))
-- Just 3 >>= (x -> return (x 1))
-- (x -> return (x 1)) 3
Prelude> (x -> return (x 1)) 3 ::Maybe Int
Just 4
Prelude> Just 3 >>= return
Just 3
-- Just 3 >>= return
-- return 3
Prelude> Just 3 >>= (x -> return (x*2)) >>= (x -> return (x 1))
Just 7
-- Just 3 >>= (x -> return (x*2)) >>= (x -> return (x 1))
-- (x -> return (x*2)) 3 >>= (x -> return (x 1))
-- Just 6 >>= (x -> return (x 1))
-- (x ->return (x 1)) 6
-- Just 7
Prelude> Just 3 >>= (x -> return (x*2) >>= (x -> return (x 1)))
Just 7
-- (x -> return (x*2) >>= (x -> return (x 1))) 3
-- return (3*2) >>= (x -> return (x 1))
-- Just 6 >>= (x -> return (x 1))
-- (x -> return (x 1)) 6
-- return (6 1)
-- Just 7

最后

还有 Comonad 我懒得来写了(逃