有没有一种有效、懒惰的方法可以将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