折叠既是恒定间距又是短路

时间:2019-03-14 19:51:58

标签: haskell fold space-complexity short-circuiting

我正在尝试构建一个与Prelude的product基本相同的Haskell函数。但是,与该函数不同,它应具有以下两个属性:

  1. 它应该在恒定的空间中运行(忽略诸如Integer之类的某些数字类型不是这样的事实)。例如,我希望myProduct (replicate 100000000 1)最终返回1,这与Prelude的product占用了我所有的RAM,然后给出*** Exception: stack overflow一样。
  2. 遇到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',它将是恒定空间但不会短路

2 个答案:

答案 0 :(得分:3)

如果您对函数的拼写稍有不同,则将其转换为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上短路,并使用恒定的空间。

答案 1 :(得分: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