Elixir for Java Developers, Episode III

Jusabe Guedes
SkyHub Labs
Published in
5 min readNov 28, 2017

Previous Episodes

Intro

After covering some of the basics comparisons about Elixir and Java, it's time to compare more advanced topics. Here I'll talk about things like polymorphism, inheritance and composition.

Interface vs Behaviour

In Java is common to achieve polymorphism through interfaces, abstract classes and methods. In Elixir we have behaviours and protocols providing polymorphism at the module and function level respectively.

So comparing the two languages, Java interfaces is more likely to be translated to a combination between Elixir behaviours and protocols. For now let's explore Elixir's behaviour.

//Java
public interface Runner {
boolean run(MyRunnable runnable);
boolean stop();
}
#Elixir
defmodule Runner do
@callback run(runnable :: term) :: boolean
@callback stop :: boolean

def call(element) do
IO.inspect element
end
end

As you can see they look pretty similar. We need to tell both input and output types. The only difference is that an Elixir behaviour is declared as a module like any other but instead of implementing functions, we declare them using the @callback directive. Despite the function declarations isn't prohibited to have function implementation along side the aforementioned @callback. The call function is just an example of it.

On the other hand Java expect us to be explicit right at the beginning with the interface reserved word. We also can't have any method implementation inside interfaces, unless it is declared as default(Java 8 or higher).

Both usage are fairly simple:

//Java
class JobRunner implements Runner{
public boolean run(MyRunnable runnable) {
return true;
}
public boolean stop() {
return true;
}
}
#Elixir
defmodule JobRunner do
@behaviour Runner
def run(runnable), do: true
def stop, do: true
end

Elixir's behaviours are just an way to declare a bunch of functions, which is not necessarily dynamically used depending on the context. Looking at the compiler perspective, interfaces and behaviours have one major distinction. The Java compiler won't compile our class unless we implement the mentioned interface.

JobRunner.java:2: error: JobRunner is not abstract and does not override abstract method stop() in Runner

But the Elixir's compiler will just throw one warning for each unimplemented function that was declared by the behaviour, letting your code run freely.

warning: undefined behaviour function run/1 (for behaviour Runner)
warning: undefined behaviour function stop/0 (for behaviour Runner)

I'm sorry but I have no comment on that. If anyone has a good explanation to it, please leave a comment so I can update this post. =)

Protocols

Elixir's protocols are a very clever way to achieve polymorphism at function level. This means we can easily extend our implementation based on our type without having to change "interfaces". To comprehend this let's see a silly Greeting example:

#Elixir
defprotocol Greeting do
@fallback_to_any true
def hello(element)
end
defimpl Greeting, for: Integer do
def hello(number), do: "Hello number #{number}."
end
defimpl Greeting, for: Map do
def hello(map) do
keys = Map.keys(map)
"Hello map! Your keys are #{inspect(keys)}."
end
end
defimpl Greeting, for: Any do
def hello(_any), do: "Hello! You can be anything."
end

#Here are some usage examples
iex(1)> Greeting.hello 5
"Hello number 5."
iex(2)> Greeting.hello %{first_name: "foo", last_name: "bar"}
"Hello map! Your keys are [:first_name, :last_name]"
iex(3)> Greeting.hello "foo"
"Hello! You can be anything."

The quickest way to achieve the exactly same abstraction in Java is by overloading the hello method inside a Greeting class. As you can see below this approach can lead to a gigantic class containing several implementations. Another way to obtain this level of abstraction is by creating an interface(or abstract class) generically typed (a.k.a Greeting<T>) with a default implementation.

//Java
public class Greeting{
public static String hello(Integer integer){
return String.format("Hello number %d.", integer);
}
public static String hello(Object obj){
return "Hello! You can be anything.";
}
}
//Here are some usage examples
> Greeting.hello(5);
"Hello number 5."
> Greeting.hello("foo");
"Hello! You can be anything."

Despite the various way we can accomplish "Java protocols", I personally don't see any of them as equivalent to Elixir's. Using interfaces we still need to create some kind of factory, a concrete class and instantiate it. With protocols we get over the factory issue by default having a transparent experience.

Inheritance vs Composition

To begin with, Elixir does not have any kind of inheritance. However this isn't a problem at all. Elixir provide us powerful ways to compose modules using other modules and make code generate code.

Even with the possibility to implement several interfaces, Java does not allow us to inherit from multiple ancestors, thus we must extend from one and only one class. Here is an elementary example:

//Java
public class Bar {
String bar(){
return "bar";
}
}
public class Foo extends Bar{
}
//Usage
> new Foo().bar();
"bar"

Now let's see the equivalent using Elixir.

#Elixir
defmodule Bar do
defmacro __using__(_opts) do
quote do
def bar do
"bar"
end
end
end
end
defmodule Foo do
use Bar
end
#Usage
iex(1)> Foo.bar
"bar"

When we typed 'use Bar' we were actually calling the __using__ macro behind the scenes and the entire quote block will be injected into Foo module. In simple words we made code produce code by letting the 'use' macro expand inside Foo at compile time.

Elixir's macros are so powerful that I won't dare explain it here. It deserves an entire post, but if you are anxious there is an wonderful book called Metaprogramming Elixir that explain everything in details.

Putting macros aside for a moment, sometimes we just need to make some functions available inside a context. To this purpose we can import as many modules as we want. Let's rewrite the example above replacing the use by import word and see how it works:

#Elixir
defmodule Bar do
def bar do
"bar"
end
end
defmodule Foo do
import Bar
def call_bar do
bar()
end
end

It will make all Bar's macros and functions accessible by Foo without the obligation of qualifying the Bar module. Java 'import static' form could be a good correlation if you think that functions in Elixir are some sort of static methods.

Conclusion

Those are complex topics in both languages and I wanted to make just a shallow comparison between them. We merely scratched the surface so they deserve a more real life example of how we can leverage from things like Elixir metaprogramming.

On the next posts I'll try to explore concepts like reflections and annotations. So if you want to see one specific topic, please let me know. =)

--

--

Jusabe Guedes
SkyHub Labs

A software engineer who doesn’t drink coffee and wants to be a digital nomad.