Comparing Scala’s functional language elements to Java

Balazs Torma

2022. May 16.

I wish I could work on this...

Having used Scala in the earlier part of my career, I grew partial to all the features it provided that helped make my code more concise, and still much less prone to errors than with the other languages that I have used up to that point.

A big part of its allure for me was how elegantly it managed to combine object-oriented and functional programming paradigms. It also provided me with a smooth transition from Haskell back into a less academic world.

 

Anyone who has already encountered at least one functional language knows that there are a few key principles that make a language fit that category. These include the lack of side effects in functions, referential transparency and higher-order functions, to name a few.

It is because of these properties that any functional language lends itself to writing concurrent code without having to fear stumbling into a race condition. Not having to write so much boiler plate is also a plus.

 

It just so happened that I got the opportunity to develop a massively scalable, asynchronous (banking) system with the Akka framework, that is originally written in Scala. The only downside was that I had to use its Java API, as the entire project is developed using that language.

I couldn’t help but compare the two JVM-based languages, and found many things that felt clunky in Java. Thankfully, Java 8 was already fairly widespread, so at least I didn’t have to give up on using lambda-expressions. I always had a nagging feeling that all the ‘hip’ and modern functional elements that eventually got introduced to Java were borrowed from Scala.

 

In this article, we’ll take a look at a few of those features that I think were inspired by Scala. I’ll focus on what was added between Java 11 and 17.

 

Var syntax

Java 10 was extended with the new ‘var’ keyword that enables its user to omit a variable’s type upon declaration. This is already one half of what Scala enables its users, the other half being the val keyword which creates an immutable value that can then be used for more functional-style code.

 

In my opinion, this could be one area where Java should go all the way in mimicking Scala, but that would require fundamental changes to the type system. What the var syntax allows a glimpse into is what is probably the most powerful feature of any functional language: type inference. In order to understand that this isn’t just for convenience’s sake, one must keep in mind the basis of Scala’s multi-paradigm style:

  • Everything is a value, be it a number, a character string or a function. This allows the use of higher-order functions in the language.
  • Everything has a type (pure object-oriented paradigm).

 

The latter of the above 2 principles is why we can work with the assumption that everything is a part of Scala’s type system, there are no primitives! Even ‘Nothing’ is a part of the type system, come to think of it.

 

If everything really has a type, then the compiler is capable of deducing from an operation used in a function what the return type of the function is. For example:

 

def function(x : Int) = x + x

 

You could say that it is common sense that the return type of function be Int. And you’d be right! By the way, the ‘+’ operator isn’t an operator in the sense that it would be in Java, as it isn’t a separate thing from any function. What you see in the example above is just syntax candy. That expression could be written in its ‘real’ form as follows:

 

3.+(5)

 

Now it is obvious that ‘+’ is just a function of Int that accepts a single parameter.

 

case class Example[A, B, C](x: A, y: B, z: C)

 

var foo = Example(“one”, 2, 3.0)

 

This last example shows how powerful type inference can be! It saves us the hassle of specifying the type parameters when creating an Example instance, which is a case class that is somewhat similar to an immutable DTO complete with getters. More on that later. But we shouldn’t just stop there:

 

var foo2 = Example(foo, function, false)

 

Yep, and the Scala compiler didn’t even break a sweat…

 

What this advanced, statically typed system gives a Scala developer is an immense safety net where the burden of proving the code’s validity is mostly on the compiler. It would sure be nice to see Java get to this point as well. Here’s hoping we don’t have to wait too long for that.

Tuples vs. records in Java

Java lacks built-in support for tuples which probably annoyed every developer who ever wanted to return with more than one value at once in a function. Barring the use of third-party dependencies, I never enjoyed having to write one-off classes for this purpose. This however, is a trivial problem in Scala. Tuples form the backbone of any modern (Lisp being one exception) functional language’s type system, so naturally Scala has them out of the box.

 

var tuple = (32, 5.0, “something”)

 

That’s all there is to it! Naturally, type inference makes it easy to declare a tuple too, so no need to declare what value has which type. Unless we disagree with the compiler, of course. I could have used a bigger example of a 10-tuple, or any n-tuple up to a limit of 22. Scala 3 will drop this limitation.

 

