Reputation: 2169
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
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
an ordinary value, like in your data Choice a = IntChoice Int | StringChoice String | OtherChoice a; choice :: Choice a -> Int
example, or
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
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