Liz Douglass

Passing by Name in Scala

with one comment

Last week I wrote a little Scala application on a sunny Sunday afternoon. My aim was to improve my beginner level Scala skills, including my knowledge of the language and the testing tools available.

I hit a snag on one unit test and it took the minds of Ben and Mark to help unravel the mystery…. This is the test:

import org.scalatest.matchers.ShouldMatchers
import org.scalatest.Spec
 
class RobotSpec extends Spec with ShouldMatchers {
  describe("A robot") {
 
    //other tests omitted
 
    it("should be able to turn to the right multiple times") {
      val robot = new Robot(1, 2, North)
      robot.turnRight
      robot.toString should equal("1 2 E")
      robot.turnRight
      robot.toString should equal("1 2 S")
    }
  }
}

Where:

class Robot(var xPosition: Int, var yPosition: Int, var direction: Direction) {
  def turnRight(): Unit = {
    direction = direction.getRightDirection;
  }
 
  //other methods omitted
 
  override def toString(): String = {
    "%d %d %s".format(xPosition, yPosition, direction.abbreviation)
  }
}
 
sealed abstract case class Direction(abbreviation: Char, leftDirection: Direction, rightDirection: Direction) {
  def getLeftDirection: Direction = leftDirection
  def getRightDirection: Direction = rightDirection
}
case object North extends Direction('N', West, East)
case object West extends Direction('W', South, North)
case object East extends Direction('E', North, South)
case object South extends Direction('S', East, West)

Interestingly this test fails with a Null Pointer Exception when we try to turnRight the second time. Printing out some information about the robot’s direction variable inside the turnRight method gives some clues:

On the first call to turnRight:
“direction: North left: West right: East”

On the second call to turnRight:
“direction: East left: null right: null”

Why are the left and right directions for East null? You can see that each Direction case object has a dependency on two other Direction case objects, meaning that we have circular dependencies. In order to evaluate the left direction of East we need North, which in turn references East. What’s interesting for me is how null has been used when to break the circular dependency and allow compilation to succeed.

So how did we get the test passing? The answer was to use pass by name (or call by name):

sealed abstract class Direction(abbreviation: Char, leftDirection: () => Direction, rightDirection: () => Direction) {
  def getAbbreviation: Char = abbreviation
  def getLeftDirection: Direction = leftDirection()
  def getRightDirection: Direction = rightDirection()
}
case object North extends Direction('N', () => West, () => East)
case object West extends Direction('W', () => South, () => North)
case object East extends Direction('E', () => North, () => South)
case object South extends Direction('S', () => East, () => West)

Now the left and right directions are evaluated only when they are used in the turnRight and turnLeft methods.

Advertisements

Written by lizdouglass

December 8, 2009 at 10:38 am

Posted in Uncategorized

Tagged with ,

One Response

Subscribe to comments with RSS.

  1. That’s an interesting problem. Looks like a bug. It’s still present in Scala 2.9.1. Inheriting from a case class now causes a deprecation warning (but fixing that problem doesn’t fix the initialisation order issue).

    I noticed that in your solution you aren’t quite using call-by-name. You are passing function values. Instead you can have:

    sealed abstract class Direction(val abbreviation: Char, left: => Direction, right: => Direction) {
      def leftDirection = left
      def rightDirection = right
    }
    case object North extends Direction('N', West, East)
    case object West extends Direction('W', South, North)
    case object East extends Direction('E', North, South)
    case object South extends Direction('S', East, West)
    

    This way you don’t have to change the definitions of North, West, East and South.

    Steven Shaw

    November 2, 2011 at 8:08 am


Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: