This evening I came across this tweet:

which shows one of the most classic “Scala WAT” snippets.

Seasoned Scala developers usually know the answer, but I think it’s easy to underestimate the number of Scala features that play together to produce this spectacularly unintuitive result.

Scala developer, puzzled by their beloved language

First of all, the answer is "falsed".

Yup. You didn’t misread: it’s exactly the string "falsed".

Let’s see how that’s even possible.

“Synthetic” apply

This is a core feature of Scala, although I don’t think it has an official name, so I arbitrarily called it “synthetic apply”. The official spec mentions it as follows:

If f has some value type, the application is taken to be equivalent to f.apply(e1,…,eme1,…,em) , i.e. the application of an apply method defined by f. The value f is applicable to the given arguments if f.apply is applicable.

(extract from https://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html)

In other terms, this means that you can take any value f and apply it to some arguments as you would do with a function:

f("some", "arg")

The compiler will automatically treat it as:

f.apply("some", "arg")

provided that the value f has a method apply defined.

In our example, this means that List("a", "b", "c").toSet() + "d" gets interpreted as:

List("a", "b", "c").toSet.apply() + "d"

Nullary methods

“But why? toSet is a method, so why isn’t it called directly instead of inserting .apply ?”

Good question! It turns out that Scala allows “nullary” or “parameterless” methods, i.e. methods that don’t have a parameter list. This is different than methods that have an empty parameter list!

You can’t pass parameters (not even empty) to a nullary method, so this won’t compile:

def nullaryMethod = 42

nullaryMethod // 42

nullaryMethod()

^

error: Int does not take parameters

If you squint really hard, you’ll see our friend .apply at work there: nullaryMethod is already an Int before you add () to . Adding () causes the compiler to try to “call” Int , but that doesn’t make sense because… Int does not take parameters .

Back to our example, you can verify that the method toSet of List is a nullary method. So when we’re adding () , we are already dealing with a Set. That’s why the compiler inserts .apply for us.

What does .apply of Set do? Reading the documentation we discover that it is an alias for contains .

Adapted arguments

Fair enough, we have written a convoluted and unexpected version of contains .

But what’s the element we’re testing against? The parameter list is empty and apply clearly takes one parameter. That MUST be a compiler error, right?

The code above — unfortunately — compiles, albeit with a deprecation warning starting from Scala 2.12.

The feature you’re looking for is called something along the lines of “adapted arguments”, and often divided in two: “auto-tupling” and “unit insertion”. This feature is not in the spec (as far as I know) and it’s implementation-defined. This feature can automatically adapt arguments in case they don’t precisely match their arity.

For example:

def someMethod(someTuple: (Int, String)) = someTuple

someMethod((1, "a")) // ok

someMethod(1, "a") // surprisingly still ok, arguments get adapted into a tuple!



This has even more surprising consequences when the parameters are missing:

def mirror[A](a: A) = a

mirror(1) // 1

mirror("a") // "a"

mirror() // () <- wait, wat?



In the last line, the compiler decided that we truly wanted to use () (the literal value for Unit ), but we felt too lazy to write mirror(()) , so it inserted the () for us, and inferred Unit for the A type.

Back to our example, it’s now clear that we’re doing:

List("a", "b", "c").toSet.apply(()) + "f"

For the record, List("a", "b", "c") does not contain any element () so the result of that expression is false .

Type inference

Oh, by the way, if you’re wondering why we can check that a Unit does not belong to a Set[String] (which should rightfully be a type error), the answer is that we’re not really dealing with Set[String] at any point.

toSet is defined as def toSet[B >: A]: Set[B] , which allows for a List[A] to become a Set[B] as long as B is supertype of A . The problem with our example is that the inference engine postpones the decision about what B should be until we hit on that apply(()) , at which point we’re trying to unify String and Unit , resulting in a glorious Any ! You can verify this yourself with a REPL:

scala> List(“a”, “b”, “c”).toSet.apply _

res0: Any => Boolean = $$Lambda$1190/892135447@716e6fa5

Let’s then annotate the proper types:

List("a", "b", "c").toSet[Any].apply(()) + "d"

which ultimately results in

false + "d"

any2stringadd

The mystery is almost solved, but we’re left with one final question: why can we “sum” a Boolean and a String ? Do all values in Scala define a magical + method that accepts a String as a parameter?

Well, almost… The standard library defines an implicit class called any2stringadd which adds that + method on literally any type, which will happily call String.valueOf (a null-safe version of toString )

(Note that this is different from the + method defined on String , which doesn’t require implicit conversions)

To wrap it up, this is our nasty snippet in its full glory:

new any2Stringadd(List("a", "b", "c").toSet[Any].apply(())) + "d"

which (obviously!) results in the string "falsed" .

Should I quit my job?

If programming languages quirks terrify you, probably.

Jokes aside, every programming language has its scary and frankly hilarious bits, and this shouldn’t automatically throw us off.

I’ve been a Scala programmer for 5 years, and I’ve never encountered anything like this in the wild, although some features of the languages will eventually bite you if you combine enough of them.

On the bright side, it looks like this specific example will be even harder to reproduce in the next versions of Scala:

In the meantime, I suggest to at least enable the compiler flag -Yno-adapted-args to get compiler errors on auto-tupling and unit insertion.

—

So, let’s be positive! And — if anything — you can now tell your fellow Scala developers a funny Scala quirk and explain it to them ;-)

I hope you’ve enjoyed this impromptu blog post!