既是恒定空间又是短路的折叠
Posted
技术标签:
【中文标题】既是恒定空间又是短路的折叠【英文标题】:Fold that's both constant-space and short-circuiting 【发布时间】:2019-08-05 20:17:28 【问题描述】:我正在尝试构建一个与 Prelude 的 product
基本相同的 Haskell 函数。然而,与那个函数不同的是,它应该具有以下两个属性:
-
它应该在常量空间中运行(忽略一些像
Integer
这样的数字类型不是这样的事实)。例如,我希望 myProduct (replicate 100000000 1)
最终返回 1,这与 Prelude 的 product
不同,后者会用完我所有的 RAM,然后给出 *** Exception: stack overflow
。
它遇到 0 时应该短路。例如,我希望 myProduct (0:undefined)
返回 0,这与 Prelude 的 product
不同,它提供 *** Exception: Prelude.undefined
。
这是我到目前为止的想法:
myProduct :: (Eq n, Num n) => [n] -> n
myProduct = go 1
where go acc (x:xs) = if x == 0 then 0 else acc `seq` go (acc * x) xs
go acc [] = acc
这正是我希望它用于列表的方式,但我想将其概括为具有(Foldable t, Eq n, Num n) => t n -> n
类型。是否可以使用任何折叠来做到这一点?如果我只使用foldr
,那么它会短路但不会是常数空间,如果我只是使用foldl'
,那么它将是常数空间但不会短路。
【问题讨论】:
顺便说一句:product
在编译的程序中不会堆栈溢出。
【参考方案1】:
如果您的函数拼写稍有不同,如何将其转换为foldr
会更明显。即:
myProduct :: (Eq n, Num n) => [n] -> n
myProduct = flip go 1 where
go (x:xs) = if x == 0 then \acc -> 0 else \acc -> acc `seq` go xs (acc * x)
go [] = \acc -> acc
现在go
有了foldr
的味道,我们可以填补这些漏洞。
myProduct :: (Foldable t, Eq n, Num n) => t n -> n
myProduct = flip go 1 where
go = foldr
(\x f -> if x == 0 then \acc -> 0 else \acc -> acc `seq` f (acc * x))
(\acc -> acc)
希望您能看到这些片段在之前的显式递归样式中的出处以及转换的机械性。然后我会做一些美学上的调整:
myProduct :: (Foldable t, Eq n, Num n) => t n -> n
myProduct xs = foldr step id xs 1 where
step 0 f acc = 0
step x f acc = f $! acc * x
我们都完成了!在 ghci 中进行的一些快速测试表明,它仍然根据需要在 0
上短路,并且在专门用于列表时使用常量空间。
【讨论】:
我以前见过“foldr 来构建一个你可以立即调用的函数”的模式,但这是第一次明确它是如何/为什么工作的。谢谢!【参考方案2】:您可能正在寻找foldM
。使用m = Either b
对其进行实例化,您将获得短路行为(或Maybe
,取决于您是否有许多可能的提前退出值,或者一个预先已知的值)。
foldM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
我记得是否应该有 foldM'
的讨论,但 IIRC GHC 大部分时间都在做正确的事情。
import Control.Monad
import Data.Maybe
myProduct :: (Foldable t, Eq n, Num n) => t n -> n
myProduct = fromMaybe 0 . foldM go 1
where go acc x = if x == 0 then Nothing else Just $! acc * x
【讨论】:
我刚刚注意到的一件很酷的事情:如果你接受这个答案,内联foldM
的定义,并简化,结果与丹尼尔瓦格纳的答案中的最终结果相同。以上是关于既是恒定空间又是短路的折叠的主要内容,如果未能解决你的问题,请参考以下文章