从空值开始
大多数语言都具有空值这一设定,它用于表示这个变量没有任何内容与之相关联。空值虽然是一个简单的概念,但是却无形中给程序员带来了巨大的压力。空值仿佛是悬在程序员头上的达摩克里斯之剑,程序员必须仔细的判断这个变量是否可能为空以及什么时候为空,否则达摩克里斯之剑就会坠下。因此,空值判断或许成了最多的重复代码,例如:
lower_string = None if string is None else string.lower()
striped_string = None if string is None else string.strip()
这让我们忍不住思考,是否有更优雅的方式来解决这个问题?
Maybe
是的,我们的确有对付空值的手段,它就是Maybe
。Maybe
的代码非常简单,任何一个学习过 python 的程序员都能信手拈来:
class Maybe:
def __init__(self, value):
self._value = value
@staticmethod
def of(value):
return Maybe(value)
def map(self, f):
return Maybe.of(None) if self.isEmpty() else Maybe.of(f(self._value))
def isEmpty(self):
return self._value is None
def get(self):
if self.isEmpty():
raise ValueError("no value present!")
return self._value
def __str__(self) -> str:
return f"Maybe({self._value})"
def __repr__(self) -> str:
return self.__str__()
首先可以看到 Maybe
具有 of
方法,它接收一个普通的值并将其存放在 Maybe
中。并且 map
方法接收一个函数并将其应用到其中存放的值上进行变换得到一个新的 Maybe
。但是这里需要注意的是,如果Maybe
中存放的值为空则不会使用函数变换而仍然返回一个空的 Maybe
。这样就算我们对装有空值的 Maybe
调用 map
方法,它也不会出现空值错误!来构建几个 Maybe
的使用样例:
x = Maybe.of(" hello,world ") \
.map(lambda x: x.strip()) \
.map(lambda x: x.upper())
print(x) # Maybe(HELLO,WORLD)
y = Maybe.of(None) \
.map(lambda x: x.strip()) \
.map(lambda x: x.upper())
print(y) # Maybe(None)
在例子中我们对装有空值的 Maybe
进行了 strip
以及 upper
但仍然平安无事,看起来确实 Maybe
将狡猾的空值控制住了。
join
到这里,我们似乎在针对空值的战争上胜利了。但是先别急,假设需要实现这样一个函数,对于类似 a=1,2,3
这样的字符串,获取 =
后面 ,
分割后的第 n
个字符。如果使用 Maybe
来实现:
def split(string, char):
return string.split(char)
def get_value(kvs):
return Maybe.of(kvs[1]) if len(kvs) == 2 else Maybe.of(None)
def get_at(values, index):
return Maybe.of(None) if len(values) < index else values[index]
a = Maybe.of("a=1,2,3") \
.map(lambda x: split(x, '=')) \
.map(get_value) \
.map(lambda x: x.map(lambda y: split(y, ',')))
print(a) # Maybe(Maybe(['1', '2', '3']))
b = Maybe.of("1,2,3") \
.map(lambda x: split(x, '=')) \
.map(get_value) \
.map(lambda x: x.map(lambda y: split(y, ',')))
print(b) # Maybe(Maybe(None))
由于我们的字符串本身存放在 Maybe
中,而 get_value
却又返回了一个 Maybe
,这使得我们不得不写出 lambda x: x.map(lambda y: split(y, ','))
这样奇怪的 lambda 表达式来进行 map,并且如果要获取其中的值,就不得不使用 a.get().get()
。这明显不是一个友好的方法,但是 map 结果返回一个 Maybe
又确实是非常常见的一件事情。为了解决这个问题,我们在 Maybe 中增加一个 join
方法:
def join(self):
return Maybe.of(None) if self.isEmpty() else self._value
a = Maybe.of("a=1,2,3") \
.map(lambda x: split(x, '=')) \
.map(get_value).join() \
.map(lambda y: split(y, ','))
print(a) # Maybe(['1', '2', '3'])
b = Maybe.of("1,2,3") \
.map(lambda x: split(x, '=')) \
.map(get_value).join() \
.map(lambda y: split(y, ','))
print(b) # None
当我们在调用了 get_value
进行一个 join ,将嵌套的 Maybe
解开,这样我们后续的操作以及得到的结果都变得清爽了。但是正如我刚刚所说,返回 Maybe
的 map
函数是如此常见,因此我们可以实现一个 flat_map
方法:
def flat_map(self, f):
return self.map(f).join()
a = Maybe.of("a=1,2,3") \
.map(lambda x: split(x, '=')) \
.flat_map(get_value) \
.map(lambda y: split(y, ','))
print(a)
b = Maybe.of("1,2,3") \
.map(lambda x: split(x, '=')) \
.flat_map(get_value) \
.map(lambda y: split(y, ','))
print(b)
一切都变得好起来了。当然,目前更通用的做法是将 map
方法延迟到子类去实现,不过这都不是核心内容:
class Maybe:
def __init__(self, value):
self._value = value
@staticmethod
def of(value):
return Nothing() if value is None else Some(value)
def isEmpty(self):
return self._value is None
def __str__(self) -> str:
return f"Maybe({self._value})"
def __repr__(self) -> str:
return self.__str__()
def join(self):
return Nothing() if self.isEmpty() else self._value
def flat_map(self, f):
return self.map(f).join()
class Some(Maybe):
def __init__(self, value):
super().__init__(value)
def map(self, f):
return Some(f(self._value))
def get(self):
return self._value
def __str__(self) -> str:
return f"Some({self._value})"
def __repr__(self) -> str:
return self.__str__()
class Nothing(Maybe):
def __init__(self):
super().__init__(None)
def map(self, f):
return Nothing()
def get(self):
raise ValueError("no value present!")
def __str__(self) -> str:
return f"Nothing()"
def __repr__(self) -> str:
return self.__str__()
说回 Monad
说了这么多,好像 Monad 还未解开她的神秘面纱。不过这正如一句话所说,众里寻她千百度,暮然回首那人却在灯火阑珊出。刚才我们谈了许久的 Maybe,其实就是一种经典的 Monad。或许不是很准确,但是你可以认为实现了 of
, map
以及 join
这个三个方法的类都可以称之为 Monad
。Monad
正如设计模式,当你对它有了详细的了解后,你会发出:“原来这个东西我一直在用”以及“原来这样简洁的东西居然有这么高大上的名字”。当然,Monad
确实远不于此, 它概念来自于范畴论,译作单子
。你还可以这样向你的朋友介绍它的高大上版本:
Monad 说白了不过就是自函子范畴上的一个幺半群而已”
只要当你朋友反问你什么是自函子,范畴以及幺半群时你能作出详细回答。
更多的 Monad
再介绍更多的 Monad
之前,我不得不引入函数类型签名。当我们可以这样来表示一个输入类型为 String 输出类型是 Numer 的函数: f: String => Number
。当然,某些时候函数的输入和输出不一定是固定的,例如 f: T => U
表示输入类型为 T 输出类型为 U 的函数。甚至有的时候函数也可以是输入参数那么那些可能表示为 (U => T) => T
,这表示输入参数为类型为U => T
的函数,输出参数类型为 T
。那么借由这些表达方式,我们可以定义 Monad 的几个方法:
Monad{
of: U => Monda[U]
map: (U => T) => Monad[T]
join: Monad[Monad[T]] => Monad[T]
flat_map: (U => Monad[T]) => Monad[T]
}
用过 scala
的朋友可能会恍然大悟: “难道,List 也是一种 Monad”。没错,如假包换,List
也是一种Monad
。举个例子,List 的 map 方法实际上是将 List 中的元素的类型变成另一种类型。那么表示出来就为 map: (U => T) => List[T]
,这与 Monad
完全符合。而 flat_map
也正好是将输出为 List
的 map 变得扁平,那么就有 (U => List[T]) => List[T]
。这实在是巧妙,原来我们已经使用了很久的 Monad
。我们可以这样实现一个简单版:
class Monad:
def of(self, value): pass
def map(self, f): pass
def join(self): pass
def flat_map(self, f):
return self.map(f).join()
class List(Monad):
def __init__(self, *value) -> None:
self._value = list(value)
def of(value):
return List(value)
def map(self, f):
return List(*[f(x) for x in self._value])
def join(self):
return List(*reduce(lambda x,y: x + y , self._value, []))
def append(self, value):
self._value.append(value)
return self
def get(self):
return self._value
y = List.of("hello,world").append("hello,monad")\
.flat_map(lambda x: x.split(","))\
.map(lambda x: x.upper()) \
.get()
print(y)
虽然足够简陋,但是也能看出 List
确实就是 Monad
。同样,在 javascript 中非常常见的 Promise
也是一种 Monad
。在 java 1.8 增加的 stream 也是一种 Monad
(java 中的 List 不算 Monad)。
map, jon, flat_map, of
实际上,map
和 flat_map
是两个可以互相转化的方法。例如,当我们定义了 map
以及 of
后可以通过:
def flat_map(self, f):
self.map(join).join()
来定义 flat_map
。但实际上,如果我们定义了 flat_map
以及 of
之后,我们也可以通过:
def map(self, f):
self.flat_map(lamnda x: self.of(f(x)))
来定义 map。