[英]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 :: B
或lookup 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
但这可能是多余的。(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.这可以作为对
componentMap
和compositeMap
的递归搜索来实现。只要它们是手工填充的。
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
).下面,我假设您只想检索和/或遍历遇到的“第一个”(例如,仅
A3
的D
,忽略A4
的一个)。
Anyway, you can do this with Data
generics and some helpers from lens
's Data.Data.Lens
.无论如何,您可以使用
Data
泛型和来自lens
的Data.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
:要按键查找(可能是深度嵌套的)值,我们可以使用来自
lens
的biplate
遍历:
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.