mucaho
mucaho

Reputation: 2169

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)?

Upvotes: 3

Views: 1021

Answers (2)

Reid Barton
Reid Barton

Reputation: 15009

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
    

Upvotes: 4

David Young
David Young

Reputation: 10783

This is confusing two different kinds of polymorphism. What you want is ad-hoc polymorphism, which is done through type classes. The kind of polymorphism of a function of type a -> Int is parametric polymorphism. With parametric polymorphism, one function definition for choice must work for any possible type a. In this case, this means it can't actually use the value of type a since it doesn't know anything about it, so choice would have to be a constant function such as choice _ = 3. This actually gives you very strong guarantees about what a function can do, just by looking it its type (this property is called parametricity).

With a type class, you can implement your example as:

class ChoiceClass a where
  choice :: a -> Int

instance ChoiceClass Int where
  choice _ = 0

instance ChoiceClass String where
  choice _ = 1

instance ChoiceClass a where
  choice _ = 2

Now, I should point out that this type class approach is often the wrong one especially when someone who is just starting wants to use it. You definitely don't want to do it to avoid a simple type like the Choice type in your question. It can add lots of complexity and instance resolution can be confusing at first. Note that in order to get the type class solution to work, two extensions needed to be turned on: FlexibleInstances and TypeSynonymInstances since String is a synonym for [Char]. OverlappingInstances is also needed because type classes work on an "open world" assumption (meaning that anyone can later come along and add a instance for a new type, and this must be taken into account). This is not necessarily a bad thing, but here it is a sign of the creeping complexity caused by using the type class solution over the much simpler data type solution. OverlappingInstances in particular can make things harder to think about and work with.

Upvotes: 6

Related Questions