当前位置:Java -> Pythonic编码通过函数式风格: Iterable Monad
Python 是一种功能强大的语言,非常适合脚本编写和快速应用程序开发。Python 支持函数式编程风格,函数被视为一级值。这使得可以编写更简洁和更表现力强的Python代码,或者更“Pythonic”的代码。在本文中,我们将介绍 Python 中的 Iterable Monad。通过使 Iterable Python 集合成为单子(monadic)的实现,我们展示了函数式特性在 Python 中的威力,以及 Monad 可以为 Python 编码带来的优雅。我们还证明了 Iterable Monad 符合 Monad 定律。考虑到可迭代对象被大量使用,而原生 Python 不支持单子编码,这一机制可以显著改善 Python 编程。
在函数式编程中,单子是一种结构,它将程序片段(函数)组合在一起,并用一种额外的计算类型将它们的返回值封装起来。除了定义封装的单子类型之外,单子还定义了两个运算符:一个用于将值封装在单子类型中,另一个用于将输出类型为单子类型的函数组合在一起(这些函数被称为单子函数)。
通用程序语言使用单子来减少常见操作所需的样板代码(例如处理未定义的值或易出错的函数,或封装繁琐的代码)。函数式语言使用单子将复杂的函数序列转换为简洁的管道,以将控制流和副作用抽象化。
在 Haskell 中,上述定义可以用两个函数表示如下:
return :: a -> M a
bind :: (M a) -> (a -> M b) -> (M b)
第一个函数是一个将值包装在单子中的包装函数;
第二个函数是一个将函数应用于值并将结果包装在单子中的函数。
在本文中,我们遵循 Python 命名约定,使用 __init__()
作为包装函数的名称,flat_map()
作为绑定函数的名称。
单子满足以下定律:
对使用 flat_map
函数将由恒等函数提升的值 x
应用函数 f
等效于直接将函数 f
应用于值 x
:
使用恒等函数作为函数 f
的 flat_map
函数结果为原始单子值:
x.flat_map(y => __init__(y)) = x
使用序列的 flat_map
调用将两个函数 f
和 g
应用于单子值等效于使用 flat_map
函数应用参数为 f
的结果的 g
:
x.flat_map(f).flat_map(g) = x.flat_map(x => f(x).flat_map(g))
一个通用接口被定义为:
class Monad:
def __init__(self, value: object = None):
pass
def flat_map(self, f: Callable) -> Monad:
pass
Monad 已经引入到 Python 编程中以解决各种问题,例如 Maybe Monad、Either Monad、Promise Monad、IO Monad 等[2]。在本文中,我们提议将 Python 可迭代对象提升为单子,从而可以在可迭代对象上执行的操作获得单子可以提供的同样好处。
Python 可迭代对象是可以遍历的项目集合。集合中的项目通过使用迭代器 function __iter__() 和 __next__()
进行访问。具体而言,可以按以下方式访问可迭代对象中的项目:
for item in an_iterable:
Print (x)
Python 原生 map 函数接受一个函数并将其映射到 Iterable,从而生成另一个可迭代对象。然而,当一个可迭代对象被包装成单子时,通过将可迭代对象提升为单子,事情变得更有趣。我们将稍后讨论其独特的特征。
Python 可迭代单子是将 Python 可迭代对象包装到单子中,能够将映射函数应用于给定可迭代对象的每个元素。单子 flat_map()
与 Python 原生映射函数的区别在于单子 flat_map()
知晓单子上下文并将可迭代对象作为整体处理。
Python 中有四种可迭代集合:list、tuple、set 和 dictionary。
可以按以下方式编码通用接口:
class IterableMonad:
def flat_map(self, f: Callable) -> 'IterableMonad':
return self.map(f).flatten()
flat_map()
充当将一个 Python 可迭代对象绑定到单子、将映射结果包装到单子并解包以返回新单子的功能。每个可迭代单子都必须根据其自身特点提供 map()
和 flatten()
的实现,例如 ListMonad:
class ListMonad(IterableMonad):
def map(self, f:Callable):
return ListMonad((list)(map(f, self.value)))
def flatten(self):
return ListMonad(list([item for monad in self.value for item in monad.value]))
*另外,我们实现 __eq__()
以检查两个单子的相等性以供测试:
def __eq__(self, other):
return self.value == other.value if isinstance(other, type(self)) else False
**SetMonad:与其他 Python 可迭代对象的实现略有不同,因为 set 元素需要是可哈希的。
我们证明我们的可迭代单子遵守 Monad 定律。例如,ListMonad:
#left identity
a = 2
lhs = ListMonad([a]).flat_map(f)
rhs = f(a)
print(lhs == rhs)
# right identity
lhs=ListMonad([a]).flat_map(lambda x: ListMonad([x]))
rhs=ListMonad([a])
print(lhs == rhs)
# associativity
m = ListMonad([1, 2])
lhs = m.flat_map(f).flat_map(g)
rhs = m.flat_map(lambda x: f(x).flat_map(g))
print(lhs == rhs)
DictMonad:
#left identity
d = {'a': 1, 'b':2}
lhs = DictMonad(d).flat_map(f)
rhs = f(d)
print(lhs == rhs)
# right identity
lhs=DictMonad(d).flat_map(lambda x: DictMonad(x))
rhs=DictMonad(d)
print(lhs == rhs)
# associativity
m = DictMonad(d)
lhs = m.flat_map(f).flat_map(g)
rhs = m.flat_map(lambda x: f(x).flat_map(g))
print(lhs == rhs)
在本文中,我们介绍了将Python可迭代对象提升为单子的思想,这使我们能够以安全、可读性和模块化的方式编写Python代码,从而使我们能够以单子而不是惯用的风格编写Python代码。我们还提供了一个参考实现,并证明该实现遵守单子定律。
参考实现可以在这里找到。
参考资料:
[1] https://en.wikipedia.org/wiki/Monad_(functional_programming)
推荐阅读: 剑指offer 65. 不用加减乘除做加法
本文链接: Pythonic编码通过函数式风格: Iterable Monad