Accessing one of the components of a tuple is simple, just call _N on the instance to access the Nth component:

 

println(tuple._3)

 

So far this isn’t that interesting, but tuples in Scala have a more exciting use when processing the result of a function that returns with a tuple.

 

val (publicKey, privateKey) = generateRsaKeyPair()

 

We have a function generateRsaKeyPair that returns with a pair containing the public and private key for a task involving asymmetric encryption. Instead of having to assign a name for the tuple just to immediately throw it away once we’ve extracted its contents, we can use a little bit of pattern matching to save ourselves the hassle.

 

I wouldn’t expect the last trick to manifest itself in Java in the near future, but it would still be nice not having to resort to external libs such as Groovy’s. Sadly, the closest thing to a built-in tuple in Scala is the record keyword previewed in Java 14 and in Java 15. In essence a record is a closer approximation of a Scala case class rather than a tuple, but Java records don’t play a special role in pattern matching. They are a restricted version of classes, with built-in features that spare us having to write (or generate in a capable IDE) the usual canonical constructor, getters, hashCode(), equals() and toString() methods. These are all done for us automatically, which is nice.

 

record Pair(String one, String two) {};

Pair result = callSomeFunction(new Pair(“foo”, “bar”));

System.out.println(result.one());

System.out.println(result.two());

 

In the above form, a record could be declared as a local class and be somewhat similar in purpose to a real 2-tuple. In this example two accessors would be automatically generated whose names match the parameters’ provided when declaring the record type.

 

The implicit equals() and hashCode() methods that we get with a record will show two records as equal if their components’ values are equal. Also, the toString() implementation will return with a string containing the component values along with their names. It isn’t forbidden to write your own constructor for a record, or even add extra validation to the generated one.

 

record Pair(String one, String two) {

Pair { //Extends the generated constructor with validation

if (one.equals(two)) {

throw new SomeException();

}

}

};

 

record Record(Object left, Boolean right) {

private final String another;  // Compile error!

}

 

abstract record Record2(Object field) extends Object // Error!

 

However, it is forbidden to explicitly declare fields in a record, or make a record abstract, or inherit from another class. The superclass of a record is always java.lang.Record. There are a couple more restrictions, but other than that a record behaves just like any other Java class. So it is possible to add our own (static) methods to it, or implement interfaces with it. We can even make it generic, not that that helps in its use as a tuple.

 

I certainly wouldn’t mind using records once the project I’m working on migrates above Java 16, but I still would prefer using native tuples sometime in the future.

Pattern matching in Scala

In a purely functional language, no computation can have any side-effects. This makes simple tasks that actually need side-effects (such as I/O) quite cumbersome, but that is the price that has to be paid for trivial parallelizability.

 

Since no function can modify a global variable, or affect anything outside of its own return value, the flow of a functional program can be described as the composition chain of functions. The first chain in the function could be regarded as the public static void main(String[] args) function known from Java, and the end of that chain could be the last return statement of the program. Every other step in between is an execution of a function, whose result is then used as the input of the next function in the chain, and so on.

 

To ensure that the data of one function cannot possibly be manipulated by another function, only immutable data structures are used. In a typical functional language, algebraic data types, also known as ADTs are used for this purpose. They serve as a basic concept in type theory. Simply put, an ADT is formed as a combination of other types that act as its components.

For example, a tuple of types (A, B, C) defines a fixed order of its components by essentially being a Cartesian product of said types: A×B×C.

 

// Syntax sugar-free method for creating a tuple in Scala:

val triplet = new Tuple3[A, B, C](new A(), new B(), new C())

 

Lists of type A on the other hand can be assembled as a disjoint union: these ADTs can take different forms (types), but only one at a time. In this case, a list can either be an empty list, or an element (head) and the rest of the list (tail). A more easy to understand example for a disjoint union would be the Optional type, where the optional value is either nothing, or the extant value.

 

val emptyList = List[Int]()

println(emptyList == Nil)    // “true”

val list = 1 :: (2 :: (3 :: Nil))

println(list)    // “List(1, 2, 3)”

 

Notice how in Scala a list can either take the form of Nil, or List which is merely an empty list with a few elements glued to it from the left one-by-one. The reason behind using algebraic types that can be assembled this way is because they can be disassembled just as easily. That’s exactly what we’re going to be looking for when processing them!

 

