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

标签 haskell functional-programming haskell-lens

假设我有一个代表袋子的数据结构,它可以容纳多个元素。用户可以将另一个容纳袋放入该袋子中,并且该袋子可以包含其他袋子,或者甚至可以包含袋子的袋子。是否有一个镜头可以在功能上更新任意嵌套的包,例如从袋子内的袋子内的袋子内的袋子内取出元素 foo ?请注意,嵌套级别以及树的总深度是动态的,在编译时未知。其他问题如thisthis似乎只处理静态已知的嵌套级别。

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

最佳答案

您对“持有袋”的描述并不准确,但我认为这与您的意思很接近。基本思想是使用 [Int] 遍历到 child 包中。 (类似于 IxedTree 实例)并使用 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] :

> 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')]) []]]

或将“foobar”插入包[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

这可以像之前一样使用expect use subBag而不是ixsubBag的定义这样写可能更清楚。

事实上,您不需要编写任何新函数,因为 Ixed Tree 的实例与 subBag is . root 相同,因此可以通过以下方式进行编辑:

> mybag & ix [1,1] . at "FOO" .~ Nothing

关于haskell - 使用镜头功能更新任意嵌套的数据结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29999433/

相关文章:

haskell - 为什么类型同义词中的类约束需要 RankNTypes

Haskell: (+1) 和 (\x->x+1) 有什么区别?

Scala 使用模式匹配获取列表的第一个和最后一个元素

haskell - 如何解决无法使用存在类型的镜头?

Haskell 中任何顺序或维度的列表理解

haskell - 停止/Pickling/Unpickling/恢复计算

scala - 在 Spark GraphX 中实现拓扑排序

functional-programming - 函数式编程: Declarative vs imperative

json - 具有错误处理功能的 Aeson 和 Lenses

haskell - 如何为 sum 类型编写镜头