Ahnfelt / type-inference-by-example

A series of down-to-earth articles on implementing type inference
152 stars 3 forks source link

Operator overloading #1

Closed kthompson closed 3 years ago

kthompson commented 3 years ago

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 😄

Ahnfelt commented 3 years ago

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.

Ahnfelt commented 3 years ago

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:

kthompson commented 3 years ago

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 😄