【问题标题】:Is it impossible to get the depth of elements inside a Traversable?是否不可能获得 Traversable 中元素的深度?
【发布时间】:2019-09-26 00:18:35
【问题描述】:

假设我有这个Tree 类型:

{-# LANGUAGE DeriveFoldable, DeriveFunctor #-}

data Tree a = Leaf | Branch (Tree a) a (Tree a) deriving(Functor,Foldable)

instance Traversable Tree where -- equivalent to the one I could derive, but written out for clarity
  traverse _ Leaf = pure Leaf
  traverse f (Branch l x r) = Branch <$> traverse f l <*> f x <*> traverse f r

编写一个函数来计算特定类型内事物的最大深度很容易:

depth Leaf = 0
depth (Branch l _ r) = 1 + max (depth l) (depth r)

但是要计算任意Traversable 内事物的最大深度并不容易。我已经知道仅仅Functor 是不够的,因为你没有通过fmap 获得关于它们内部事物“位置”的信息,而且我也已经知道仅仅Foldable 是不够的这是因为foldrfoldMap 都只提供与列表一样多的结构。不过Traversable 可能是,因为它比FunctorFoldable 都更通用。

但是,在做了一些实验之后,我认为Traversable 也没有办法做到这一点。到目前为止,这是我的逻辑。考虑这两棵树:

fooTree = Branch (Branch Leaf () Leaf) () (Branch Leaf () Leaf)
barTree = Branch (Branch Leaf () (Branch Leaf () Leaf)) () Leaf

现在,traverse (\() -&gt; thingy) fooTree 是:

Branch <$> (Branch <$> pure Leaf <*> thingy <*> pure Leaf) <*> thingy <*> (Branch <$> pure Leaf <*> thingy <*> pure Leaf)

在大量使用应用法则和一些简化之后,就变成了:

(\x y z -> Branch (Branch Leaf x Leaf) y (Branch Leaf z Leaf)) <$> thingy <*> thingy <*> thingy

同样,traverse (\() -&gt; thingy) barTree 是:

Branch <$> (Branch <$> pure Leaf <*> thingy <*> (Branch <$> pure Leaf <*> thingy <*> pure Leaf)) <*> thingy <*> pure Leaf

在大量使用应用法则和一些简化之后,就变成了:

(\x y z -> Branch (Branch Leaf x (Branch Leaf y Leaf)) z Leaf) <$> thingy <*> thingy <*> thingy

现在 traverse (\() -&gt; thingy) fooTreetraverse (\() -&gt; thingy) barTree 看起来它们具有相同的“形状”(唯一的区别是开头的 lambda,甚至它们的类型都相同),但它们来自具有不同的树深度。这让我相信不可能找到traverse 的深度,但我不是 100% 确定它,我不知道如何严谨地解释它。

我说这不可能吗?如果是这样,那么如何才能真正严格地解释这一点?如果没有,那你将如何实现它?

【问题讨论】:

  • 不可能。考虑深度优先和广度优先顺序都给出了有效的 Traversable 实现。
  • 另外,考虑你需要什么除了 Traversable,这至少是tree-traversals提供的一些,例如Control.Applicative.Phase

标签: haskell depth traversable


【解决方案1】:

这确实是不可能的,因为从FoldableTraversable 实际上并没有帮助。获取Trees 的深度需要合并一个分支下两个子树的信息。至于……

traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)

...关于,任何这样的合并只能通过结果的组合应用效果f来实现(合法的traverse必须保持t结构的形状,并且每个b值通过a -&gt; f b 函数从单个a 值获得)。不过,获得综合效果,is already possible through Foldable...

traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f ()

...所以Traversable 的额外力量在这里没有任何区别。

如果仅仅指向traverse_ 感觉不够清晰,这里有一种替代方式来呈现上述论证的最后一步。 traverse 的自然性属性之一是 Bird 等人称为“数据类型中的“自然性””的属性。在Understanding Idiomatic Traversals Backwards and Forwards(详见该论文的第 6 节):

-- r is a natural transformation that preserves toList:
-- toList = toList . r
fmap r . traverse f = traverse f . r

考虑一个任意的toList-保留树重排r :: Tree a -&gt; Tree a 和一些f,这样traverse f 的结果以某种方式编码了树的深度。因为,如上所述,只有组合效果对计算深度很重要,所以fmap (const ()) . traverse f 将与traverse f 一样对深度进行编码。现在,让我们取 naturality 属性,在两边写fmap (const ())

fmap (const ()) . fmap r . traverse f = fmap (const ()) . traverse f . r
-- Or simply:
fmap (const ()) . traverse f = fmap (const ()) . traverse f . r

由于fmap (const ()) . traverse f 对深度进行编码,这意味着r,无论它是什么,都不会改变树的深度。然而,情况并非如此,例如,如下反例所示:

-- Makes a tree with only leaves as left subtrees, preserving traversal order.
-- Assuming a toList that matches your traverse, or the derived one. 
straighten :: Tree a -> Tree a
straighten = foldr dangle Leaf . toList
    where
    dangle x t = Branch Leaf x t

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-31
    • 1970-01-01
    • 2011-08-26
    • 1970-01-01
    • 2012-11-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多