函数编程中functor和monad的形象解释
Applicative
当我们的值被一个上下文包裹,就像函子Functor:
之前我们讨论的是如何将一个普通函数应用到这个函子中,现在如果这个普通函数也是一个被上下文包裹的,怎么办?
在Haskell中,Control.Applicative 定义了 <*>, 它能知道如何应用一个被上下文包裹的函数到一个被上下文包裹的值中。
上图可见,Applicative内部也是将各自包裹的盒子打开,应用其中函数与值的计算,然后包裹新值在一个上下文中。
Just (+3) <*> Just 2 == Just 5
Swift并没有内建的Applicative. 可以显式打开包裹遍历实现::
extension Optional{
func apply<U>(f: (T -> U)?) -> U? {
switch f {
case .Some(let someF): return self.map(someF)
case .None: return .None
}
}
}
extension Array {
func apply<U>(fs: [Element -> U]) -> [U] {
var result = [U]()
for f in fs {
for element in self.map(f) {
result.append(element)
}
}
return result
}
}
如果 self
和函数都是 .Some
, 那么函数应用到解开包裹的optionm,否则返回 .None
.
也可以使用定义 <*>
来做同样事情:
infix operator <*> { associativity left }
func <*><T, U>(f: (T -> U)?, a: T?) -> U? {
return a.apply(f)
}
func <*><T, U>(f: [T -> U], a: [T]) -> [U] {
return a.apply(f)
}
i.e:
Optional.Some({ $0 + 3 }) <*> Optional.Some(2)
// => 5
使用 <*>
会有趣的事情发生:
> [(*2), (+3)] <*> [1, 2, 3]
[2, 4, 6, 4, 5, 6]
下面是使用Applicative能实现,而使用函子Functor不能实现的,你如何应用一个带有两个输入参数的函数到两个已经包裹的值中?
使用函子的代码如下:
> (+) <$> (Just 5)
Just (+5)
> Just (+5) <$> (Just 4)
ERROR ??? WHAT DOES THIS EVEN MEAN WHY IS THE FUNCTION WRAPPED IN A JUST
第二个发生错误了。
使用Applicative:
> (+) <$> (Just 5)
Just (+5)
> Just (+5) <*> (Just 3)
Just 8
Swift的代码如下:
func curriedAddition(a: Int)(b: Int) -> Int {
return a + b
}
curriedAddition <^> Optional(2) <^> Optional(3)
// => COMPILER ERROR: Value of optional type '(Int -> Int)? not unwrapped; did you mean to use '!' or '??'
Applicative:
curriedAddition <^> Optional(2) <*> Optional(3)
Applicative
将Functor
推到到一边. “只有大孩子才能使用带有任何数量参数的函数。”
func curriedTimes(a: Int)(b: Int) -> Int {
return a * b
}
curriedTimes <^> Optional(5) <*> Optional(3)
Monad
函子funtor是将一个普通函数应用到包裹的值:
Applicative应用一个包裹的函数到一个包裹的值:
Monad 则是将一个会返回包裹值的函数应用到一个被包裹的值上,Haskell中使用“>>=”表示,而Swift使用“|”.
Haskell中的MayBe也是一个monad:
假设half是一个只工作于偶数数字的函数:
half x = if even x
then Just (x `div` 2)
else Nothing
Swift代码如下:
func half(a: Int) -> Int? {
return a % 2 == 0 ? a / 2 : .None
}
对于这个half函数,其形象工作原理入下图,如果输入一个值,那么half会返回一个被包裹的值。
但是如果我们输入一个包裹的值,而不是普通的值呢?
这时原来的代码就不工作了,我们需要使用新的语法Haskell是“>>=”来将包裹后的值放入函数,“>>=”类似我们排堵的拔子:
代码工作如下:
> Just 3 >>= half
Nothing
> Just 4 >>= half
Just 2
> Nothing >>= half
Nothing
Swift的代码:
Optional(3) >>- half
// .None
Optional(4) >>- half
// 2
Optional.None >>- half
// .None
那么内部发生了什么?Monad 是另外一个typeclass. 这里是partial定义:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
Swift代码:
// For Optional
func >>-<T, U>(a: T?, f: T -> U?) -> U?
// For Array
func >>-<T, U>(a: [T], f: T -> [U]) -> [U]
monad工作原理图如下:
首先获得一个Monad,如Just 3,其次定义一个返回Monad的函数如half,最后结果也会返回一个Monad。
Haskell中Maybe也是一个Monad:
instance Monad Maybe where
Nothing >>= func = Nothing
Just val >>= func = func val
Swift的Optional
也是一个Monad。
下面是输入一个包裹值到一个函数中完整示意图:
第一步,绑定已经解除包裹的值,第二步,将已经解除包裹的值输入函数,第三步,一个被重新包裹的值被输出。
如果你输入无None,更加简单:
你可以像链条一样链接这些调用:
> Just 20 >>= half >>= half >>= half
Nothing
Swift代码:
Optional(20) >>- half >>- half >>- half
// => .None
总结
- 函子functor是一种实现fmap或map的数据类型
- applicative是一种实现了Applicative 或apply的数据类型
- monad是一种实现了Monad或flatmap的数据类型
.
- Haskell的Maybe和Swift的Optional是functor函子 applicative和Monad。.
那么函子、applicative和Monad三个区别是什么?
- functor: 应用一个函数到包裹的值,使用fmap/map
.
- applicative: 应用一个包裹的函数到包裹的值。
- monad: 应用一个返回包裹值的函数到一个包裹的值。
本文另外一篇中文翻译:Functor, Applicative, 以及 Monad 的图片阐释