So, one can define a membership constraint like so:
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
module Whatever where
type family MemberB (x :: k) (l :: [k]) where
MemberB _ '[] = 'False
MemberB a (a : xs) = 'True
MemberB a (b : xs) = MemberB a xs
type Member x xs = MemberB x xs ~ 'True
data Configuration = A | B | C
data Action (configuration :: Configuration) where
Action1 :: Member cfg '[ 'A ] => Action cfg
Action2 :: Member cfg '[ 'B, 'C ] => Action cfg
Action3 :: Member cfg '[ 'A, 'C ] => Action cfg
exhaustive :: Action 'A -> ()
exhaustive Action1 = ()
exhaustive Action3 = ()
exhaustive Action2 = ()
But the error message we get is not very informative:
• Couldn't match type ‘'False’ with ‘'True’
Inaccessible code in
a pattern with constructor:
Action2 :: forall (cfg :: Configuration).
Member cfg '['B, 'C] =>
Action cfg,
in an equation for ‘exhaustive’
• In the pattern: Action2
In an equation for ‘exhaustive’: exhaustive Action2 = () (intero)
It'd be nice to use the new TypeError
feature to improve on this message, however, a naive solution gobbles the error:
import GHC.TypeLits
type family MemberB (x :: k) (l :: [k]) where
MemberB _ '[] = TypeError ('Text "not a member")
MemberB a (a : xs) = 'True
MemberB a (b : xs) = MemberB a xs
It seems that, maybe, TypeError
behaves as any type, and so it unifies happily with 'True
?
Is there a way to get a nice type error, while preserving the membership behavior?
Well, it doesn't use TypeError
, but you might find it interesting anyway:
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
module Whatever where
data IsMember k = IsMember | Isn'tMember k [k]
type family MemberB (x :: k) (l :: [k]) (orig :: [k]) where
MemberB a '[] ys = 'Isn'tMember a ys
MemberB a (a : xs) ys = 'IsMember
MemberB a (b : xs) ys = MemberB a xs ys
type Member x xs = MemberB x xs xs ~ 'IsMember
data Configuration = A | B | C
data Action (configuration :: Configuration) where
Action1 :: Member cfg '[ 'A ] => Action cfg
Action2 :: Member cfg '[ 'B, 'C ] => Action cfg
Action3 :: Member cfg '[ 'A, 'C ] => Action cfg
exhaustive :: Action 'A -> ()
exhaustive Action1 = ()
exhaustive Action3 = ()
exhaustive Action2 = ()
The error is a bit more informative now:
test.hs:32:16: error:
• Couldn't match type ‘'Isn'tMember 'A '['B, 'C]’ with ‘'IsMember’
Inaccessible code in
a pattern with constructor:
Action2 :: forall (cfg :: Configuration).
Member cfg '['B, 'C] =>
Action cfg,
in an equation for ‘exhaustive’
• In the pattern: Action2
In an equation for ‘exhaustive’: exhaustive Action2 = ()
|
32 | exhaustive Action2 = ()
| ^^^^^^^
exhaustive
is handling a case that can never happen but that's not really an error. Or at least, it's working as intended for now, even if the type system could be improved to not allow handling of impossible cases.
Pattern-matching on Action2
provides you the constraint Member 'A '[ 'B, 'C ]
into your context. This is different from using Action2
as an expression, which requires that constraint, and that would result in an error in the constraint solver.
I think you probably want to go back to the first attempt:
type family MemberB (x :: k) (l :: [k]) where
MemberB _ '[] = 'False
MemberB a (a : xs) = 'True
MemberB a (b : xs) = MemberB a xs
But let's fix up Member
.
type Member x l = Member' x l (MemberB x l)
type family Member' x l mem :: Constraint where
Member' x l 'True = ()
Member' x l 'False =
TypeError ('ShowType x :<>:
'Text " is not a member of " :<>:
'ShowType l)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.