So, I’m working on a fun experiment in contextual meaning and I’m running into a wall. I’m trying to define a data type that can either be a primitive or a function that transforms from one constructor to another.
data WeaponPart =
WInt Int |
WHash (Map.Map String Int) |
WNull |
WTrans (WeaponPart -> WeaponPart)
instance Show WeaponPart where
show (WInt x) = "WInt " ++ (show x)
show (WHash x) = "WHash " ++ (show x)
show (WTrans _) = "WTrans"
show WNull = "WNull"
cold :: WeaponPart -> WeaponPart
cold (WInt x) = WHash (Map.singleton "frost" x)
cold (WHash x) = WHash $ Map.insertWith (+) "frost" 5 x
cold (WTrans x) = cold $ x (WInt 5)
cold (WNull) = cold $ (WInt 5)
ofTheAbyss :: WeaponPart -> WeaponPart
ofTheAbyss (WTrans x) = x (WTrans x)
The problems is that the signature for ofTheAbyss allows any WeaponPart as an argument, whereas I only want to allow WTrans-constructred arguments. You can see I’ve only written a pattern match for that case.
I’ve tried doing with with GADTs but I fear it was a rabbit hole. Could never really get them to do what I wanted. Does anyone have any ideas how I could enforce only WTrans arguments into ofTheAbyss? Or am I just completely missing something.
Thanks.
Best,
Erik
You can do this sort of thing with GADTs. Far be it from me to judge whether what results is a rabbit hole, but let me at least show the recipe. I’m using the new
PolyKindsextension, but you can manage with less.First, decide what sorts of stuff you will need, and define a datatype of those sorts.
Next, define your data indexed by their sorts. It’s like building a little typed language.
You can represent ‘data of any sort’ via existential quantification, as follows:
Note that the
xdoes not escape, but we can still inspect the ‘evidence’ thatx‘satisfies’p. Note thatSomemust be adatatype, not anewtypeas GHC objects to existentialnewtypes.You are now free to write
Sort-generic operations. If you have generic inputs, you can just use polymorphism, effectively curryingSome p -> ...asforall x. p x -> ....The existential is needed for
Sort-generic outputs: here I use it for input and output.I had to add the occasional touch of
Witabout the place, but it’s the same program.Meanwhile, we can now write
So it’s not horrendous to work with embedded type systems. Sometimes there is a cost: if you want your embedded language to have subsorting, you may find you do extra computation just to change the index of some data’s type, making no difference to the data themselves. If you don’t need subsorting, the extra discipline can often be a real friend.