fredag, januari 04, 2008

Scala magic

Oh, right. This is a perfect example of Scala magic that I REALLY don't understand yet. If there are any Scala wizards reading, please explain!

This line of code is from a test cases written in specs:
      runtime.ev("\"hello world\"")._with(interpreter)
must be_==(runtime.stringFor("hello world"))
Runtime is a Scala class and so is interpreter. I added println()'s before and after, and at different methods in this statement. Now, the baffling thing, the really strange thing (at least in my mind), is that the left hand side of the must, gets evaluated THREE times. But this code only run once. How can that happen? What does Scala do here? (And must doesn't seem to be a special method. I looked at the implementation, and it's just a regular method on a trait, mixed into the Scala Object.

Someone please explain this! =)

5 kommentarer:

David R. MacIver sa...

If you take a look at the type signature of must you'll see it's

def must[S >: T](m: => Matcher[S]): Boolean

The => prior Matcher[S] means that you're not actually passing a Matcher, you're passing an expression which evaluates to a Matcher. Every time its value is used it will be reevaluated. This can be quite handy for e.g. implementing control flow operators. For example while can be considered as a method while(test : => Boolean) (body : => Unit).

These are called call-by-name parameters. Morally they're just a bit of syntactic sugar over passing a () => Matcher[S], but I find their exact semantics a bit flaky.

Ola Bini sa...

David: Yeah, I understand that part of it. My problem is this - the matcher is the right hand side of the must. But the part that is reevaluated 3 times is the LEFT HAND side. And I couldn't find any way a method can reevaluate it's receiver-expression to an arbitrary level.

David R. MacIver sa...

Oh, well "must" is a method on Assert. So what's happening is that the left hand expression is being boxed into an Assert object by an implicit conversion defined in AssertFactory.scala.

Note that value is typed as => A, and that Assert takes a =>A argument in its constructor, so the object match is dispatched on is simply evaluating its constructor argument multiple times.

This does seem a little non obvious I have to admit. :-)

Eric sa...

Yes, there's a bit of magic in specs involving implicit defs.

For example:
a must be_==(b)

can be rewritten as:

theValue(a) must be_==(b) (in the AssertFactory class). The "theValue" method will transform a to an Assert object owning the "must" method as David said.

Now, having a being evaluated 3 times is a flaw! It is currently evaluated once for the assertion itself and twice to build the ok/failure messages. You can call that the "side-effect" tax!

Ok, seriously I am going to correct this because this can clearly lead to issues when doing state-based specs/tests.

Stay tuned for v.1.1.4!

Eric.

Eric sa...

I have released a new version (1.1.4). I hope it's ok now.

Eric.