tags:

views:

468

answers:

2

I've done some programming in Scala, and I know that, e.g.,

xs map f

is the same thing as

xs.map(f)

but I have no idea how to generalize this syntax to something like ScalaTest's syntax, e.g.,

it should "throw NoSuchElementException if an empty stack is popped" in {
  val emptyStack = new Stack[String]
  evaluating { emptyStack.pop() } should produce [NoSuchElementException]
}

I'm mainly wondering about the things that look like multi-word constructs, namely should produce. It's neat.

+2  A: 

It's reasonably easy because it's normal code - it should is equivalent to it.should hence there must be a value (or method) called it in scope. And there is!

This variable is of type ItWord which exposes a method called should which takes an object of type BehaveWord. These matchers are mixed in via implicit conversions in the ShouldMatchers trait.

ScalaTest is actually extremely well documented with tons of examples and descriptions of how things work.

oxbow_lakes
Thanks for the pointer to the right place in the documentation. Sorry, the `it should` question was actually obvious (not sure how I missed that), but I'm still confused about `_ should produce _`.
Yang
+6  A: 

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.

Bill Venners