简体   繁体   English

具有组合查找和更新的地图记录?

[英]Record of maps with compositional lookups and updates?

Some pseudocode:一些伪代码:

data A = A
data B = B
data C = C
data D = D
data E = E
data F = F
data G = G

data A1 = A1 A B C
data A2 = A2 A
data A3 = A3 B C D
data A4 = A4 D E F
data A5 = A5 A1 A4 G

data Foo k = Foo
    {
        a1s :: Map.Map k A1,
        a2s :: Map.Map k A2,
        a3s :: Map.Map k A3,
        a4s :: Map.Map k A4,
        a5s :: Map.Map k A5,
--and my attempted solution would use
        -- e.g. [(A1, [(A, Unit), (B, Unit), (C, Unit)]), (A5, [(A1, Composite), (A4, Composite), (G, Unit) ]) ]
        componentMap :: Map.Map Type (Set Type),

        -- e.g. [(A, [A1, A2]), (A1, [A5, A1]) ]
        compositeMap :: Map.Map Type (Set Type)
    }

I'd like to construct some kind of data structure that looks like this.我想构建某种看起来像这样的数据结构。 From here, I'd like to:从这里,我想:

  • lookup :: Foo k -> k -> Either FailureReason v individual values; lookup :: Foo k -> k -> Either FailureReason v单个值; if we assume that we have populated maps, I'd like lookup foo a1 :: A1 , but also transitive instances such as lookup foo a1 :: B or lookup foo a5 :: A1 (since this is shorthand for getA1fromA5 $ lookup foo a5 ) and lookup foo a5 :: B .如果我们假设我们已经填充了地图,我想要lookup foo a1 :: A1 ,但也希望传递实例,例如lookup foo a1 :: Blookup foo a5 :: A1 (因为这是getA1fromA5 $ lookup foo a5简写) 并lookup foo a5 :: B I'm considering FailureReason = WrongType | NotPresent我正在考虑FailureReason = WrongType | NotPresent FailureReason = WrongType | NotPresent but this is probably excessive. FailureReason = WrongType | NotPresent但这可能是多余的。
  • traversals over types such as an (indexed) traversal over (k, D) which should hit everything in A3, A4, A5遍历类型,例如(索引)遍历(k, D)应该命中A3, A4, A5

This could be implemented as a recursive search over componentMap and compositeMap ..so long as they were populated by hand.这可以作为对componentMapcompositeMap的递归搜索来实现。只要它们是手工填充的。

Since the above seem very much recursive, I feel this has a GHC.Generics solution.由于上面看起来非常递归,我觉得这有一个GHC.Generics解决方案。 Possibly a lens/optics + generic-lens/generic-optics one?可能是lens/optics + generic-lens/generic-optics

Or is my solution one that doesn't need generics and its ilk, and is instead just writing some traversals and lenses to index into my structure?还是我的解决方案不需要generics及其同类,而只是编写一些遍历和镜头来索引到我的结构中?

The question then becomes: is this functionality already existing in some library?那么问题就变成了:这个功能是否已经存在于某个库中? If not, is Generics the tool I'm looking for to implement it?如果没有, Generics是我正在寻找实现它的工具吗?

I'm assuming you don't actually want multiple maps here -- that is, a given key should correspond to exactly one value, not an A1 value from the a1s map and another A2 value from from the a2s map, etc.我假设你实际上并不想在这里多张地图-那就是,一个给定的键应该对应一个值,而不是A1从价值a1s地图和另一A2从价值a2s地图等。

Also, you haven't said what you want to do if there are multiple matches of a particular type within in a single value, for example if you have values of type:此外,如果单个值中有多个特定类型的匹配项,您还没有说要做什么,例如,如果您有以下类型的值:

data A6 = A6 A3 A4

and try to retrieve or traverse terms of type D .并尝试检索或遍历类型D术语。 Below, I assume you want to retrieve and/or traverse only the "first" one encountered (eg, the D in A3 only, ignoring the one in A4 ).下面,我假设您只想检索和/或遍历遇到的“第一个”(例如,仅A3D ,忽略A4的一个)。

