宽度优先和深度优先的树的遍历是否不同?

Lis*_*one 2 tree haskell traversable

我有一个玫瑰树结构,我想为其编写一个Traversable实例。因此,我从以下内容开始:

data Tree a = Tree a [Tree a] deriving (Show)

instance Functor Tree where
  fmap f (Tree x subs) = Tree (f x) (fmap (fmap f) subs)
Run Code Online (Sandbox Code Playgroud)

我做了深度优先的变体:

newtype Depth a = Depth (Tree a) deriving (Show)

depth :: Tree a -> [a]
depth (Tree x subs) = x : concatMap depth subs

instance Functor Depth where
  fmap f (Depth t) = Depth $ fmap f t

instance Foldable Depth where
  foldMap f (Depth t) = mconcat $ f <$> depth t

instance Traversable Depth where
  traverse f (Depth t) = Depth <$> go t
    where go (Tree x subs) = Tree <$> f x <*> traverse go subs
Run Code Online (Sandbox Code Playgroud)

然后,我尝试了广度优先的变体:

newtype Breadth a = Breadth (Tree a) deriving (Show)

breadth :: Tree a -> [a]
breadth tree = go [tree]
  where
    go [] = []
    go (Tree x subs:q) = x : go (q <> subs)

instance Functor Breadth where
  fmap f (Breadth t) = Breadth $ fmap f t

instance Foldable Breadth where
  foldMap f (Breadth t) = mconcat $ f <$> breadth t

instance Traversable Breadth where
  traverse f (Breadth t) = ???
Run Code Online (Sandbox Code Playgroud)

而且我意识到,此方法的广度和深度优先变体Traversable应相同。是这样吗 我不相信我实际上已经在任何地方阅读过了,但是遍历与元素的顺序无关吗?

如果是这样,这会有点奇怪,因为Traversable可以直接为实现Tree,这意味着Foldable需要为实现Tree,但是显然有多种方法Foldable可以实现。

HTN*_*TNW 5

Traversable必须同意Foldable。特别是,如果Monoid m的话Applicative (Const m),引起了一致性规律foldMap f = getConst . traverse (Const . f)。因此不可能BreadthDepth共享一个Traversable。要么有一个不同的实现Traversable Breadth与其一致Foldable,要么根本没有。我可以制定一个我认为确实可以实现的实现,但是我还没有验证其他法律。

instance Traversable Breadth where
  traverse f (Breadth t) = Breadth <$> head <$> go [t]
    where
      go [] = pure []
      go ts = zipWith Tree <$> traverse f rs
                           <*> (fmap (rebuild css) $ go $ concat css)
        where
          (rs, css) = unzip $ map (\(Tree r cs) -> (r, cs)) ts
          -- rebuild s d = evalState (traverse (state splitAt') d) s
          -- I think, but let's keep the dependencies down, shall we?
          rebuild [] [] = []
          rebuild (struct : structs) destruct
            = let (cs, destruct') = splitAt' struct destruct
              in  cs : rebuild structs destruct'
          -- ignoring the as in a [a] makes it look like a number
          splitAt' [] xs = ([], xs)
          splitAt' (_ : n) (x : xs)
            = let (pre, suf) = splitAt' n xs
              in  (x : pre, suf)
Run Code Online (Sandbox Code Playgroud)

这是多毛的,到处都是非总计,但应该可以解决。

  • 绝对不!“遍历”将产生值的动作集合转换为产生值的动作集合。执行动作的顺序从“深度”更改为“宽度”,因此它们产生的值可能会更改,或者它们执行的动作可能会更改。例如`traverse print`和`traverse(const $ get &lt;* Modify(+1))`。哎呀:`depth = getConst。遍历(常量返回)。Depth`和`breadth = getConst。遍历(常量返回)。宽度` (4认同)
  • @Listerone,尽管您是正确的,但如果在与顺序无关的Applicative(例如,Identity)或((r-&gt;))上使用“遍历”,则遍历将是相同的。但总的来说,效果的顺序绝对重要。 (2认同)