天天看点

了解函数式编程背后的属性:单子(Monad)

单子(Monad)和它们的法则

如前面所解释的,单子(Monad)是容器。对于面向对象开发者来说,它们看起来非常像范型。事实上,它们是范型的一个带有具体法则的更为特殊的版本。这些法则存在,以允许你在和单子(Monad)打交道的时候,可以总是期待相同的行为:

  • 左单位元(Left Identity);
  • 右单位元(Right Identity);
  • 关联性

在解释该法则之前,我还会定义类型必须是一个单子的一组操作,使用额外的通用签名,使用

arguments -> return

:

  • 返回(return)

    a -> M[a]

    ;
  • 绑定(bind)函数

    (M[a], a -> M[b]) -> M[b]

虽然类型构造和返回函数似乎很明显,但是绑定函数(在scala中,称为

flatMap

)需要一点解释:这是一个接受用

a

创建的单子的函数,以及一个接受

a

和返回用

b

创建的单子来返回用

b

创建的单子(原文是:It is a function that takes a Monad of

a

, and a function that takes

a

and returns a Monad of

b

to return a Monad of

b

)。

这可以如下在代码中写成:

val m: List[Int] = List(12, 34)
// m is our Monad, bein List a monadic type
​
val n = m flatMap (i => i.toString)
// toString is an operation that returns a String, which can be also read as a list of characters
​
n == List[Char]('1', '2', '3', '4')      

为嘛?该函数接受一个Int并返回一个String(字符表),它符合上面定义的

bind

签名。它接受一个值(列表中的第n个位置)和一个接受该值的函数,并用所提供函数的结果生成一个单子(与我们使用的单子相同的类型)。

有了这个解释,让我们继续讲法则:

左单位元(Left Identity)

该法则规定,

(return a) bind f == f a

或者

用'a'创建一个单子并用'f'绑定,与调用'f(a)'相同

我们可以证明这个法则:

def toListString: Int => List[String] = i => List(i.toString)
​
val leftProperty = List(1) flatMap (toListString)
val rightProperty = toListString(1)
​
leftProperty == rightProperty      

右单位元(Right Identity)

该法则规定,

m bind return == m

或者

在一个单子上绑定return返回相同的单子

我们可以证明这个法则:

val monad = List(1)
​
val boundValue = monad flatMap (List(_))
​
boundValue == monad      

关联性

该法则规定,

m bind f bind g == m bind (i -> f(i) bind g)

或者

绑定f到一个单子,然后绑定g,与绑定一个函数(这个函数使用f生成一个单子然后绑定g到这个单子)到相同的单子相同

它也许听起来有点复杂,但是我们可以证明这个法则:

def double(i: Int): List[Int] = List(i * 2)
def triple(i: Int): List[Int] = List(i * 3)
​
val monad = List(1)
​
val leftProperty = monad flatMap double flatMap triple
val rightProperty = monad flatMap {i => double(i) flatMap triple}
​
leftProperty == rightProperty      

你在Python中的第一个单子(Monad)

下面,我在Python中定义了Monad类,暴露了上面定义的法则,以及一些使用样例。

## Monad
class Monad(object):
​
  """A Monadic container in python."""
​
  def __init__(self, containedValue):
    """init is our 'return' function here."""
    self.__value = containedValue
​
  def flat_map(self, f):
    return f(self.__value)
​
  # Overriding '==' so we can prove the laws
  def __eq__(self, o):
    return isinstance(o, type(self)) and o.__value == self.__value
​
  def __repr__(self):
    return "{}({})".format(self.__class__.__name__, self.__value)
​
## Helper functions
​
def to_monad(i):
  return Monad(i)
​
def to_doubled_monad(i):
  return Monad(i * 2)
​
## Proving the laws
# Left Identity
Monad(1).flat_map(to_doubled_monad) == to_doubled_monad(1)
Monad("something").flat_map(to_doubled_monad) == to_doubled_monad("something")
​
# Right Identity
m = Monad(1)
m == m.flat_map(lambda k: Monad(k))
​
m2 = to_monad({1: 2})
m2 == m2.flat_map(to_monad)
​
# Associativity
m = Monad(1)
left = m.flat_map(to_monad).flat_map(to_doubled_monad)
right = m.flat_map(lambda k: to_monad(k).flat_map(to_doubled_monad))      

你在Python中的第二个单子(Monad)

正如上面的例子所描述的,

Monad

类型并未做什么特别的事,它只是像一个容器一样,包装实际的值。可以创建一些从Monad继承的有意义的类,例如下面的Try Monad:

class Try(Monad):
​
    class Success(Monad):
        pass
​
    class Failure(Monad):
        pass
​
    def flat_map(self, f):
        try:
            return super(Try, self).flat_map(f)
        except Exception as e:
            return Try.Failure(e)      

请注意,这个实现是基本的,并缺乏类型正确性,但是它对我们探索单子法则仍旧足够有趣。

想获取python学习资料的小伙伴可以加QQ:728711576