How does Scala work it’s Magic?
Hello everyone, today we’re gonna talk a little about the said “automagics” of Scala, how it does what it does, and generates Java code in the end. Because if you think about it, if all that it does is generate Java classes, then we should just do it directly with Java right? Wrong. I’m not going to go really deep in the complex concepts of the language that is beyond the scope if this post, I’m just going to show a little of what it does, and maybe that will clear some of your heads on it’s inner workings. It surely did it for me the first time I saw something like what I’m going to present here. And also, show a little bit of what can be done with the Scala Collections.
I’m going to show some examples of Java code, from situations we can run into in our normal dev day, but of course, the examples posted here are going to be simple for teaching reasons. Shall we begin?
The first situation is to alter all the items in a given list. How can we execute a function to all it’s items? We just iterate over it and execute one by one, right?
for(String str : strings) { // execute function on str }
And what if we need to do something similar on another list?
for(Integer number : numbers){ // execute function on number }
In the end the only thing that changes is the function that is going to be applied on each item of the list, the rest of the code is the same, and you have to agree that it’s annoying to keep repeating code everywhere. Check out the full example:
public static void main(String[] args) throws Exception{ List<String> strings = Arrays.asList("Simple", "Java", "String", "List"); List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<String> newStrings = new ArrayList<String>(); for (String str : strings){ newStrings.add(str + "Verbose"); } List<Integer> newNumbers = new ArrayList<Integer>(); for (Integer number : numbers){ newNumbers.add(number * number); } System.out.println(newStrings); System.out.println(newNumbers); }
Now I ask you, how could we avoid this repetition? How could we express only what we want, in the highlighted lines?
Well, we could put it all inside a method, but… how could we avoid the repetition if we can’t send a method as an argument to another method?
I bet some of you already know the answer, an anonymous inner class. That’s right, it’s not the prettiest solution, but it’s a solution nonetheless. With that we can simply alter the function that is to be applied to each item. You guys ready? Here we go.
First we create our interface, named Transformer:
public interface Transformer<F, T>{ public T transform(F f); }
It has only one method signature, that recieves an F, and returns a T, nothing new here.
And now we create our own list, that will use this interface in it’s transform method.
public class MyList<E> extends ArrayList<E>{ public MyList(){} public MyList(E... elements){ for(E element : elements){ add(element); } } public <T> MyList<T> transform(Transformer<E, T> transformer){ MyList<T> myList = new MyList<T>(); Iterator<E> ite = iterator(); while(ite.hasNext()){ myList.add(transformer.transform(ite.next())); } return myList; } }
And now all we have to do is tell the transform method how to transform our boject, see the code example now rewritten using our new structure
public static void main(String[] args){ MyList<String> strings = new MyList<String>("Simple", "Java", "String", "List"); MyList<Integer> numbers = new MyList<Integer>(1, 2, 3, 4, 5); MyList<String> newStrings = strings.transform(new Transformer<String, String>(){ public String transform(String str){ return str + " Verbose"; } }); MyList<Integer> newNumbers = numbers.transform(new Transformer<Integer, Integer>(){ public Integer transform(Integer num){ return num * num; } }); System.out.println(newStrings); System.out.println(newNumbers); }
Now in each list, we avoid code repetition and define the function that we want to be applied to each of it’s elements. But of course, using anonymous inner classes, it doesn’t look so pretty.
Now it’s where things get simple, in Scala we have a function called map that does exactly that, it applies a given function to each item on a list, and returns the modified list (it’s good to remember that the original list remains unaltered). See the same example now written in Scala:
object Main extends App{ val strings = List("Awesome", "Scala", "String", "List") val numbers = 1 to 5 val newStrings = strings.map(_ + " Clean") val newNumbers = numbers.map(x => x * x) println(newStrings) println(newNumbers) }
Much cleaner code, right? That’s because Scala works it’s inner magics under the hood, and takes the responsability of doing all that we did before away from the developer.
Oh, I see the difference. But Object? App? What is that?
If you don’t know what it is, don’t worry about it, it’s not important for this post, just think of that block of code as a plain old void main Java method.
Alright, but how does Scala make it all possible?
What Scala does automagically is what we did in the previous Java code, in an anonymous inner class it defines a function to be applied to each element inside our list, interesting, huh? Of course other thing get tinkered around, adaptations and optimizations occur during the compiling process, but for what we care here, that’s pretty much what happens.
Let’s see another example, filtering lists. In this example, the only thing that changes is the criteria of what should be added to the filtered list, the rest of the code is repetition, repetition, repetition.
Again, we start with the Java example:
public static void main(String[] args){ List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<Integer> evenNumbers = new ArrayList<Integer>(); for(Integer number : numbers){ if(number % 2 == 0){ evenNumbers.add(number); } } List<Integer> oddNumbers = new ArrayList<Integer>(); for(Integer number : numbers){ if(number % 2 != 0){ oddNumbers.add(number); } } System.out.println(evenNumbers); System.out.println(oddNumbers); }
Now we’ll have a new interface, to help us with the filtering method, inginously named Filter.
public interface Filter<E>{ public boolean matchesRequirement(E e); }
And how we already have our super-special list, all we need to do is add this new method to it.
public MyList<E> filter(Filter<E> filter){ MyList<E> myList = new MyList<E>(); Iterator<E> ite = iterator(); while(ite.hasNext()){ E element = ite.next(); if(filter.matchesRequirement(element)){ myList.add(element); } } return myList; }
Great! We can use our filter. Look at how we can do the same filtering as before, but avoiding repeated code.
public static void main(String[] args){ MyList<Integer> numbers = new MyList<Integer>(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); MyList<Integer> evenNumbers = numbers.filter(new Filter<Integer>(){ public boolean matchesRequirement(Integer number){ return number % 2 == 0; } }); MyList<Integer> oddNumbers = numbers.filter(new Filter<Integer>(){ public boolean matchesRequirement(Integer number){ return number % 2 != 0; } }); System.out.println(evenNumbers); System.out.println(oddNumbers); }
Now back on the Scala side of things, we also have a ready-to-use method that does all of this, not coincidentally named filter, where we send a function as argument, that will be used to tell which elements get in the new list, and which don’t. Here’s the example:
object Main extends App{ val numbers = 1 to 10 val evenNumbers = numbers.filter(_ % 2 == 0) val oddNumbers = numbers.filter(_ % 2 != 0) println(evenNumbers) println(oddNumbers) }
Once again we see how easy it is to work with Scala collections, all the manual labor isnot manual anymore, and now we can focus more on the business logic we’re working with, and less on implementation details.
So what about these interfaces? Does Scala create a new one for each function I want to implement?
Nope. Scala has a series of pre-defined functions that are ready to be used in these situations, it chooses how to compose them based on the number of arguments.
In case you want to see the official documentation, you could check out Function, Function1, Function2 e PartialFunction.
Now what if I want to transform all the items in my number list, multiplying them by themselves, and in this new list, filter all the ones that are divisible by 5?
Huh? Why would any one want to do that?
Alright, it’s a wacky example, but it’s just to compare code snippets, see how it’d look in Java with our cool new list:
public static void main(String[] args){ MyList<Integer> numbers = new MyList<Integer>(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); MyList<Integer> newNumbers = numbers.transform(new Transformer<Integer, Integer>(){ public Integer transform(Integer n){ return n * n; } }).filter(new Filter<Integer>(){ public boolean matchesRequirement(Integer number){ return number % 5 == 0; } }); System.out.println(newNumbers); }
We can easily see that the more we want to explore this with Java, the more verbose our code gets, so I really wouldn’t recommend anyone to do things like this. It would only add unnecessary complexity, in Scala on the other hand…
object Main extends App { val numbers = 1 to 10 val newNumbers = numbers.map(n => n * n).filter(_ % 5 == 0) println(newNumbers) }
Things like this are done with much greater simplicity, it’s almos natural to work like this, so in this situation, I’d recommend it be done like this, no need for iterating loops repeated again and again when we’re working with Scala.
I hope this post has cleared out some of the mystical inner workings of Scala, and shown a bit of how easy it is to work with it’s collections. Even though I only showed you some basic stuff.
Using Java 8 in the samples would have been made a difference, right? ;)