What Elixir Taught Me About Java - Method Overloading Is Just a Convention

I was struggling to understand the differences between anonymous and named functions in Elixir - only to eventually realize that a lot of it is due to the incorrect mental model I had about method overloading from before.

As the saying goes, "for every hard problem, there is a logical, yet completely wrong explanation".

Examples of function arity overloading

Arity is just the term used to refer to the number of arguments the function has.

Java

public class MethodOverloadingArity {

    public static String join(String a, String b) {
        return join(a, b, " ");
    }

    public static String join(String a, String b, String sep) {
        return String.format("%s%s%s", a, sep, b);
    }
}

Elixir

defmodule MethodOverloadingArity do

    def join(a, b) do
        join(a, b, " ")
    end

    def join(a, b, sep) do
        a <> sep <> b  # string concatenation
    end

end

My wrong mental model

I was thinking that since in interviews, tutorials, etc. method overloading always comes up, there must be something special about it. It makes for dynamic dispatch - which gets bound at compile time in regular Java, but when invoked dynamically, it will resolve to the appropriate overload. I.e.: the following pseudocode would be valid:

foo = MethodOverloadingArity.join
foo.("a", "b")
foo.("a", "b", " - ")

You can tell just from the heading that of course, it's is not valid. And this is what we get from Elixir:

** (UndefinedFunctionError) undefined function: MethodOverloadingArity.join/0
    MethodOverloadingArity.join()
    MethodOverloadingArity.exs:13: (file)
    (elixir) src/elixir_lexical.erl:17: :elixir_lexical.run/2
    (elixir) lib/code.ex:301: Code.require_file/2


In the language, the functions are only unique with the combination of the function name and the number of arguments - it fails because it cannot find an overload which takes 0 arguments. Thus at the time I store a reference to it in a variable, I have to resolve the arity.

Initially I thought it's just a special behavior for Elixir (Erlang), so I set out to explore how it works in Java

import java.lang.reflect.Method;
import java.lang.IllegalArgumentException;

public class MethodOverloadingMain {

    public static void main(String[] args) throws Exception {
        Class<?> c = Class.forName("MethodOverloadingArity");
        Method[] declaredMethods = c.getDeclaredMethods();
        for(Method method : declaredMethods) {
            if (method.getName() != "join")
                continue;
            System.out.println(method.toString());
            try {
                Object result = method.invoke(c, "first", "second");
                System.out.format("... invocation result (2 args): %s\n", result);
            } catch(IllegalArgumentException e) {
                System.out.format("... (2 args) %s\n", e);
            }
            try {
                Object result = method.invoke(c, "first", "second", " - ");
                System.out.format("... invocation result (3 args): %s\n", result);
            } catch(IllegalArgumentException e) {
                System.out.format("... (3 args) %s\n", e);
            }
        }
    }
}

(Yes, it reminds me how much more elegant metaprogramming is in Python. But Python has no method overloading)

To my surprise (though in retrospect, it's obvious), arity also plays a role in identifying methods in Java - notice how there are two methods returned:

public static java.lang.String MethodOverloadingArity.join(java.lang.String,java.lang.String)
... invocation result (2 args): first second
... (3 args) java.lang.IllegalArgumentException: wrong number of arguments
public static java.lang.String MethodOverloadingArity.join(java.lang.String,java.lang.String,java.lang.String)
... (2 args) java.lang.IllegalArgumentException: wrong number of arguments
... invocation result (3 args): first - second

So in Java function overloading works the exact same way as it does in Elixir (and based on my quick hallway research, in most other languages) - an actual method is only identified by its name and arity (and in typed languages, by type). Thus there is no dynamic method invocation based on just its name.

So method overloading is just a convention, or if you wish to call it, a hack. I wish I learned of name and arity instead of method overloading first!

This is yet another example why learning other languages benefits you even if you won't end up using the new language in your day job!

With regardso to using method overloading, I think I prefer the Ruby/Python approach - one name, one function, period. For most usecases, default argument values allow for enough flexibility, and prevent abuse of this convention (giving two wildly different implementations for the various overloads).

Disclaimer: of course, I might still be entirely wrong. If so, please let me know!

Also, I would like to thank chrismccord, josevalim, and ericmj from the #elixir-lang IRC channel for patiently putting up with me and helping me understand all this.

What do you think? I would love if you would leave a comment - drop me an email at hello@zsoldosp.eu, tell me on Twitter!

Posted on in elixir, oop, programming languages, software by

Share this post if you liked it - on Digg, Facebook, Google+, reddit, or Twitter

Your email address