So how do we write a conditional expression where we can consume such data structures in a functional language? ‘If’ statements cannot be used, since they can have side effects, but something a bit closer to a switch-case might do the trick. This is known as pattern matching, where we can give requirements on what structure and content the input data must have in order to execute one branch of the code. It is possible to add further boolean expressions here, also known as guard statements, that decide where the program flow should go next.

 

In the previous section I’ve mentioned the so-called case classes. These are the closest that Scala has to ADTs and provide another facet of what makes Scala in part a functional language. They are similar to value-based classes (such as Java Optional), and even more similar to the Java records discussed earlier. Other than that they have the following features:

  • There’s no need to use the new keyword upon instantiation because of an apply method that is available by default.
  • All its parameters upon creation are immutable and public.
  • Instances of case classes are compared by structure and not by reference. You could say that case classes come with an equals method built-in that takes into account the contents of an instance (quite similarly to Java records).

 

This makes case classes ideal for pattern matching as demonstrated below.

 

 

abstract class Animal

case class Dog(legs: Int, eyes: Int, teeth: Int) extends Animal

 

case class Octopus(tentacles: Int) extends Animal

 

def patternMatching(animal: Animal): Boolean = {

animal match {

case Dog(_, _, 3) => true

case Dog(legs, eyes, _) if eyes * 2 == legs => true

case Octopus(1) => true

case _ => false

}

}

 

println(patternMatching(Dog(1,1,3)))  -> true

println(patternMatching(Dog(4,2,10)))  -> true

println(patternMatching(Dog(6,2,42)))  -> false

println(patternMatching(Octopus(2)))  -> false

 

 

This somewhat zany example shows a simple pattern matching with 4 possible cases. Note the _ wildcard character which can also be used for giving a final, default case. Without that, a scala.MatchError exception would be thrown if an input doesn’t match any of the first 3 cases.

It is the second case that contains the guard statement: any Dog instance will match that has twice the number of legs compared to the number of its eyes. Since we don’t care about the dog’s teeth in this case, it can be anything, so a wildcard can be left in the pattern as a placeholder.

 

So in essence, pattern matching in Scala lets us make decisions based on the input’s type, structure, and value. This input however, doesn’t necessarily have to be a case class. It can also be a simple list of integers:

 

def patternMatchList(list: List[Int]): Boolean = {

list match {

case 1 :: 2 :: 3 :: xs => false

case 1 :: xs => true

case _ => false

}

}

 

println(patternMatchList(List(1,2,3,4,5))) -> false

println(patternMatchList(List(1,3,4,5,6))) -> true

 

The ‘::’ infix function (operator) takes an element as its first operand and a list of elements with the same type as the second operand. It then appends the given element as its new ‘head’ to the list. One could argue that it is used in a reverse manner in this instance by describing how to deconstruct the list to be matched, instead of constructing it. This is why this kind of pattern is called a deconstruction pattern.

 

Pattern matching in Java

 

Fortunately, the changes introduced in Java 16, then 17 contain a form of pattern matching that isn’t too far behind what is possible with Scala. There are 2 proposals that have made it into Java that have to do with pattern matching:

  • JEP 394 extends the capabilities of the instanceof operator for a more refined matching on an input’s type.
  • JEP 406 augments switch statements with pattern matching that is quite similar to what is used in the new instanceof.

 

With these changes the original switch statement in Java is repurposed almost as a full-blown pattern matching language element. It also has an alternate version, a switch expression since JEP 361 that is similar in concept, but differs in how it functions. It is also more suitable for pattern matching as we’ll see later on.

 

Let’s take a look at the example code in the JDK documentation linked above. Firstly, instanceof operators get to have a bit of syntax candy. Before, if you wanted to check an object’s type in a condition, you would have inevitably had to typecast the inspected object:

 

if (obj instanceof String) {

String s = (String) obj;

System.out.println(s);

}

 

But now it’s as simple as:

 

if (obj instanceof String s) {

System.out.println(s);

}

 

