Skip to content

Tutorial ~ Example (Part 1)

Vlad Ureche edited this page Jun 11, 2015 · 15 revisions
ildl logo This section will transform the code shown in the introduction using the `ildl-plugin`. If you have either the demo virtual machine or have set up `ildl-plugin` locally, you can use the `ildl-scalac` runner script to execute the code on your machine:
$ cd ildl-plugin
$ mkdir -p sandbox
$ cd sandbox
$ vim example.scala # or your favorite editor

Let us take a small example:

object Test {
  // "define" type complex based on integer pairs
  type Complex = (Int, Int)

  // add the addition and multiplication operation to complex numbers
  implicit class IntPairAsComplex(val p1: Complex) extends AnyVal {
    def +(p2: Complex): Complex = (p1._1 + p2._1 , p1._2 + p2._2)
    def *(p2: Complex): Complex = (p1._1 * p2._1 - p1._2 * p2._2,
                                   p1._1 * p2._2 + p1._2 * p2._1)
    // we could define other operations here as well...
  }
  
  // test the output
  def main(args: Array[String]): Unit = {
    val x1: Complex = (3, 5)
    val x2: Complex = (2, 8)
    println(x1 + x2)
    println(x1 * x2)
  }
}

If we compile this code, we get:

$ ildl-scalac example.scala # invoke the Scala compiler + ildl-plugin
$ ildl-scala  Test          # invoke the Scala runner + ildl-plugin
(5,13)
(-34,34)

If running the ildl-scala command produces:

$ ildl-scala Test No such file or class on classpath: Test

> Then you need to unset the `CLASSPATH` environment variable:
> ```
$ unset CLASSPATH
$ ildl-scala Test
(5,13)
(-34,34)

(since we do not use ildl in this section, scalac and scala can be used equally well)

So far, so good. Let's now assume we heard from a fellow programmer that we could encode our complex numbers as long integers. The first step would be to change the alias:

  type Complex = Long

Now, suddenly, none of our code compiles anymore:

$ ildl-scalac example.scala
example.scala:7: error: value _1 is not a member of Test.Complex
    def +(p2: Complex): Complex = (p1._1 + p2._1 , p1._2 + p2._2)
                                      ^
...

example.scala:16: error: type mismatch;
 found   : (Int, Int)
 required: Test.Complex
    (which expands to)  Long
    val x2: Complex = (2, 8)
                      ^
14 errors found

An instinct would be to define implicit conversions from what we previously had, pairs of integers to long integers and back:

  // implicit conversions:
  import language.implicitConversions
  implicit def intPairToComplex(p: (Int, Int)): Complex = 
    (p._1.toLong << 32l) | (p._2.toLong & 0xFFFFFFFFl)
  implicit def complexToIntPair(c: Complex): (Int, Int) = 
    ((c >>> 32).toInt, (c & 0xFFFFFFFF).toInt)

Great, with these implicit conversions, our program compiles again:

$ ildl-scalac example.scala
$ ildl-scala  Test
21474836493
146028888104

But wait! What are those results?!? Those are not our resulting complex numbers! Of course, we need to convert back to complex numbers before printing:

    println(complexToIntPair(x1 + x2))
    println(complexToIntPair(x1 * x2))

Now, with these lines, our program became twice as large:

object Test {
  // "define" type complex based on integer pairs
  type Complex = Long

  // add the addition and multiplication operation to complex numbers
  implicit class IntPairAsComplex(val p1: Complex) extends AnyVal {
    def +(p2: Complex): Complex = (p1._1 + p2._1 , p1._2 + p2._2)
    def *(p2: Complex): Complex = (p1._1 * p2._1 - p1._2 * p2._2,
                                   p1._1 * p2._2 + p1._2 * p2._1)
    // we could define other operations here as well...
  }

  // implicit conversions
  import language.implicitConversions
  implicit def intPairToComplex(p: (Int, Int)): Complex = 
    (p._1.toLong << 32l) | (p._2.toLong & 0xFFFFFFFFl)
  implicit def complexToIntPair(c: Complex): (Int, Int) = 
    ((c >>> 32).toInt, (c & 0xFFFFFFFF).toInt)

  // test the output
  def main(args: Array[String]): Unit = {
    val x1: Complex = (3, 5)
    val x2: Complex = (2, 8)
    println(complexToIntPair(x1 + x2))
    println(complexToIntPair(x1 * x2))
  }
}

Let's compile and run now:

$ ildl-scalac example.scala
$ ildl-scala  Test
(5,13)
(34,40)

Wait, what? The first time we ran the example, the results were (5,13) and (-34,34). The addition seems okay, but what about multiplication? Well, long integers have + and * operations of their own, so the IntPairAsComplex methods are not being invoked anymore. And while the long integer addition corresponds to some extent to the encoded complex number addition (there are corner cases), the multiplication does not correspond at all.

We started trying to transform a toy example, but even for this toy example refactoring has become a nightmare: it's both tedious and error-prone, even with the advanced features of Scala, such as implicit conversions. Furthermore, we have to manually transform parts of the code, which obfuscates the original intent. Thus, although it was easy to introduce complex numbers, it is much more difficult to transform their representation.

Then next part shows how the ildl-plugin automates the transformation and does it correctly.

From Here:

Frog Work Ahead: Some of the pages of this wiki are in flux. If you can't find what you are looking for, please [file a bug](https://github.com/miniboxing/ildl-plugin/issues).
Clone this wiki locally