I'm playing around with the constraints
package (for GHC Haskell). I have a type family for determining if a type-level list contains an element:
type family HasElem (x :: k) (xs :: [k]) where
HasElem x '[] = False
HasElem x (x ': xs) = True
HasElem x (y ': xs) = HasElem x xs
This works, but one thing it doesn't give me is the knowledge that
HasElem x xs entails HasElem x (y ': xs)
since the type family isn't an inductive definition of the "is element of" statement (like you would have in agda). I'm pretty sure that, until GADTs are promotable to the type level, there is no way to express list membership with a data type.
So, I've used the constraints
package to write this:
containerEntailsLarger :: Proxy x -> Proxy xs -> Proxy b -> (HasElem x xs ~ True) :- (HasElem x (b ': xs) ~ True)
containerEntailsLarger _ _ _ = unsafeCoerceConstraint
Spooky, but it works. I can pattern match on the entailment to get what I need. What I'm wondering is if it can ever cause a program to crash. It seems like it couldn't, since unsafeCoerceConstraint
is defined as:
unsafeCoerceConstraint = unsafeCoerce refl
And in GHC, the type level is elided at runtime. I thought I'd check though, just to make sure that doing this is ok.
--- EDIT ---
Since no one has given an explanation yet, I thought I would expand the question a little. In the unsafe entailment I'm creating, I only expect a type family. If I did something that involved typeclass dictionaries instead like this:
badEntailment :: Proxy a -> (Show a) :- (Ord a)
badEntailment _ = unsafeCoerceConstraint
I assume that this would almost certainly be capable of causing a segfault. Is this true? and if so, what makes it different from the original?
--- EDIT 2 ---
I just wanted to provide a little background for why I am interested in this. One of my interests is making a usable encoding of relational algebra in Haskell. I think that no matter how you define functions to work on type-level lists, there will be obvious things that aren't proved correctly. For example, a constraint (for semijoin) that I've had before looked like this (this is from memory, so it might not be exact):
semijoin :: ( GetOverlap as bs ~ Overlap inAs inBoth inBs
, HasElem x as, HasElem x (inAs ++ inBoth ++ inBs)) => ...
So, it should be obvious (to a person) that if I take union of two sets, that it contains an element x
that was in as
, but I'm not sure that it's possible the legitimately convince the constraint solver of this. So, that's my motivation for doing this trick. I create entailments to cheat the constraint solver, but I don't know if it's actually safe.
as
andbs
help you if you don't know where it is in that union? I'd be interested in seeing some of the actual code. – dfeuer