Ahnfelt / type-inference-by-example

A series of down-to-earth articles on implementing type inference

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Operator overloading

kthompson opened this issue · comments

Hello! I really like your series on type inference. Thank you. I was wondering if you knew of any information on how to implement something like operator overloading ie if i have some method fn(x) = x + x and my language has both int and float so therefore two ways to add, how would you go about inferring the type(s?) in that case?

Thanks 😄

Thank you 👍

So the first thing to note is that operators are just functions with a slightly different syntax.

Haskell provides overloading of functions via its type class mechanism:

class Num a where
    (+) :: a -> a -> a

instance Num Int where
    x + y = -- implementation for Int + Int goes here

instance Num Float where
    x + y = -- implementation for Float + Float goes here

instance Num MyOwnType where
    x + y = -- implementation for MyOwnType + MyOwnType goes here

-- ...

(Some things from the Num type class is omitted here for brevity)

This gives you an operator with the following signature:

(+) :: forall a. Num a => 
    a -> a -> a

This says that for all types a for which there exists a Num a instance, a + a :: a. E.g:
Float + Float :: Float.
Int + Int :: Int.
MyOwnType + MyOwnType :: MyOwnType.

In the syntax of the series, we might have something like:

function f(x, y) { return x < x + y; }

Which should end up with a type signature like this:

function f<T>(x : T, y : T) : Bool where Order<T>, Number<T>

Given that the types of the operators are like this:
< : (T, T) => Bool where Order<T> generic over T
+ : (T, T) => T where Number<T> generic over T

So let's try our usual approach:

function f(x : $1, y : $2) : $3 { 
    return x < x + y; 
}

When we get to x < (x + y), we can conclude from the signature of < that we must require Order<$1>.

When we get to x + y, we can conclude from the signature of + that $1 = $2 and that we must require Number<$1>.

From return we can conclude $1 = $3.

Thus we have Order<$1>, Number<$1> and $2 := $1, $3 := $1. Generalizing the type we get:

function f<T>(x : T, y : T) : Bool where Order<T>, Number<T>

Now, how do we keep track of the Order<$1>, Number<$1>?

For single parameter type classes like this, I believe the easiest approach is this:

  • Keep a map from type variable to required type classes
  • When binding one type variable to another, e.g. $2 := $1, transfer all the required type classes of $2 to $1.

Nice. Thanks for the input. This is an interesting approach. When I was thinking about it I was thinking about it from a stand point that there would be two functions for the type ie x + y being float or int but in a way you are essentially passing in a function that performs the addition via the typeclass. In that way we would only ever pass in a single way of adding but the types could still be generic.

I also looked at the other language you work on and I see how you use "constraints" which appear to essentially be the type classes. Very cool stuff. Anyways thanks for the feedback 😄