当前位置:Java -> Pythonic编码通过函数式风格: Iterable Monad

Pythonic编码通过函数式风格: Iterable Monad

Python 是一种功能强大的语言,非常适合脚本编写和快速应用程序开发。Python 支持函数式编程风格,函数被视为一级值。这使得可以编写更简洁和更表现力强的Python代码,或者更“Pythonic”的代码。在本文中,我们将介绍 Python 中的 Iterable Monad。通过使 Iterable Python 集合成为单子(monadic)的实现,我们展示了函数式特性在 Python 中的威力,以及 Monad 可以为 Python 编码带来的优雅。我们还证明了 Iterable Monad 符合 Monad 定律。考虑到可迭代对象被大量使用,而原生 Python 不支持单子编码,这一机制可以显著改善 Python 编程。

Monad

在函数式编程中,单子是一种结构,它将程序片段(函数)组合在一起,并用一种额外的计算类型将它们的返回值封装起来。除了定义封装的单子类型之外,单子还定义了两个运算符:一个用于将值封装在单子类型中,另一个用于将输出类型为单子类型的函数组合在一起(这些函数被称为单子函数)。

通用程序语言使用单子来减少常见操作所需的样板代码(例如处理未定义的值或易出错的函数,或封装繁琐的代码)。函数式语言使用单子将复杂的函数序列转换为简洁的管道,以将控制流和副作用抽象化。

在 Haskell 中,上述定义可以用两个函数表示如下:

return :: a -> M a 
bind :: (M a) -> (a -> M b) -> (M b)


第一个函数是一个将值包装在单子中的包装函数;

第二个函数是一个将函数应用于值并将结果包装在单子中的函数。

在本文中,我们遵循 Python 命名约定,使用 __init__() 作为包装函数的名称,flat_map() 作为绑定函数的名称。

单子满足以下定律:

左单位元

对使用 flat_map 函数将由恒等函数提升的值 x 应用函数 f 等效于直接将函数 f 应用于值 x

右单位元

使用恒等函数作为函数 fflat_map 函数结果为原始单子值:

x.flat_map(y => __init__(y)) = x


结合律

使用序列的 flat_map 调用将两个函数 fg 应用于单子值等效于使用 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


Python 的 Iterable Monad

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 定律遵从

我们证明我们的可迭代单子遵守 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)

[2] https://dev.to/hamzzak/mastering-monad-design-patterns-simplify-your-python-code-and-boost-efficiency-kal

推荐阅读: 剑指offer 65. 不用加减乘除做加法

本文链接: Pythonic编码通过函数式风格: Iterable Monad