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.