Search code examples
haskellpattern-matchingpolymorphic-functions

Pattern match on type of polymorphic parameter - alternatives


Let's say I need different output depending on the type of the polymorphic parameter of a function. My initial attempt fails with some cryptic error message:

choice :: a -> Int
choice (_ :: Int) = 0
choice (_ :: String) = 1
choice _ = 2

However, we can fix that easily by wrapping the desired types in different data constructors and use those in pattern-matching:

data Choice a = IntChoice Int | StringChoice String | OtherChoice a

choice :: Choice a -> Int
choice (IntChoice _) = 0
choice (StringChoice _) = 1
choice (OtherChoice _) = 2

Question: Do you know of a way to circumvent this? Is there a feature in Haskell2010, GHC or any of the extensions that allows me to use the first variant (or something similar)?


Solution

  • Question: Do you know of a way to circumvent this? Is there a feature in Haskell2010, GHC or any of the extensions that allows me to use the first variant (or something similar)?

    No, there is no feature either in Haskell 2010 or provided by any GHC extension that lets you write a function of type

    choice :: a -> Int
    

    whose return value depends on the type of its argument. You can count on such a feature never existing in the future either.

    Even with hacks to inspect GHC's internal data representation at runtime, it's impossible to distinguish a value of type Int from a value whose type is a newtype of Int: those types have identical runtime representations.

    The Int returned by your function is a value that exists at runtime, so it needs to be determined by another value that exists at runtime. That could be either

    1. an ordinary value, like in your data Choice a = IntChoice Int | StringChoice String | OtherChoice a; choice :: Choice a -> Int example, or

    2. a type class dictionary, using either a custom class as in David Young's answer, or the built-in Typeable class:

      choice :: Typeable a => a -> Int
      choice a
        | typeOf a == typeOf (undefined :: Int)    = 0
        | typeOf a == typeOf (undefined :: String) = 1
        | otherwise                                = 2