This syntax candy has a bit more to it than meets the eye. The instanceof operator from now on takes a type pattern instead of a type as its second operand. This consists of a predicate and a single pattern variable. In the above example the former is String, while the latter is ‘s’. The problem is how to determine the scope of the pattern variable. If we could only use it inside the conditional block, it would be a lot less useful than it really is. We couldn’t write a more complex conditional expression with this new instanceof operator. So the rule is that the pattern variable is in scope when the pattern has definitely matched.

 

if (obj instanceof String s && s.length > 5) { // Correct!

System.out.println(s);

}

 

if (obj instanceof String s || s.length > 5) {      // Compile error!

System.out.println(s);

}

 

The type pattern hasn’t definitely matched when the ‘or’ logical operator is used because the pattern matching could be unsuccessful at that point in evaluation. That’s why the pattern variable s isn’t necessarily defined in the second half of the conditional expression.

 

Aside from this rule, pattern variables act just as local variables, and can be assigned to. They can even shadow other declarations, but only inside their peculiar scope which can cause tricky situations. What if in the example that caused a compile error, there already was a previously declared s variable? Then the code would compile, but could show surprising behavior for anyone who isn’t familiar with how pattern variables work.

 

Next up, the new pattern matching switch expression:

 

record Point(int i, int j) {}

enum Color { RED, GREEN, BLUE; }

 

static void typeTester(Object o) {

switch (o) {

case null     -> System.out.println(“null”);

case String s -> System.out.println(“String”);

case Color c  -> System.out.println(“Color with ” + Color.values().length + ” values”);

case Point p  -> System.out.println(“Record class: ” + p.toString());

case int[] ia -> System.out.println(“Array of ints of length” + ia.length);

default       -> System.out.println(“Something else”);

}

}

 

Before, only primitives, their boxed form, strings or enums could be used in the selector expression (‘o’ in the code above). From now on, the type of the selector expression can be of any reference type. The case labels are also not restricted to being only constants, but can now be patterns similar to instanceof’s type patterns.

 

These changes bring with them new things to consider. If the input is null, then the switch statement shouldn’t necessarily throw a NullPointerException as before. Instead, a new special null label can be used to handle this everyday occurrence. If we omit the use of this new type of label, then an NPE will still be thrown if the input is null! This is because the default label doesn’t match a null selector. Sadly, this rule exists for backwards compatibility.

 

Another interesting change is how the type patterns in the labels must cover all possible types of the selector expression. If the selector is of type Object, then that means that the labels together must cover every subtype of Object. If we remove the default label from the example, the code will produce a compile error since the union of the remaining labels’ type coverage isn’t enough for covering all of Object’s subtypes. This means that a pattern matching switch expression must always yield a result. This differs from Scala’s solution where only an exception is thrown if the input isn’t matched by any of the patterns.

 

Remember the quirkiness of the pattern variable’s scope in the new instanceof expressions? The underlying rule remains the same, but we must apply it in a way that isn’t self-explanatory whenever a switch statement is used instead of a switch expression. In the expression form, the blocks after the arrows (‘->’) make it easier to determine the pattern variable’s scope. With the statement groups of a switch statement’s label, it isn’t so simple.

 

static void test(Object o) {

switch (o) {

case Character c:

if (c.charValue() == 7) {

System.out.print(“Ding “);

}

if (c.charValue() == 9) {

System.out.print(“Tab “);

}

System.out.println(“character”);

default:

System.out.println();

}

}

 

Anyone accustomed to the old switch expression would say that due to the lack of a break expression at the end of the first statement group, there should be a fall-through to the default label’s statements. This causes a problem with the c pattern variable’s scope. In principle it should only be in scope where the pattern has definitely matched. That is true inside the first statement group, but false after the default label. And that’s how it works, seemingly in contradiction with the fall-through of the code execution. This might be the best reason to start using switch expressions exclusively for pattern matching.

 

static void test(Object o) {

switch (o) {

case String s && (s.length() == 1) -> …

case String s                      -> …

}

}

 

Thankfully, guarded patterns weren’t left out in Java 17 which really puts the cherry on top of the pattern matching capability of the language. The guard is a run of the mill boolean expression that is glued to a type pattern with the ‘&&’ notation that shouldn’t be confused with the logical and operator. In ambiguous situations, using parenthesized patterns can help:

 

var boolean1 = e instanceof String s && s.length() > 1;

