Home > other >  Overloaded / Polymorphic Functions with different types
Overloaded / Polymorphic Functions with different types

Time:10-25

I'm learning Haskell and came into an unanswered question:

From the lesson I'm following:

( ) :: Num a => a -> a -> a

For any numeric type a, ( ) takes 2 values of type a and returns a value of type a.

As per the examples:

1   1
-- 2 # type a is Int
3.0   4.0
-- 7.0 type a is Float
'a'   'b' 
-- Type Error: Char is not a Numeric type

This makes perfect sense, but i end up not understanding what goes on in the background, when:

1   3.0

Is the Type Inference system auto casting my Int to Float because it knows it will return Float?

CodePudding user response:

You should investigate these sorts of questions in ghci. It's an invaluable learning resource:

$ ghci
GHCi, version 9.0.1: https://www.haskell.org/ghc/  :? for help
ghci> :t 1
1 :: Num p => p
ghci> :t 3.0
3.0 :: Fractional p => p
ghci> :t 1   3.0
1   3.0 :: Fractional a => a

First lesson: Numeric literals are polymorphic. 1 isn't an Int, it's polymorphic. It can be any instance of Num that is necessary for the code to compile. 3.0 isn't a Float, it's any instance of Fractional that is necessary for the code to compile. (The difference being the decimal in the literal - it restricts the types allowed.)

Second Lesson: when you combine things into an expression, types get unified. When you unify Num and Fractional constraints, you get a Fractional constraint. This is because Fractional is defined to require all instances of it also be instances of Num.

For a bit more, let's turn on warnings and see what additional information they provide.

ghci> :set -Wall
ghci> 1

<interactive>:5:1: warning: [-Wtype-defaults]
    • Defaulting the following constraints to typeInteger
        (Show a0) arising from a use of ‘print’ at <interactive>:5:1
        (Num a0) arising from a use of ‘it’ at <interactive>:5:1In a stmt of an interactive GHCi command: print it
1
ghci> 1   3.0

<interactive>:6:1: warning: [-Wtype-defaults]
    • Defaulting the following constraints to typeDouble
        (Show a0) arising from a use of ‘print’ at <interactive>:6:1-7
        (Fractional a0) arising from a use of ‘it’ at <interactive>:6:1-7In a stmt of an interactive GHCi command: print it
4.0

When printing a value, ghci requires the type have a Show instance. Fortunately, that isn't too important of a detail here, but it's why the defaulting warnings refer to Show.

The lessons to observe here are that the default type for something with a Num instance if inference doesn't require something more specific is Integer, not Int. The default type for something with a Fractional instance if inference doesn't require something more specific is Double, not Float. (Float is basically never used. Forget it exists.)

So when inference runs, the expression 1 3.0 is inferred to have the type Fractional a => a. In the absence of further requirements on the type, defaulting kicks in and says "a is Double". Then that information flows back through the ( ) to its arguments and requires each of them to also be Double. Fortunately, each argument is a polymorphic literal that can take the type Double. Type checking succeeds, instances are chosen, addition happens, the result is printed.

It's very important to this process that numeric literals are polymorphic. Haskell has no implicit conversions between any pair of types. Especially not numeric types. If you want to actually convert values from one type to another, you must call a function that does the conversion you desire. (fromIntegral, round, floor, ceiling, and realToFrac are the most common numeric conversion functions.) But when the values are polymorphic, it means inference can pick a matching type without needing a conversion.

  • Related