Let’s consider the following code:
multi :: String -> a
"Int" = (1 :: Int)
multi "Float" = (1 :: Float) multi
Output:
Error occurred
ERROR line 5 - Type error in explicitly typed binding
*** Term : multi
*** Type : String -> Float
*** Does not match : String -> Int
Or
multi :: String -> a
= 1.0 multi _
Output:
Error occurred
ERROR line 4 - Cannot justify constraints in explicitly typed binding
*** Expression : multi
*** Type : String -> a
*** Given context : ()
*** Constraints : Fractional a
Or even:
multi :: String -> a
= 1.0 :: Float multi _
Output:
Error occurred
ERROR line 4 - Inferred type is not general enough
So that does not work. How about faking polymorphism via data type members?
data X = X1 Int | X2 Float | X3 String
data Z a = Z1 Int | Z2 Float | Z3 String
bar :: String -> Z a
= case s of
bar s -> Z3 undefined :: Z a
_ -- "Int" -> Z1 1 :: Z Int
foo :: String -> X
= case s of
foo s "Int" -> X1 1
"Float" -> X2 1.0
-> X3 "Vrotebal"
_
That works, but our functions/types aren’t polymorphic in the original sense… So let’s modify the first example:
multi :: String -> (forall a. a -> b) -> b
"Int" f = f (1 :: Int)
multi "Float" f = f (1 :: Float) multi
Now think about what f can be :P…
To be useful, you’ve got to include information that would let the caller figure out which of the two you’ve returned The easiest is obviously data Foo = FooInt Int | FooFloat Float, like in the second code example above. There’s also Data.Typeable, where the compiler provides some magic to give you a “tag” for every type and an existential wrapper that preserves the tag…
So, on the one hand the type of ... :: String -> a
exists and is legit, e.g. abort :: String -> a
, error :: String -> a
, absurd :: Void -> a
, on the other hand they are, practically speaking, all special functions with seemingly no real way to make custom useful functions with the type of ... :: String -> a
.
To see why this can’t work, consider
= do
main <- getLine
str let x = multi str -- What type does x have?
print x -- Instances are chosen at compile time. Which
-- instance should be picked here? What should
-- the output be?
I hope that helps in case you ended up in a similar rabbit hole of haskell polymorphism.
Special thanks to the contributors, whose questions and comments have provoked this post: OlexP, Morrow, Xal, Ailrun the Corgi