函数编程中functor和monad的形象解释

函数编程中Functor函子与Monad是比较难理解的概念,本文使用了形象的图片方式解释了这两个概念,容易理解与学习,分别使用Haskell和Swift两种语言为案例。

虽然Swift并不是一个函数式语言,但是我们可以用更多点代码来完成与Haskell同样的结果。Swift的代码见on GitHub.

这里是一个简单的值:

如果我们应用一个函数(+3)到这个值:

非常简答,是不是?现在我们拓展一下,上面的值我们没有设定上下文场景,如果我们假设一个值处于一种上下文场景context中,你可以将上下文场景看成是一个盒子,盒子里面放入的是一个值:

为什么要假设上下文场景呢?其实任何事物都无法脱离其与环境的关系,任何真理都是有上下文前提的,当然除了这条。

当值放入一个盒子上下文中时,如果你想将一个函数应用到这个值时,取决于上下文场景你会得到不同的结果。这个思想其实是Functors, Applicatives, Monads, Arrows等概念的基础。

那么上下文使用什么语法表达呢?再Haskell中使用Maybe,在Swift中使用Optional:

 

Swift:

enum Optional<T> {
  case None
  case Some(T)
}

Haskell:

data Maybe a = Nothing | Just a

Optional和Maybe类似两个盒子,其中包裹着两个不同的值函数:无(None)和有(Some)。

下面我们看看上面Swift中两个函数 .Some(T).None.不同,也就是Haskell两个函数Nothing与Just a的不同。现在来谈谈函子。

 

Functor函子

当一个值被一个上下文包裹时,你不能使用普通函数应用到这个值:

这就是需要 map 的用途 (fmap in Haskell), map 知道如何将普通函数应用到一个被上下文包裹的值中,比如,假设你要应用一个函数:+3,将这个函数应用到 .Some(2). 使用 map:

func plusThree(addend: Int) -> Int {
  return addend + 3
}

Optional.Some(2).map(plusThree)
// => .Some(5)

Haskell使用fmap,将+3函数应用到Just 2:

> fmap (+3) (Just 2) 
Just 5

 

fmap告诉我们它已经成功完成,但是它是如何应用函数到盒子里的值呢?

 

函子到底是什么?

一个函子Functor是任意类型,这些类型定义了如何应用 map (fmap in Haskell) 。 也就是说,如果我们要将普通函数应用到一个有盒子上下文包裹的值,那么我们首先需要定义一个叫Functor的数据类型,在这个数据类型中需要定义如何使用map或fmap来应用这个普通函数。这个函子Functor如下图:

 

fmap的输入参数是a->b函数,在我们这个案例中是(+3),然后定义一个函子Functor,这里是Haskell的Just 2,最后返回一个新的函子,在我们案例中,使用Haskell是Just 5。

我们再看看在Swift中如何实现,下面是autoclosure实现的函子:

Optional.Some(2).map { $0 + 3 }
// => .Some(5)

这里 map 魔术地使用了我们的函数(+3),这是因为 Optional 是一个函子,它定义了map如何将外部指定的普通函数应用到被上下文包裹的值函数 Somes 和 Nones中:

func map<U>(f: T -> U) -> U? {
  swifth self {
  case .Some(let x): return f(x)
  case .None: return .None
}

下图展示了函子内部工作原理:

第一步是将值从上下文盒子中解救出来,然后将外部指定的函数(+3)应用到这个值上,得到一个新的值(5),再将这个新值放入到上下文盒子中。是不是很形象生动?

当然,你也可以将(+3)应用到None上:

 

Optional.None.map { $0 + 3 }
// => .None

map或fmap知道如果你应用函数到None,你同样得到的还是无None,这里map或fmap是不是像禅一样呢?

现在我们知道Optional类型或Maybe类型存在的原因了吧?我们再用实际中案例说明,如果我们使用一种没有Optional/Maybe类型的语言从数据库查询结果,比如Ruby:

let post = Post.findByID(1)
if post != nil {
  return post.title
} else {
  return nil
}

而使用有Optional/Maybe类型的语言Swift/Haskell,则可以使用函子Optional:

findPost(1).map(getPostTitle)

如果 findPost(1) 返回一个帖子, 我们会使用 getPostTitle函数得到这个帖子的标题,如果返回无 None, 我们也返回无None!

我们可以甚至定义一个中缀操作符号,Swift的map的中缀操作符是<^>,而Haskell的fmap是<$>。:

Haskell直接写为:

getPostTitle <$> (findPost 1)

Swift代码如下:

infix operator <^> { associativity left }

func <^><T, U>(f: T -> U, a: T?) -> U? {
  return a.map(f)
}
getPostTitle <^> findPost(1)
 

下图显示了如何将一个普通函数应用到值集合,不是单个值,而是值的集合数组中:

图中数组函子将数组一个个打开(遍历),然后分别将普通函数应用到这些元素中,最后返回一个新的集合值。Haskell中定义:

instance Functor [] where fmap = map

下面我们看看将一个函数应用到另外一个函数的情况:

fmap (+3) (+1)

这里我们将函数(+3)应用到(+1)函数上,首先我们看看什么是函数,见下图:

 

一个函数是输入一个值,返回一个值。

下图是将一个函数应用到另外一个函数:

结果就是另外一个函数!

> import Control.Applicative
> let foo = fmap (+3) (+2)
> foo 10
15

所以说,函数也是一个函子functor:

instance Functor ((->) r) where fmap f g = f . g

你在一个函数上使用fmap ,实际你在做函数的组合,如同堆积木一样。

Swift将一个函数应用到另外一个函数的代码如下:

typealias IntFunction = Int -> Int

func map(f: IntFunction, _ g: IntFunction) -> IntFunction {
  return { x in f(g(x)) }
}

let foo = map({ $0 + 2 }, { $0 + 3 })
foo(10)
// => 15

见下文

 

本文另外一篇中文翻译:Functor, Applicative, 以及 Monad 的图片阐释

函数编程之道

什么是Monad

函数式编程