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 offoogives an inexhaustive patterns warning. And it should, because the way you have definedAorBis "open". That is, somebody in another module is free to declareand then your definition of
foowill suddenly become invalid as it fails to handle theSomeCcase. To get what you are looking for, you should use a closed type family:This family is totally defined on
States. We would then define your previousAorBconstraint like so:However, in a moment we will need to use
AorBin 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
foralladded for clarity)is that we are allowed to instantiate
bar @B, giving usAorB Bis 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 thebarfunction to choose whichsit is, not the caller. InformallyThat is,
barchooses ans, and returns aSome s, together with some evidence thatAorB sholds. This is no longer an implication, we would not put the responsibility on the caller to prove that the typebarchose was eitherAorB-- 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
PolyKindsandConstraintKinds)Ex c fcan be read as "there existsasuch thatc aholds and there is a valuef a". This is existential becuase the variableadoes not appear inEx c f, it's hidden by the constructor. And now we can implementbarWe 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.