在Haskell中使用镜头遍历和向Data.Tree添加元素

时间:2017-04-02 16:22:41

标签: haskell tree lens lenses

我开始使用镜头,直到现在我一直无法在我正在编写的代码库的具体部分中使用它们。我的目标是通过在现有的一个节点中添加一个新节点来更新玫瑰树结构,例如Data.Tree中的玫瑰树结构。为此,我认为用唯一的id识别每个节点是有意义的,所以它看起来像这样:

type MyTree = Tree Id
type Path = [Id]

addToTree :: MyTree -> MyTree -> Path -> MyTree
addToTree originalTree newNode path = undefined

函数addToTree必须通过跟踪id的路径遍历originalTree并在该级别添加newNode,返回整个更新的树。我没有遇到问题,但是我无法找到合适的镜头来执行操作。

这就是我到现在所拥有的:

import           Control.Lens
import           Data.Tree
import           Data.Tree.Lens

addToTree :: MyTree -> Path -> MyTree -> MyTree
addToTree tree path branch = tree & (traversalPath path) . branches %~ (branch:)

traversalPath :: (Foldable t, Applicative f, Contravariant f) => t Id -> (MyTree -> f MyTree) -> MyTree -> f MyTree
traversalPath = foldl (\acc id-> acc . childTraversal id) id

childTraversal :: (Indexable Int p, Applicative f) => Id -> p MyTree (f MyTree) -> MyTree -> f MyTree
childTraversal id = branches . traversed . withId id

withId :: (Choice p, Applicative f) => Id -> Optic' p f MyTree MyTree
withId id = filtered (\x -> rootLabel x == id)

但它无法编译:

• No instance for (Contravariant Identity)
    arising from a use of ‘traversalPath’
• In the first argument of ‘(.)’, namely ‘(traversalPath path)’
  In the first argument of ‘(%~)’, namely
    ‘(traversalPath path) . branches’
  In the second argument of ‘(&)’, namely
    ‘(traversalPath path) . branches %~ (branch :)’

谢谢!

1 个答案:

答案 0 :(得分:1)

这不是特别优雅,但应该做到这一点:

import Control.Lens
import Data.Monoid (Endo(..)) -- A tidier idiom for 'foldr (.) id'.
import Data.List.NonEmpty (NonEmpty(..)) -- You don't want an empty path.
import qualified Data.List.NonEmpty as N
import Data.Tree
import Data.Tree.Lens -- That's where I got 'branches' from.

addToTree :: Eq a => NonEmpty a -> Tree a -> Tree a -> Tree a
addToTree path newNode oldTree = head $ over pathForests (newNode :) [oldTree]
    where
    pathForests = appEndo $ foldMap (Endo . goDown) path 
    goDown x = traverse . filtered ((x ==) . rootLabel) . branches

(特别是,我从不喜欢使用head,即使在像这样的情况下它也可能会失败。请随意用你最喜欢的迂回替换它。)

演示:

GHCi> addToTree (1 :| []) (Node 2 []) (Node 1 [])
Node {rootLabel = 1, subForest = [Node {rootLabel = 2, subForest = []}]}
GHCi> addToTree (4 :| []) (Node 2 []) (Node 1 [])
Node {rootLabel = 1, subForest = []}
GHCi> addToTree (1 :| [5]) (Node 2 []) (Node 1 [Node 5 [], Node 6 []])
Node {rootLabel = 1, subForest = [Node {rootLabel = 5, subForest = [Node {rootLabel = 2, subForest = []}]},Node {rootLabel = 6, subForest = []}]}
GHCi> addToTree (1 :| [7]) (Node 2 []) (Node 1 [Node 5 [], Node 6 []])
Node {rootLabel = 1, subForest = [Node {rootLabel = 5, subForest = []},Node {rootLabel = 6, subForest = []}]}
GHCi> addToTree (1 :| [5,3]) (Node 2 []) (Node 1 [Node 5 [], Node 6 []])
Node {rootLabel = 1, subForest = [Node {rootLabel = 5, subForest = []},Node {rootLabel = 6, subForest = []}]}

请注意我们正在处理遍历,而不是透镜 - 不能保证或期望路径的目标存在或是唯一的。

这是一个更加风格化的变体,没有head并使用alaf来处理Endo包装。

addToTree :: Eq a => NonEmpty a -> Tree a -> Tree a -> Tree a
addToTree (desiredRoot :| path) newNode oldTree@(Node x ts)
    | x == desiredRoot = Node x (over pathForests (newNode :) ts)
    | otherwise = oldTree
    where
    pathForests = alaf Endo foldMap goDown path 
    goDown x = traverse . filtered ((x ==) . rootLabel) . branches