堆栈溢出在大型列表中的幺半折叠

bga*_*ari 5 stack-overflow haskell monoids

先是一些imports,

import Control.Applicative
import Data.Traversable as T
import Data.Foldable as F
import Data.Monoid
Run Code Online (Sandbox Code Playgroud)

假设我有一个拿着一对值的仿函数,

data Fret a = Fret a a deriving (Show)

instance Functor Fret where fmap f (Fret a b) = Fret (f a) (f b)

instance Applicative Fret where
    pure a = Fret a a
    Fret aa ab <*> Fret ba bb = Fret (aa ba) (ab bb)

instance Monoid a => Monoid (Fret a) where
    mempty = Fret mempty mempty
    a `mappend` b = mappend <$> a <*> b
Run Code Online (Sandbox Code Playgroud)

我有一大堆这些,

frets = replicate 10000000 (Fret 1 2)
Run Code Online (Sandbox Code Playgroud)

我想要计算一个,例如,平均,

data Average a = Average !Int !a deriving (Read, Show)

instance Num a => Monoid (Average a) where
    mempty = Average 0 0
    Average n a `mappend` Average m b = Average (n+m) (a+b)

runAverage :: Fractional a => Average a -> a
runAverage (Average n a) = a / fromIntegral n

average = Average 1
Run Code Online (Sandbox Code Playgroud)

以下是一些可能的实现,

average1 = runAverage <$> foldMap (fmap average) frets

average2 = pure (runAverage . mconcat) <*> T.sequenceA (map (pure (Average 1) <*>) frets)
Run Code Online (Sandbox Code Playgroud)

不幸的是,所有这些导致堆栈溢出.

考虑到问题可能是过度的懒惰Foldable.foldMap,我尝试实施更严格的变体,

foldMap' :: (F.Foldable f, Monoid m) => (a -> m) -> f a -> m
foldMap' f = F.foldl' (\m a->mappend m $! f a) mempty

average3 = runAverage <$> foldMap' (fmap average) frets
Run Code Online (Sandbox Code Playgroud)

不幸的是,这也溢出了.

如何在不损害方法清洁结构的情况下实现这一目标?

更新

如果我制作Fret严格的字段,事情似乎按预期工作.检查这是否适用于较大的应用程序.

Don*_*art 7

看起来foldMap太懒了,你的Fret数据类型肯定是,导致经典的foldl (+)类型空间泄漏,你积累了一大堆thunks试图将你的输入列表减少到它的平均值.它类似于带有元组的列表平均值中空间泄漏.

很明显,你唯一的循环中的累加器太懒了 - 你使用堆栈的唯一地方就是 foldMap

在此输入图像描述

使用相同的解决方案 - 一个严格的对类型Fretsfoldl'实现foldMap就足够了,它将在恒定的空间中运行:

 foldMap' f = F.foldl' (\m -> mappend m . f) mempty
Run Code Online (Sandbox Code Playgroud)

 data Fret a = Fret !a !a
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

  • 我认为,累加器的折叠应该是严格的.你可以在构造函数中添加惰性,但事后很难使它们变得严格.这些讨厌的操作细节并不总是引起一些图书馆的注意. (4认同)