var instanceOfExpr = e instanceof String s;

var isLongerThan1 = s.length > 1;

var boolean2 = instanceOfExpr && isLongerThan1

// boolean1 and boolean2 are equivalent!

 

var guardedPattern = e instanceof (String s && s.length() > 1);

// boolean1/boolean2 and guardedPattern differ in meaning!

 

As seen with the guardedPattern boolean expression above, a pair of parentheses can make all the difference between a simple type pattern combined with a condition, and a guarded pattern. “But what difference is there really?” You could ask.

Again, the pattern variable’s scope is the key, and of course a simple pattern with a boolean expression cannot be used as a switch expression’s label (but that wouldn’t be an ambiguous situation anyway).

 

Obviously, a pattern variable’s scope inside a guarded pattern with the form “pattern && expression“ will encompass both the pattern and the expression.

 

Another problem to consider is when do pattern labels dominate each other. Originally, switch statements’ labels are evaluated in the order they are written. The preceding label would dominate the one that followed it and so on. In the case of a pattern matching switch statement however, one has to be careful not to put a pattern whose type coverage fully covers a later pattern’s. Or in other words, a pattern label cannot be a subtype of another label that precedes it. If one dominates another for this reason, or any other that will be mentioned later, then a compile error will occur.

 

static void error(Object o) {

switch(o) {

case CharSequence cs ->

System.out.println(“A sequence of length ” + cs.length());

case String s ->  // Pattern is dominated by previous pattern!

System.out.println(“A string: ” + s);

default -> {

break;

}

}

}

 

This requirement ensures that all pattern labels will be in subtype order. Also, a pattern p will always dominate a guarded pattern with the same pattern p before the guard expression. So guarded patterns also have to be added to a switch statement with care.

 

Summary

 

Going through these three topics reminded me of the fond memories I have of working with Scala on my master’s thesis. That language is so powerful that I could create a domain specific language (DSL) for compiling OpenCL kernels for GPGPU by using nothing more than the core features and syntax candy that the language already has! Mind you, the OpenCL C compilation was done with an external tool.

 

I wish it were more widely used, but if Java eventually assimilates all the features that make Scala so great, I honestly wouldn’t mind. Next up, I’d like to see Scala’s elegant and strictly static type system adopted so I can finally use type inference in my daily work. Maybe in Java 19? Who knows…

Related job opportunities

Java Backend Developer More

Full Stack Developer More

Meet Our Team

Event
Past events

What keeps a senior developer awake?

2022. June 9. - 2022. June 9.

University of Obuda, 18.00

What keeps a senior developer awake?

Event
Past events

Data Management 13.0

2022. May 12. - 2022. May 12.

, 6pm

Interested in Data Management? Then we have a good program idea for you! Register for our Data Management 13.0 MeetUp on May 12, 2022. Our presenters will start with Gyongyi, who will talk about Data Management solutions - followed by a panel discussion with Laszlo Csite, Lorand Peter Kasler, Marton Kelemen and Jozsef Szoke. Finally, UpScale's Zoltan Laszlo will present our TiDB NewSQL POC.

Event
Past events

Modern technologies in an Enterprise environment

2022. June 3. - 2022. June 3.

, 9am

Modern technologies in an Enterprise environment UpScale presentation Financial & Corporate IT 2021 Portfolio hybrid conference June 3, 2021 - Kempinski Hotel Corvinus Building on UpScale's four years of experience, the presentation will show, through specific case studies, how to make modern (even open source) technology solutions enterprise compatible.

Event
Past events

AI – the solution

2021. October 13. - 2021. October 13.

, 9am

AI - the solution UpScale presentation Banking Technology 2021 / Portfolio conference October 13, 2021 - Corinthia Hotel Key points of the presentation: Artificial Intelligence (AI) can be a real competitive advantage for large corporates / large financial institutions with an extraordinary amount of data at their disposal. However, AI is more than an analytical tool and its implementation requires a proper approach. The modernisation of legacy systems, the next generation databases, the holistic use-cases and AI's well-defined role in business processes are all important elements of a well thought-out strategy.

Contact us!

We offer professional solutions to every problem. Don’t hesitate to contact us!

Contact

Join our team!

Check out our open positions and if you’re interested, feel free to contact us!

Apply