有没有一种有效、懒惰的方法可以将foldMap与traverse融合在一起? 最近关于Haskell图书馆邮件列表的建议使我考虑如下: ft :: (Applicative f, Monoid m, Traversable t) -> (b -> m) -> (a -> f b) -> t a -> f m ft f g xs = foldMap f <$> traverse g xs

有没有一种有效、懒惰的方法可以将foldMap与traverse融合在一起? 最近关于Haskell图书馆邮件列表的建议使我考虑如下: ft :: (Applicative f, Monoid m, Traversable t) -> (b -> m) -> (a -> f b) -> t a -> f m ft f g xs = foldMap f <$> traverse g xs,haskell,traversal,fold,Haskell,Traversal,Fold,在最初的提案中,f应该是id,导致 foldMapA :: (Applicative f, Monoid m, Foldable t) -> (a -> f m) -> t a -> f m --foldMapA g = getAp . foldMap (Ap . fmap id . g) foldMapA g = getAp . foldMap (Ap . g) 严格来说,这比先横向然后折叠的方法要好 但是在更一般的ft中,存在一个潜在的问题:fmap在f函

在最初的提案中,
f
应该是
id
,导致

foldMapA
  :: (Applicative f, Monoid m, Foldable t)
   -> (a -> f m) -> t a -> f m
--foldMapA g = getAp . foldMap (Ap . fmap id . g)
foldMapA g = getAp . foldMap (Ap . g)
严格来说,这比先横向然后折叠的方法要好

但是在更一般的
ft
中,存在一个潜在的问题:
fmap
f
函子中可能很昂贵,在这种情况下,融合版本可能比原始版本更昂贵

处理昂贵的
fmap
的常用工具是
Yoneda
Coyoneda
。由于我们需要多次提升,只需降低一次,
Coyoneda
是能够帮助我们:

import Data.Functor.Coyoneda

ft' :: (Applicative f, Monoid m, Foldable t)
    => (b -> m) -> (a -> f b) -> t a -> f m
ft' f g = lowerCoyoneda . getAp
  . foldMap (Ap . fmap f . liftCoyoneda . g)
因此,现在我们用一个(埋在
lowerCoyoneda
中)替换所有那些昂贵的
fmap
s。问题解决了吗?不完全是

Coyoneda的问题在于它的
liftA2
很严格。所以如果我们写一些像

import Data.Monoid (First (..))

ft' (First . Just) Identity $ 1 : undefined
-- or, importing Data.Functor.Reverse,
ft' (Last . Just) Identity (Reverse $ 1 : undefined)
然后它将失败,而
ft
在这些方面没有问题。有没有办法既吃蛋糕又吃蛋糕?也就是说,一个仅使用
可折叠
约束的版本,仅
fmap
so(1)倍于
f
函子中的
遍历
,并且与
ft
一样懒惰


注意:对于
Coyoneda
我们可以让
liftA2
稍微懒一点:


这足以让它生成对
ft'(First.Just)标识$1:2:undefined
,但对
ft'(First.Just)标识$1:undefined
的答案。我看不到任何明显的方法可以让它比这更懒惰,因为存在主义的模式匹配必须总是严格的。

我认为这是不可能的。在元素处避免
fmap
s似乎需要一些容器结构的知识。例如,可以编写列表的
Traversable
实例

traverse f (x : xs) = liftA2 (:) (f x) (traverse f xs)
我们知道
(:)
的第一个参数是单个元素,因此我们可以使用
liftA2
将映射该元素的操作的过程与将该操作的结果与与与列表其余部分关联的结果相结合的过程相结合

在更一般的上下文中,可以使用带有伪
幺半群的岩浆类型忠实地捕捉褶皱结构

data Magma a = Bin (Magma a) (Magma a) | Leaf a | Nil
  deriving (Functor, Foldable, Traversable)

instance Semigroup (Magma a) where
  (<>) = Bin
instance Monoid (Magma a) where
  mempty = Nil

toMagma :: Foldable t => t a -> Magma a
toMagma = foldMap Leaf
traverse f (Leaf x) = Leaf <$> f x
这正是我们试图避免的那种麻烦。而且没有懒惰的解决办法。如果我们遇到
Bin l r
,我们就不能懒洋洋地判断
l
r
是叶子。所以我们被困住了。如果我们允许对
ft''
进行
Traversable
约束,我们可以用更丰富的岩浆类型(例如
lens
中使用的岩浆类型)捕获穿越的结果,我怀疑这可以让我们做一些更聪明的事情,尽管我还没有发现任何东西

data Magma a = Bin (Magma a) (Magma a) | Leaf a | Nil
  deriving (Functor, Foldable, Traversable)

instance Semigroup (Magma a) where
  (<>) = Bin
instance Monoid (Magma a) where
  mempty = Nil

toMagma :: Foldable t => t a -> Magma a
toMagma = foldMap Leaf
ft'' :: (Applicative f, Monoid m, Foldable t)
   => (b -> m) -> (a -> f b) -> t a -> f m
ft'' f g = fmap (lowerMagma f) . traverse g . toMagma

lowerMagma :: Monoid m => (a -> m) -> Magma a -> m
lowerMagma f (Bin x y) = lowerMagma f x <> lowerMagma f y
lowerMagma f (Leaf x) = f x
lowerMagma _ Nil = mempty
traverse f (Leaf x) = Leaf <$> f x