Posted on August 31, 2021

Are Polymorphic Output Functions, given a concrete type, Possible?

Let’s consider the following code:

multi :: String -> a
multi "Int"   = (1 :: Int)
multi "Float" = (1 :: Float)          

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
multi _   = 1.0

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
multi _   = 1.0 :: Float

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
bar s = case s of
          _       -> Z3 undefined :: Z a
--          "Int"   -> Z1 1 :: Z Int

foo :: String -> X
foo s = case s of
          "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
multi "Int"   f = f (1 :: Int)
multi "Float" f = f (1 :: Float)

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

main = do
  str <- getLine
  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

comments powered by Disqus