That kind of syntax is method calls in operator notation, but carried forward more than just three tokens. As you already mentioned:
xs map f
means:
xs.map(f)
But you could go further and say:
xs map f map g
which means:
xs.map(f).map(g)
In ScalaTest matchers, for example, you could say:
result should not be null
That gets desugared by the compiler to:
result.should(not).be(null)
This:
it should "throw an exception" in { ... }
gets desugared into:
it.should("throw an exception").in { ... }
The curly braces at the end is really just a way to pass the code in between the curly braces (the test code) into the in method, wrapped as a no-arg function. So all of these are the same idea. Operator notation used twice in a row.
The last one you asked about is a tad different:
evaluating { ... } should produce [IllegalArgumentException]
This gets transformed into:
evaluating { ... }
is, well, evaluated first, because the curly braces give it precedence. So that is a method call, you are calling a method named "evaluating", passing in the code in between the curly braces as a no-arg function. That returns an object, on which should is invoked. So should is a method on the object returned by invoking evaluating. What should actually takes is the result of invoking produce. Here produce is actually a method, which has a type parameter such as [IllegalArgumentException]
. It must be done this way so the Scala compiler can "poor-man's-reify" that type parameter. It passes an implicit "Manifest" parameter into produce that can provide the java.lang.Class
instance for IllegalArgumentException
. When that should method is invoked, therefore, it has a function containing the code passed to evaluating, and a way to find the java.lang.Class
of the exception type put in the square brackets. So it executes the block of code wrapped in a try, catches the exception, compares it with what's expected. If no exception is thrown, or the wrong one, the should method throws a TestFailedException. Otherwise the should method just returns silently.
So, the answer is that line gets desugared into:
(evaluating { ... }).should(produce[IllegalArgumentException] (compilerSuppliedManifest))
And the moral of the story is that high level code like this makes it easier to see the programmer's intent, but often harder to understand how the code actually works. Most of the time in practice all you care about is the intent, but now and then you need to know how something works. In such cases in Scala you can pass -Xprint:typer as a command line arg to the Scala compiler and it will print out a version of your file after all the desugaring has happened. So you can see what's what when you need to.