I'm trying to restrict the return type of a function to a subset of (kind) constructors. I can use typeclasses to restrict the input types, but when I try the same technique on return types as shown below, I get a Couldn't match type ‘s’ with ‘'A’
error.
Is there a way to restrict the bar
function below to return either SomeA
or SomeB
?
It seems that Liquid Haskell is an alternative, but it seems that this should be possible using only things like DataKinds
, GADTs
and/or TypeInType
.
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
module Test where
data State = A | B | C
class AorB (s :: State)
instance AorB 'A
instance AorB 'B
data Some (s :: State) where
SomeA :: Some 'A
SomeB :: Some 'B
SomeC :: Some 'C
-- This works
foo :: AorB s => Some s -> Bool
foo aorb = case aorb of
SomeA -> True
SomeB -> False
-- This fails to compile with ""Couldn't match type ‘s’ with ‘'A’""
bar :: AorB s => Some s
bar = SomeA
A few things here. If you compile with
-Wall
(which you should!) you would find that your definition offoo
gives an inexhaustive patterns warning. And it should, because the way you have definedAorB
is "open". That is, somebody in another module is free to declareand then your definition of
foo
will suddenly become invalid as it fails to handle theSomeC
case. To get what you are looking for, you should use a closed type family:This family is totally defined on
State
s. We would then define your previousAorB
constraint like so:However, in a moment we will need to use
AorB
in curried form, which is not allowed for type synonyms. There is an idiom for declaring curriable synonyms which is a bit clunky, but is the best we have at the moment:Anyway, with this new definition, you will find that the inexhaustive pattern warning goes away for
foo
. Good.Now on to your question. The trouble with your definition (with an explicit
forall
added for clarity)is that we are allowed to instantiate
bar @B
, giving usAorB B
is satisfiable, so we should be able to get aSome B
, right? But not according to your implementation. So something is logically wrong here. You are probably looking to return an existentially quantifieds
, in other words, you want thebar
function to choose whichs
it is, not the caller. InformallyThat is,
bar
chooses ans
, and returns aSome s
, together with some evidence thatAorB s
holds. This is no longer an implication, we would not put the responsibility on the caller to prove that the typebar
chose was eitherA
orB
-- the caller has no idea what was chosen.Haskell does not directly support existential types, but we can model them with a GADT (make sure to use
PolyKinds
andConstraintKinds
)Ex c f
can be read as "there existsa
such thatc a
holds and there is a valuef a
". This is existential becuase the variablea
does not appear inEx c f
, it's hidden by the constructor. And now we can implementbar
We can test that the constraints are correctly propagated by passing this to your
foo
:There you go.
That said, design-wise I would just say
instead. Type signatures should be as informative as possible. Don't hide information that you know -- let abstraction do the hiding. When you hide information about your assumptions/arguments, you are making your signature stronger; when you hide about your results, you are making it weaker. Make it strong.