Part B

  1. We want to build a DSL for expressing approximate equality, like this:
        val a = 2.0
        val b = Math.sqrt(2)
        val c = b * b
        println(a == c) // prints false
        println(a ~ c ± 1E-12) // should print true because Math.abs(a - c) < 1E-12

    So, we need to define a ~ operator. But we can't add it to the Double class. Instead, define a class

    class ApproxValue(value : Double) {
      def ~(other : Double) = ...  
    }

    and a companion object

    object ApproxValue {
        implicit def double2ApproxValue(d : Double) = new ApproxValue(d)    
    }

    Then a ~ c will compile. What do you know about the result of that expression? (a) It should support ... (b) It needs to store ...

  2. There are two ways of solving this. You could return an ApproxValue with |value - d| or you could return an object of another class that stores value and d. Whichever you chose in 1, add the code for the ± method to that class.

    The method name ± is a bit curious because it is not an ASCII character. However, in Java and Scala, you can use any Unicode character as a method name. (The ± is U+00B1.)

    How do you enter such a thing? That depends on your operating system. In Linux, hit the Compose key (which I mapped to the otherwise useless Caps Lock) followed by + -. You know your operating system—just follow its procedure. Or copy/paste it from this document. Or wimp out and use +- instead.

    Your ± operator should return a Boolean, true if the absolute value of the difference is less than the threshold. What is the code of that method?

  3. Now put it all together. Remember to import the implicit conversion. What is your code? What is the output?

Part B

  1. We want to build a DSL for working with physical units such as
    1.5 kg
    10 m / (s^2)

    To keep things simple, we will support grams, meters, and seconds only. We start with this class:

    class Units(val value : Double, val g : Int, val m : Int, val s : Int)  { 
      // Unit is something else :-)
      // g = grams, m = meter, s = seconds, e.g. new Units(10, 1, 0, -2)
      // is 10 m/s^2
      override def toString() = "" + value +  
        unitToString(g, "g") +
        unitToString(m, "m") +
        unitToString(s, "s")
    
      private def unitToString(u : Int, uname : String) = 
          " " + (if (u != 0) uname + (if (u != 1) "^" + u else "") else "")
    }

    How do you construct an object of type Units that represents the speed of light? How do you construct an object representing 1.5 kg?

  2. Now we want to be able to write 1.5 kg. So, kg should be a method that yields a Units object. A method of which class? It can't be a method of Int. So, let's define a class
    class UnitsValue(value : Double) {
      def g = new Units(value, 1, 0, 0)
      def m = new Units(value, 0, 1, 0)
      def s = new Units(value, 0, 0, 1)
      def kg = new Units(1000 * value, 1, 0, 0)
      def in = new Units(0.0254 * value, 0, 1, 0)  
      ...  
    }

    What is the result of new UnitsValue(1.5).kg()?

  3. Which Scala language construct lets you convert 1.5 into new UnitsValue(1.5)?
  4. Add an implicit conversion to object Units, the companion object to class Units. What is your method?
  5. Now try running
    object Main {
      def main(args: Array[String]) {
        import Units._
        val x = 10.0 in
        println(x)
      }
    }

    (Note that the import is restricted to the body of main.)

    What happens?

  6. Now let's add units. We want the following code to work.
    val x = 10.0 in
    val y = 0.254 m
    val z = x + y
    println(z)

    Supply a method

     def +(other: Units) = 
        if (m == other.m && g == other.g && s == other.s)
          ...
        else
          throw new IllegalArgumentException("Incompatible units " + this + " and " + other)
  7. Change the definition of x to
    val x = 10.0 in + 0.254 m

    What happens? How can you fix it? (This is a bit disappointing, and it demonstrates a common problem with internal DSLs—a leaky abstraction. Users cannot fully comprehend the error messages without knowing about details of the implementation.)

  8. Now define * and / operators for units. For example, we want
    val x = 10.0 m / ((1.0 sec) * (1.0 sec))

    to yield 10 m/s2. (Remember to multiply the values, add/subtract the powers of g, m, s.)

  9. Now try this:
    val x = 10.0 m
    val y = 2.0 * x

    Why doesn't it work? How can you make it work?

  10. Can you make 10.0 m / s^2 compute 10 m/s2? If so, outline a strategy. If not, explain why not.