1

Does anyone know of an extension that would allow one to put type constraints on pattern matching? For example:

{Language Extension}
IsOrderable::(Eq a)=>a->IO Bool
IsOrderable x = case x of
    o::(Ord a=>a) -> do
                         putStrLn "This equatable thing is also orderable."
                         return True
    _             -> do
                         putStrLn "This equatable thing is not orderable."
                         return False

Note: I am asking this so I could make monads that react differently based on its input type. Specifically, I was tring to make a probability monad, but I would like to check if the input type is equatable so I could combine duplicates.

PyRulez
  • 10,513
  • 10
  • 42
  • 87
  • How in the world would you make use of that? Haskell is statically typed. – Karolis Juodelė Nov 29 '13 at 18:44
  • Two words: Restricted Monads. You could have a set monad which just gives a failure context if you put in a nonorderable thing. This would just be finding out which specific type something is of a more general type, and handling them differently. – PyRulez Nov 29 '13 at 18:46
  • @PyRulez Even if that were the case, you'd be able to determine it at compile time and throw an error then. – bheklilr Nov 29 '13 at 18:57
  • @bheklilr It would be best if it threw it at compile time. Then it would be as if the pattern match were part of the type-checking system. – PyRulez Nov 29 '13 at 18:59
  • I suspect that this question is arising from an attempt to squash other languages' idioms into Haskell. If you give a bit more context about what you're trying to accomplish, you may get a more helpful answer. – Daniel Wagner Nov 29 '13 at 19:20
  • Oh, if you're interested in constraints over monads, there are more direct solutions. Check out for instance this paper: http://www.ittc.ku.edu/csdl/fpg/files/Sculthorpe-13-ConstrainedMonad.pdf – user2141650 Feb 11 '14 at 00:56

2 Answers2

2

I don't know of such an extension, and furthermore I suspect it would be very difficult to create such an extension with GHC's current method of implementing classes. In particular, GHC does classes by passing around "evidence" that a given type implements a given class; so when you see foo :: Eq a => a -> IO Bool that really means foo takes two arguments: one is an a and the other is a piece of evidence that there is an Eq a instance. This evidence is unforgeable.

Now think about what's happening in your proposed code: you have in scope a value of type a and evidence that there's an Eq a instance, and then you ask the question: is there evidence anywhere in this program that there's an Ord a instance? Moreover, I'm not going to tell you ahead of time what a is. Please give me the right answer to my question anyway, thanks!

Here's the most canonical tricky example I can come up with.

{-# LANGUAGE ExistentialQuantification #-}
data Existential = forall a. Eq a => Existential a

tricky :: Existential -> IO Bool
tricky (Existential x) = isOrderable x

The Existential type wraps up a value of another type, remembering only two things: the value that we wrapped, and some evidence that there is an Eq type for that instance. In particular, the type itself isn't even remembered! So now, when it comes time to run tricky, we have a value of some type (but we've forgotten which one) and evidence that the type in question is an instance of Eq. Now, how are we going to guess whether it's also an instance of Ord? We can't really know -- we don't even know which instance to look for! (Indeed, we can't even really inspect the value in a reasonable way to try to reconstruct what type it is; the only inspection we can do are operations offered by the Eq type class. That doesn't tell us much.) Are we looking for an Ord Int instance? Or maybe an Ord (Complex Double) instance? No idea.

On the other hand, you can do something like this already:

{-# LANGUAGE DefaultSignatures #-}
class Eq a => QueryOrd a where
    isOrd :: a -> IO Bool
    default isOrd :: Ord a => a -> IO Bool
    isOrd _ = putStrLn "yup" >> return True

isOrdAltDef :: Eq a => a -> IO Bool
isOrdAltDef _ = putStrLn "nope" >> return False

instance Eq a => QueryOrd (Complex a) where isOrd = isOrdAltDef
instance         QueryOrd MyFancyType where isOrd = isOrdAltDef

...but you will need one instance of QueryOrd for each non-Ord instance of Eq.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • If I did `isOrderable 5`, wouldn't there be evidence of it being orderable? What is an example in which there wouldn't be evidence? – PyRulez Nov 29 '13 at 19:24
  • @PyRulez Evidence _where_, though? The point is that since we have the type `isOrderable :: Eq a => a -> IO Bool`, we only pass in evidence that there is an `Eq` instance for whatever type `5` happens to have. There may be other evidence lying around somewhere, but it's not available to the body of `isOrderable` -- only what we pass it. I'll try to add an example in my answer that makes it clear why this might be tricky. – Daniel Wagner Nov 29 '13 at 19:39
2

There is a way, but it isn't pretty. We'll first make the function that we want to see dispatch on type class instance.

class IsOrd a where
    isOrd :: a -> IO Bool

isOrd will eventually have two instances, corresponding two each of your cases. Of course, the simple

instance Ord a => IsOrd a where
    isOrd = putStrLn "Orderable!" >> return True
instance IsOrd a where
    isOrd = putStrLn "Not orderable" >> return False

Won't work because the instance heads (IsOrd) are the same, and the way the compiler works is that it matches the instance head whether or not the constraint holds, and only then checks the constraint.

Now comes the complicated part: it is described here and here. (The second link, Oleg Kiselyov's collecion on Type Classes, might have other stuff that's relevant, and that I'm not including here because I don't know about it yet. It's also a great resource generally!). The essence of it is getting to:

data HTrue
data HFalse
instance (Ord a) => IsOrd' HTrue a where
  isOrd' _ x = putStrLn "Orderable." >> return True
instance IsOrd' HFalse a where
  isOrd' _ x = putStrLn "Not orderable" >> return False

Where we've added an extra type-level boolean to the instance head so that what in your example were cases become distinct instance heads. Pretty nifty idea!

There are other details that you have to worry about, some of which I don't fully understand, but it's easy enough to get it working anyhow: here's some code that does what you want (you'll have to thread Eq instances all along it, from IsOrd onwards, if you want the Eq constraint too). And here's the end result:

*Scratch> :l scratch.hs
[1 of 1] Compiling Scratch          ( scratch.hs, interpreted )
Ok, modules loaded: Scratch.
*Scratch> isOrd (5::Int)
Orderable.
True
*Scratch> isOrd ('c')
Not orderable
False
user2141650
  • 2,827
  • 1
  • 15
  • 23