如何实现mapAccumRM?

时间:2015-05-22 23:08:29

标签: haskell

之前我问了一个类似的问题(how to implement mapAccumM?)。

我也需要从右侧折叠的那个(mapAccumR):

mapAccumRM :: (Monad m, Traversable t) 
           => (a -> b -> m (a, c)) -> a -> t b -> m (a, t c)

这是否有简单的实现?

2 个答案:

答案 0 :(得分:2)

可以考虑为有序遍历定义一个新类型类。让我们看看可以采取的一种方式。我们需要一点前奏:

{-# LANGUAGE Rank2Types, TypeFamilies #-}

import Control.Applicative
import Data.Foldable
import Data.Traversable
import Data.Tree

大多数Haskell数据类型可以被视为多项式仿函数的固定点;而它们固定点的仿函数是对数据结构“脊柱”的良好描述。我们将滥用这个想法来提供一种具体的方法来编码在遍历期间应该使用的排序。该类本身看起来像这样:

type Order t = forall f a. Applicative f => Spine t (f a) (f (t a)) -> f (t a)

class OrderedTraversable t where
    data Spine t :: * -> * -> *
    otraverse :: Applicative f => Order t -> (a -> f b) -> t a -> f (t b)

请注意,otraverse的类型看起来就像traverse的类型,但它现在需要一个额外的排序参数。从某种意义上说,排序参数是可变的;因为不同的数据类型在其结构中的不同位置具有不同数量的值/子,并且排序可能关心所有这些。 (特别感兴趣的是使用rank-2类型来阻止排序观察数据结构“太多”的技术:它不能使用关于Applicative或给定类型的给定实例的特殊事实要决定如何遍历脊椎的元素,只允许基于脊椎形状的决定。)让我们看一个简单的例子,列表:

instance OrderedTraversable [] where
    -- Cute hack: the normal presentation for the spine of a list uses both
    -- a `Cons` and a `Nil`; but parametricity says the only thing an
    -- `Order []` can do with a `Nil` is `pure []` anyway. So let's just
    -- bake that into `otraverse`.
    data Spine [] a r = Cons a r
    otraverse order f = go where
        go []     = pure []
        go (x:xs) = order (Cons (f x) (go xs))

Traversable对标准库中列表的实现进行比较(我已经自由扩展了foldr的定义,使其更符合上述代码):

instance Traversable [] where
    traverse f = go where
        go [] = pure []
        go (x:xs) = (:) <$> f x <*> go xs

正如您所看到的,主要区别在于我们已经抽象出用于组合f xgo xs的函数。我们可以使用“head-first”命令恢复标准Traversable实例。还有一个“倒数第一”的订单;这些基本上只是对列表有意义的两个订单。

headFirst, lastFirst :: Order []
headFirst (Cons fx fxs) = liftA2       (:)  fx fxs
lastFirst (Cons fx fxs) = liftA2 (flip (:)) fxs fx

在ghci中,我们现在可以看到它们的区别:

> runState (traverse (\_ -> modify (+1) >> get) "hello, world!") 0
([1,2,3,4,5,6,7,8,9,10,11,12,13],13)
> runState (otraverse headFirst (\_ -> modify (+1) >> get) "hello, world!") 0
([1,2,3,4,5,6,7,8,9,10,11,12,13],13)
> runState (otraverse lastFirst (\_ -> modify (+1) >> get) "hello, world!") 0
([13,12,11,10,9,8,7,6,5,4,3,2,1],13)

再举一个例子,这里是你如何将这个类用于玫瑰树:

instance OrderedTraversable Tree where
    data Spine Tree a r = SNode a [r]
    otraverse order f = go where
        go (Node x ts) = order (SNode (f x) (map go ts))

-- two example orders for trees
prefix, postfix :: Order [] -> Order Tree
prefix  list (SNode fx fts) = liftA2       Node  fx (otraverse list id fts)
postfix list (SNode fx fts) = liftA2 (flip Node) (otraverse list id fts) fx

请注意,玫瑰树实际上有很多“好”的排序函数;上面列出了两个特别可能是你想要的东西。

答案 1 :(得分:2)

一种方法是定义使用您喜欢的顺序的Traversable的新实例。例如,对于列表,可以简单地定义一个新类型:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

import Control.Applicative
import Data.Traversable

newtype BackwardsList a = BackwardsList [a]
    deriving (Eq, Ord, Read, Show, Functor, Foldable)

instance Traversable BackwardsList where
    traverse f (BackwardsList xs) = BackwardsList <$> go xs where
        go [] = pure []
        go (x:xs) = liftA2 (flip (:)) (go xs) (f x)

在ghci中,我们可以看到它与标准实例之间的区别:

> runState (traverse (\_ -> modify (+1) >> get) "hello, world!") 0
([1,2,3,4,5,6,7,8,9,10,11,12,13],13)
> runState (traverse (\_ -> modify (+1) >> get) (BackwardsList "hello, world!")) 0
(BackwardsList [13,12,11,10,9,8,7,6,5,4,3,2,1],13)

这种方法相当简单;但是,对于您感兴趣的每个新遍历顺序,它都需要一个新类型(以及相关的newtype包装/展开垃圾)。