使用镜头功能更新任意嵌套的数据结构

时间:2015-05-02 06:58:29

标签: haskell functional-programming lens

假设我有一个代表Bag of Holding的数据结构,它可以容纳多个项目。用户可以在这个袋子里放置另一个袋子,那个袋子可以包含其他袋子,甚至包含袋子的袋子。是否有用于功能性更新任意嵌套袋的镜片,例如从袋子里面的袋子里面的袋子里取出物品foo?请注意,嵌套级别以及树的总深度是动态的,在编译时不知道。其他问题如thisthis似乎只涉及静态已知的嵌套级别。

我正在寻找的东西可以在Clojure中使用update-in function,通过动态生成一个访问器向量来传递给该函数。

2 个答案:

答案 0 :(得分:1)

假设{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE FlexibleInstances #-} import Control.Lens import Control.Lens.Reified import Data.Monoid type Item = Int data Bag = Bag { _items :: [Item] , _bags :: [Bag] } deriving (Show) $(makeLenses ''Bag) exampleBag :: Bag exampleBag = Bag [1,2] [Bag [] [], Bag [] [Bag [3] [Bag [0] []]]] 数据类型如下:

ReifiedTraversal

Control.Lens.Reified中,Monoid newtype用于在容器中存储遍历。我们可以为那些以相同数据类型开头和结尾的遍历声明一个instance Monoid (ReifiedTraversal s s s s) where mempty = Traversal id mappend (Traversal t1) (Traversal t2) = Traversal (t1 . t2) 实例:

mappend

Bag只是遍历的组合(有点像Endo幺半群的工作方式。)

现在我们可以使用列表在运行时定义从BaglensList :: [ReifiedTraversal' Bag Bag] lensList = [ Traversal $ bags . ix 1 , Traversal $ bags . ix 0 , Traversal $ bags . ix 0 ] 的遍历:

main :: IO ()
main = print $ over ((runTraversal $ mconcat lensList) . items . ix 0) succ exampleBag

测试一下:

Bag

我们还可以为{{1}}定义Plated个实例,让我们可以执行列出层次结构中所有行李的操作,或者在行李上执行paramorphisms。如果你愿意,可以使用“bagamorphism”。

答案 1 :(得分:1)

您对“Bag of Holding”的描述并不精确,但我认为这与您的意思相近。基本想法是使用[Int](类似于Ixed的{​​{1}}实例)遍历子包并使用Tree实例At来实现编辑项目。

Map

现在,如果我们要删除包{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-} import Control.Lens import qualified Data.Map as M data Bag k a = Bag (M.Map k a) [Bag k a] deriving (Show) -- | Lens onto top level items of a bag. items :: Lens' (Bag k a) (M.Map k a) items f (Bag k a) = f k <&> \k' -> Bag k' a -- | Use 'At' instance for 'M.Map' to edit top level items. atItem :: Ord k => k -> Lens' (Bag k a) (Maybe a) atItem k = items . at k type instance Index (Bag k a) = [Int] type instance IxValue (Bag k a) = Bag k a instance Ixed (Bag k a) where ix is0 f = go is0 where -- Use the `Ixed` instance for lists to traverse over -- item `i` in the list of bags. go (i:is) (Bag m bs) = Bag m <$> ix i (go is) bs go _ b = f b {-# INLINE ix #-} mybag :: Bag String Char mybag = Bag [("a1",'a')] -- ix [] [ Bag [] [] -- ix [0] , Bag [] -- ix [1] [ Bag [("foo", 'x'), ("bar",'y')] [] -- ix [1,0] , Bag [("FOO", 'X'), ("BAR",'Y')] [] -- ix [1,1] ] ] 中的“FOO”项目:

[1,1]

或将“foobar”插入包> mybag & ix [1,1] . atItem "FOO" .~ Nothing Bag (fromList [("a1",'a')]) [Bag (fromList []) [] ,Bag (fromList []) [Bag (fromList [("bar",'y'),("foo",'x')]) [] ,Bag (fromList [("BAR",'Y')]) []]]

[1,0]

实际上我对> mybag & ix [1,0] . atItem "foobar" ?~ 'z' Bag (fromList [("a1",'a')]) [Bag (fromList []) [] ,Bag (fromList []) [Bag (fromList [("bar",'y'),("foo",'x'),("foobar",'z')]) [] ,Bag (fromList [("BAR",'Y'),("FOO",'X')]) []]] 的定义只是一个专门的Bag

Tree

这可以与使用import Data.Tree import Data.Tree.Lens type Bag k a = Tree (M.Map k a) atItem :: Ord k => k -> Lens' (Bag k a) (Maybe a) atItem k = root . at k subBag :: [Int] -> Traversal' (Bag k a) (Bag k a) subBag (i:is) = branches . ix i . subBag is subBag _ = id 代替subBag之前相同。 ix的定义可能更清晰。

实际上您不需要编写任何新函数,因为subBag的{​​{1}}实例与Ixed相同,因此可以通过以下方式完成编辑:

Tree