Python中Haskell的“跨度”的延迟递归
Posted
技术标签:
【中文标题】Python中Haskell的“跨度”的延迟递归【英文标题】:Lazy recursion for Haskell's `span` in Python 【发布时间】:2021-12-06 21:45:23 【问题描述】:有人可以建议 Haskell span
函数的 Python 实现吗?
span :: (a -> Bool) -> [a] -> ([a],[a])
span p [] = ([],[])
span p (x:xs) = if p x then (x:ys,zs)
else ([],x:xs)
where (ys,zs) = span p xs
我希望它在每个参数中都是惰性的,所以是两个生成器的列表。如果它是递归实现的,我更喜欢它,以了解在列表尾部递归的 Python 定义。如果合适的话,也许是yield from
。
【问题讨论】:
除了 amalloy 的答案中提出的优秀观点之外,还值得注意的是 Python 和 Haskell 中的列表非常不同。 Haskell 列表是链表,非常适合递归并且非常适合 Haskell。 Python 列表实际上只是顺序数组(类似于 C++ 中的std::vector
或 Java 中的 ArrayList
),因此如果不每次都复制几乎整个列表,就无法真正获取列表的“尾部”。 Python 程序员很少使用链表,因为它对范式毫无意义。
基于数组的列表和惰性不能很好地结合使用,因为这样的列表不是递归数据结构并且本质上是严格的。您需要一个命令式构造(生成器)才能懒惰地构造它。
【参考方案1】:
我希望它在每个参数中都是惰性的,所以是两个生成器的列表。
你真的不能这样做。生成器和惰性列表之间有很大的区别:后者被缓存,因此您可以重用它的值。在这种情况下,考虑 Haskell 实现,其中(xs, ys)
是span f zs
的结果。当您第一次查看xs
时,您需要浏览该列表,这会触发对span f zs
的更多评估...最后,在xs
和ys
的末尾会发现更多内容。您不能使用生成器来执行此操作,因为从其中一个读取会影响另一个。
我在回复Lazy partition-by 时对此进行了更多讨论。
【讨论】:
我猜 OP 可以创建一个Thunk
类型,它在评估时共享其结果(例如,通过破坏性更新)。 Thunk
可以实现 applicative/monad 以使显式 thunk 调用再次隐式。以上是关于Python中Haskell的“跨度”的延迟递归的主要内容,如果未能解决你的问题,请参考以下文章