Elixir for Java Developers, Episode II

Jusabe Guedes
SkyHub Labs
Published in
6 min readJun 21, 2017

Hello everyone! It's time to take the second sip of our new drink! If you haven't seen the first episode, you can read it here. On this episode I'm going to tackle how we control flows, do loops and handle exceptions in Elixir.

Flow Control

In Java there are two main ways of controlling the execution flow which are the if/else and switch/case statements. Nothing fancy at the beginning:

//Java
if(x > 0){
System.out.println("greater than zero");
}else if(x < 0){
System.out.println("less than zero");
}else{
System.out.println("zero");
}
#Elixir
if x > 0 do
IO.puts "greater than zero"
else
if x < 0 do
IO.puts "less than zero"
else
IO.puts "zero"
end
end

Note that Elixir doesn't have an else if form to chain many checks, forcing us to embed the second if inside the else statement.

Don't panic! There is a much more elegant way of checking multiple boolean expressions.

#Elixir
cond do
x > 0 ->
IO.puts "greater than zero"
x < 0 ->
IO.puts "less than zero"
true ->
IO.puts "zero"
end

The cond structure will evaluate each statement top to bottom and execute the first block which the expression produces true. Placing true as last condition will have exactly the same behavior as the last Java else, executing it in case all previous conditions evaluate to false.

Elixir also has a more idiomatic way of checking negative expressions using unless which is equivalent to if(!expression). But there is one missing thing in Elixir that is the ternary boolean operator. Java easily accomplishes it with ?. Let's see some examples.

//Java
if(!isValid(x)){
System.out.println("invalid");
}
int y = x > 0 ? x + 1 : x - 1;#Elixir
unless is_valid(x), do: IO.puts "invalid"
if !is_valid(x), do: IO.puts "invalid"
y = if x > 0, do: x + 1, else: x - 1

Differently from the Java if that allows only boolean expressions, in Elixir the structures if/unless/cond evaluate everything besides nil and false to true. It means that the code bellow is just fine.

#Elixir
x = "foo"
if x do
IO.puts "bar"
end
y = nil
cond do
y ->
IO.puts "will never get here"
true ->
IO.puts "will be executed"
end

The other approach to change flows in Java is using swich/case. Beside its ugly format I personally don't like to use it due to its very odd behavior. Most cases we want to execute only the first block that matches our expression, but in Java we need to explicitly put a break in every case, otherwise all subsequents blocks will be executed whether the following cases matches or not our expression. Let's see how we can write code in both languages:

//Java
int x = 42;
switch (x) {
case 1:
case 2:
case 3:
System.out.println("Hi");
break;
case 4:
System.out.println("Hello");
break;
default:
System.out.println("whatever");
}
#Elixir
x = 42
case x do
_ when x in [1, 2, 3] ->
IO.puts "Hi"
4 ->
IO.puts "Hello"
"bla bla bla" ->
IO.puts "Talking too much"
_ ->
IO.puts "this block will be executed"
end

Java switch/case is strongly typed and the switch’s expression must evaluate to a String, char, byte, short, int or an enum. In addition to that all cases must have the same type. As you may have noticed, this level of rigor is not required in Elixir. We can try to pattern match x with anything we want and if nothing matches we can use the underscore wildcard _ meaning anything. So every time you see an _ alone, it means anything.

To finish up the contrast between the two languages I need to mention that all four if/unless/cond/case will return values. Like Elixir functions, the result of the last instruction will be returned to the caller or nil if the block is not executed. It means we can code like this:

#Elixir
y =
cond do
x == "foo" ->
"bar"
true ->
"nothing"
end

Loops

In Java we can iterate through collections or simply loop until one boolean expression is valid using for or while. Well.. we don't have any of those in Elixir.

Differently from imperative languages, Elixir is a functional language and it relies entirely on recursion to write any kind of loop. Think about for/while as function that will be called over and over until a condition is satisfied. To exemplify it let's write a positive pow function using loops:

//Java
public long pow(long number, int times) {
long acc = 1;
for(int i = 1; i<=times; i++){
acc *= number;
}
return acc;
}
#Elixir
def pow(number, times), do:
do_pow(number, times, 1)
defp do_pow(_, 0, acc), do: acc
defp do_pow(number, times, acc), do:
do_pow(number, times-1, acc*number)

At this point I bet your clever mind doesn't stop thinking about the java.lang.StackOverflowError error that hunts those who careless use recursion. To prevent this kind of error Elixir uses the tail call optimization feature. This feature has many aspects but trying to explain it in one sentence would be:

When the last instruction of a function is a call to itself, instead of piling up a new frame to the execution stack, the last frame is replaced with the new frame.

Before finish this section I’ve the obligation to mention that Elixir do has an special form of for. It’s just a syntactic sugar when we want to extract all values from a collection, optionally filter some of them, and then generate a new collection using the values that remain(a.k.a filter and map operation).

#Elixir
#will produce [4, 9]
for x <- [0, 1, 2, 3], x > 1 do
x * x
end
#equivalent to
[0, 1, 2, 3]
|> Enum.filter_map(&(&1 > 1), &(&1 * &1))

Exceptions

Elixir doesn't suport inheritance like Java, nevertheless create custom exceptions are straightforward. We can even use a default message in case no message is given.

//Java
public class CustomException extends Exception{
public CustomException(String message) {
super(message);
}
}
#Elixir
defmodule CustomException do
defexception message: "houston we have a problem"
end

Java has Error and Exception which can be handled using try/catch/finally. Elixir doesn't separate errors and exceptions. They are the same thing and use a similar structure try/rescue/after to be handled. See all Elixir exceptions as RuntimeException(uncheked) because none of them require explicitly checking. Additionally we can use an else that will match the try's result(like a case) if no exception is raised.

//Java
try{
throw new CustomException("Houston we have a problem");
}catch(NullPointerException | CustomException e){
System.out.println(e.getMessage());
}catch(Exception e){
System.out.println(e.getMessage());
}finally{
System.out.println("finally");
}
#Elixir
try do
#will raise an RuntimeError with the message bellow
raise "Houston we have a problem"
rescue
e in [RuntimeError, CustomException] ->
IO.puts e.message
e ->
IO.puts e.message
else
42 ->
//some code
_ ->
//some other code
after
IO.puts "finally"
end

Elixir also has the try/throw/catch form, but it’s to return values from a block, not handle exceptions. We usually don’t use it because there are simpler ways to achieve the same result. I see them like a Java break inside a loop. See how it works:

#Elixir
try do
letter = "c"
Enum.each ["a", "b", "c", "d"], fn(x) ->
if letter == x, do: throw(x)
end
catch
x -> IO.puts x
end

The tricky thing about Elixir'stry/catch/rescue/after is that any variable change or new declaration inside its scope do not affect the outer scope(like functions). The block also returns values like if. To clear things up, here goes an example:

#Elixir
what = "before try"
try do
what = "started try"
raise "houston we have a problem"
rescue
e ->
what = "rescue"
after
what = "after"
end
#will print "before try"
IO.puts what

One nice Java feature absent in Elixir is the try-with-resource. For example, if we open a file we need to explicitly close it, like the old days before Java 7. Some people might argue that when an Elixir process dies, it will close the files. Even so this is not a option for long running processes.

Conclusion

Even little things like if/else or a simple loop can be very contrastive when we play with another language/paradigm. So far are just the basics, there is much more to cover.

On the next episodes I will start covering more interesting concepts like pattern matching, polymorphism, parallelism and concurrency.

Hope you have like this one. Let me know if you have any suggestion or comment =).

--

--

Jusabe Guedes
SkyHub Labs

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