关于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
我注意到Traversable
约束可以减弱为Foldable
:
import Data.Monoid (Ap (..)) -- Requires a recent base version
ft :: (Applicative f, Monoid m, Foldable t)
-> (b -> m) -> (a -> f b) -> t a -> f m
ft f g = getAp . foldMap (Ap . fmap f . g)
在原始提案中,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)
因此,现在我们将所有昂贵的fmap
替换为一个(埋在lowerCoyoneda
中)。问题解决了?不完全是。
Coyoneda
的问题在于它的liftA2
是 strict 。所以如果我们写类似
import Data.Monoid (First (..))
ft' (First . Just) Identity $ 1 : undefined
-- or, importing Data.Functor.Reverse,
ft' (Last . Just) Identity (Reverse $ 1 : undefined)
然后它将失败,而ft
对此毫无问题。有办法吃蛋糕吗?也就是说,仅使用Foldable
约束的版本是fmap
函子中traverse
的O(1)的O(1)倍,是{{1} f
?
注意:我们可以用ft
为liftA2
做Coyoneda
有点懒惰:
liftA2 f m n = liftCoyoneda $
case (m, n) of
(Coyoneda g x, Coyoneda h y) -> liftA2 (\p q -> f (g p) (h q)) x y
这足以使它产生对ft' (First . Just) Identity $ 1 : 2 : undefined
的回答,但对ft' (First . Just) Identity $ 1 : undefined
却没有回答。我看不出有什么比这更懒的明显方法,因为对存在物进行模式匹配必须始终严格。
答案 0 :(得分:1)
我不认为有可能。避免在元素上使用fmap
似乎需要对容器的结构有所了解。例如,可以编写列表的Traversable
实例
traverse f (x : xs) = liftA2 (:) (f x) (traverse f xs)
我们知道(:)
的第一个参数是单个元素,因此我们可以使用liftA2
将对该元素的操作进行映射的过程与对该元素的结果进行合并的过程结合起来操作,并将结果与列表的其余部分相关联。
在更一般的上下文中,可以使用带有伪造Monoid
实例的岩浆类型来忠实地捕获褶皱的结构:
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
但是Traversable
实例中有麻烦:
traverse f (Leaf x) = Leaf <$> f x
这正是我们要避免的麻烦。而且没有懒惰的解决方案。如果遇到Bin l r
,我们将懒惰地确定l
或r
是树叶。所以我们被困住了。如果我们对Traversable
施加ft''
约束,则可以捕获具有更丰富的岩浆类型(例如lens
中使用的岩浆类型)的遍历结果,我怀疑可以让我们做些更聪明的事情,尽管我还没有发现任何东西。