Anyway, you can do this with Data generics and some helpers from lens 's Data.Data.Lens .无论如何,您可以使用Data泛型和来自lensData.Data.Lens一些助手来做到这Data.Data.Lens

No special data type is needed.不需要特殊的数据类型。 A plain Map is sufficient, with a sum type to represent the collection of values you want to store:一个普通的Map就足够了,用 sum 类型来表示要存储的值的集合:

data Dat = D_A1 A1 | D_A2 A2 | D_A3 A3 | D_A4 A4 | D_A5 A5 deriving (Data)
type Foo k dat = Map k dat

To look up a (possibly deeply nested) value by key, we can use the biplate traversal from lens :要按键查找(可能是深度嵌套的)值,我们可以使用来自lensbiplate遍历:

lookupFoo :: (Ord k, Typeable v, Data dat) => k -> Foo k dat -> Maybe v
lookupFoo k foo = do
  dat <- Map.lookup k foo
  firstOf biplate dat

Here, biplate recursively traverses all the subterms of type v in the term dat .在这里, biplate递归遍历所有类型的subterms v在长期dat The firstOf query returns the first matching term or Nothing if no terms are found. firstOf查询返回第一个匹配的术语,如果没有找到术语,则返回Nothing (The do block is running in the Maybe monad.) do块在Maybe monad 中运行。)

To perform an indexed traversal, we can also use biplate , modified using taking 1 to traverse only the first match:要执行索引遍历,我们还可以使用biplate ,修改为使用taking 1只遍历第一个匹配项:

itraverseFoo :: (Applicative f, Typeable v, Data dat) => (k -> v -> f v) -> Foo k dat -> f (Foo k dat)
itraverseFoo f foo = Map.traverseWithKey f' foo
  where f' k dat = taking 1 biplate (f k) dat

The full code:完整代码:

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ExplicitForAll #-}

import Control.Lens
import Control.Monad.Writer
import Data.Data
import Data.Data.Lens
import Data.Map (Map)
import qualified Data.Map as Map

data Dat = D_A1 A1 | D_A2 A2 | D_A3 A3 | D_A4 A4 | D_A5 A5 | D_A6 A6 deriving (Data)
type Foo k dat = Map k dat

lookupFoo :: (Ord k, Typeable v, Data dat) => k -> Foo k dat -> Maybe v
lookupFoo k foo = do
  dat <- Map.lookup k foo
  firstOf biplate dat

itraverseFoo :: (Applicative f, Typeable v, Data dat) => (k -> v -> f v) -> Foo k dat -> f (Foo k dat)
itraverseFoo f foo = Map.traverseWithKey f' foo
  where f' k dat = taking 1 biplate (f k) dat

data A = A deriving (Data, Show)
data B = B deriving (Data, Show)
data C = C deriving (Data, Show)
data D = D deriving (Data, Show)
data E = E deriving (Data, Show)
data F = F deriving (Data, Show)
data G = G deriving (Data, Show)

data A1 = A1 A B C deriving (Data, Show)
data A2 = A2 A deriving (Data, Show)
data A3 = A3 B C D deriving (Data, Show)
data A4 = A4 D E F deriving (Data, Show)
data A5 = A5 A1 A4 G deriving (Data, Show)
data A6 = A6 A3 A4 deriving (Data, Show)

foo :: Foo String Dat
foo = Map.fromList [ ("a1", D_A1 (A1 A B C))
                   , ("a3", D_A3 (A3 B C D))
                   , ("a4", D_A4 (A4 D E F))
                   , ("a5", D_A5 (A5 (A1 A B C) (A4 D E F) G))
                   , ("a6", D_A6 (A6 (A3 B C D) (A4 D E F)))
                   ]

find :: forall a k. k -> a -> Writer [k] a
find k a = tell [k] >> pure a

main = do
  print $ (lookupFoo "a1" foo :: Maybe A1)
  print $ (lookupFoo "a1" foo :: Maybe B)
  print $ (lookupFoo "a5" foo :: Maybe A1)
  print $ (lookupFoo "a5" foo :: Maybe B)
  print $ execWriter (itraverseFoo (find @D) foo)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM