haskellpattern-matchinglanguage-extension

Haskell Extension Pattern Matching With Type Constraints


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.


Solution

  • 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