Friday, April 22, 2011

Great way to learn the Scala API: Scala interpreter

If you have already learned the language construct of Scala, the next step is to learn its API. If you're a Java programmer, usually you'll try to do that by writing small Scala programs in an IDE. However, there is a much better way: using the Scala interactive interpreter. This way you can inspect the effect of each line of code as soon as you press Enter. This is quite non-obvious for Java programmers because there is no such thing in the Java tool set.
For example, you'd like to learn about the Seq trait in Scala. So you open the API page of it and find a long list of methods. Let's say you're interested in learning the behaviors of the following methods:

/** Appends all elements of this sequence to a string builder. The written text consists of the string representations (w.r.t. the method toString) of all elements of this sequence without any separator string. */
def addString(b: StringBuilder): StringBuilder

To do that, you issue the "scala" command from a shell/command prompt and then explore the method like:

kent@dragon:~$ scala
Welcome to Scala version 2.8.0.r20327-b20091231020112 (Java HotSpot(TM) Server VM, Java 1.6.0_20).
Type in expressions to have them evaluated.
Type :help for more information.

scala> val a = List("a", "b", "c")
a: List[Int] = List(a, b, c)

scala> val b = new StringBuilder
b: StringBuilder =

scala> b.append("hi")
res0: StringBuilder = hi

scala> a.addString(b)
res1: StringBuilder = hiabc

From the experiment, it is clear that the addString() method will append all the elements of the Seq to the string builder.
Let's consider another. You're interested in:

/** Multiplies up the elements of this collection. num is an implicit parameter defining a set of numeric operations which includes the * operator to be used in forming the product.
*/
def product[B >: A](num: Numeric[B]): B

So, try it in the Scala interpreter:

scala> val a = List(3, 5, 2)
a: List[Int] = List(3, 5, 2)

scala> a.product
res2: Int = 30

So it seems to work fine. Possible to override the * operator? From the API doc it is clear that the default being used is the IntIsIntegral object:

package scala.math

class Numeric {
object IntIsIntegral extends IntIsIntegral with IntOrdering {
...
}
}

So, the first try is:

scala> import scala.math.Numeric._
import scala.math.Numeric._

scala> val x = new IntIsIntegral {
| override def times(x: Int, y: Int) = x+y;
| }
<console>:9: error: object creation impossible, since method compare in trait Ordering of type (x: Int,y: Int)Int is not defined

Oops, forgot to specify the IntOrdering trait which defines the compare() method. So, do it now:

scala> import scala.math.Ordering._
import scala.math.Ordering._

scala> val x = new IntIsIntegral with IntOrdering {
| override def times(x: Int, y: Int) = x+y;
| }
x: java.lang.Object with math.Numeric.IntIsIntegral with math.Ordering.IntOrdering = $anon$1@1e5d007

We have successfully created a new object to redefine the * operator as just addition. Now, pass it to the product() method:

scala> a
res5: List[Int] = List(3, 5, 2)

scala> a.product(x)
res6: Int = 11

It should be 3+5+2=10 but why the result is 11? Recall that it is doing multiplication, so it is using 1 as the seed for calculation (1+3+5+2). To change the seed from 1 to, say, 0, we can override the one() method:

scala> val x = new IntIsIntegral with IntOrdering {
| override def times(x: Int, y: Int) = x+y;
| override def one = 0;
| }
x: java.lang.Object with math.Numeric.IntIsIntegral with math.Ordering.IntOrdering = $anon$1@11a9f20

scala> a.product(x)
res7: Int = 10

Obviously now it works.

No comments:

Post a Comment