【问题标题】:Is Traversable different for breadth-first and depth-first trees?广度优先树和深度优先树的 Traversable 是否不同?
【发布时间】:2019-06-09 23:57:05
【问题描述】:

我有一个玫瑰树结构,我想为它写一个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)

我做了它的深度优先变体:

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

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

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) = ???

我意识到Traversable 的广度和深度优先变体应该是相同的。是这样吗?我不相信我实际上在任何地方都读过这篇文章,但遍历与元素的顺序无关?

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

【问题讨论】:

  • 你绝对可以为两者做不同顺序的遍历效果。虽然最后重建树可能有点棘手!不过,这绝对是可能的。

标签: haskell tree traversable


【解决方案1】:

Traversable 必须同意Foldable。具体来说,如果Monoid m,则Applicative (Const m),导致一致性法则foldMap f = getConst . traverse (Const . f)。因此BreadthDepth不可能共享TraversableTraversable 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)

这很麻烦,而且到处都是非整体,但应该可以正常工作。

【讨论】:

  • 我的意思是Traversable 对于DepthBreadth 的输出应该是一样的,不是吗?它只是以不同的顺序处理元素。
  • 绝对不是! traverse 将产生值的动作集合转换为产生值集合的动作。操作的执行顺序从Depth 更改为Breadth,因此它们产生的值可能会发生变化,或者它们执行的操作可能会发生变化。例如。 traverse printtraverse (const $ get &lt;* modify (+1))。见鬼:depth = getConst . traverse (Const . return) . Depthbreadth = getConst . traverse (Const . return) . Breadth
  • @Listerone 虽然您是正确的,如果您在与顺序无关的 Applicative(例如 Identity(r -&gt;))上使用 traverse 将是相同的。但总的来说,效果的顺序绝对重要。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-11-06
  • 2010-10-15
  • 1970-01-01
  • 2023-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多