If your code is Haskell 2010, with no language extensions turned on, Haskell actually doesn't support run-time polymorphism at all!
That's quite surprising. Haskell feels like a very polymorphic language. But in fact, all the types can in principle be decided purely at compile-time. (GHC chooses not to, but that's an implementation detail.)
This is exactly the same situation as C++ templates. When you write something like std::vector<int>
, the compiler knows, at compile-time, all the times involved. The really surprising thing is just how rarely you actually need true run-time polymorphism!
Now, there are scenarios where you want to run different code based on run-time circumstances. But in Haskell, you can do that just by passing a function as an argument; you don't need to create a "class" (in the OOP sense) merely to achieve this.
Now, if you turn on some language extensions (most conspicuously ExistentialQuantification
) then you get true, run-time polymorphism.
Note that the main reason people seem to do this is so you can do
class Foo f where
bar :: f -> Int
baz :: f -> Bool
data AnyFoo = forall f. Foo => AnyFoo f
my_list :: [AnyFoo]
...
This is widely considered a Haskell anti-pattern. In particular, if you upcast stuff in Java to put it into a list, you then later downcast it again. But the code above offers no possibility to ever downcast. You also can't use run-time reflection (since Haskell doesn't have that either). So really, if you have a list of AnyFoo
, the only thing you can do with it is call foo
or bar
on it. So... why not just store the result of foo
and bar
?
data AnyFoo = AnyFoo {foo :: Int, bar :: Bool}
It lets you do exactly the same stuff, but doesn't require any non-standard extensions. In fact, in some ways, it's actually a bit more flexible. You now don't even need a Foo
class, you don't need to define a new type for every sort of Foo
you might have, just a function that constructs the AnyFoo
data structure for it.