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 vindividual values; if we assume that we have populated maps, I'd likelookup foo a1 :: A1, but also transitive instances such aslookup foo a1 :: Borlookup foo a5 :: A1(since this is shorthand forgetA1fromA5 $ lookup foo a5) andlookup foo a5 :: B. I'm consideringFailureReason = WrongType | NotPresentbut this is probably excessive.- traversals over types such as an (indexed) traversal over
(k, D)which should hit everything inA3, A4, A5
This could be implemented as a recursive search over componentMap and compositeMap..so long as they were populated by hand.
Since the above seem very much recursive, I feel this has a GHC.Generics solution. Possibly a lens/optics + generic-lens/generic-optics one?
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?
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?
I'm assuming you don't actually want multiple maps here -- that is, a given key should correspond to exactly one value, not an
A1value from thea1smap and anotherA2value from from thea2smap, etc.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:
and try to retrieve or traverse terms of type
D. Below, I assume you want to retrieve and/or traverse only the "first" one encountered (e.g., theDinA3only, ignoring the one inA4).Anyway, you can do this with
Datagenerics and some helpers fromlens'sData.Data.Lens.No special data type is needed. A plain
Mapis sufficient, with a sum type to represent the collection of values you want to store:To look up a (possibly deeply nested) value by key, we can use the
biplatetraversal fromlens:Here,
biplaterecursively traverses all the subterms of typevin the termdat. ThefirstOfquery returns the first matching term orNothingif no terms are found. (Thedoblock is running in theMaybemonad.)To perform an indexed traversal, we can also use
biplate, modified usingtaking 1to traverse only the first match:The full code: