Scala problem #1

Posted on January 10, 2009 by Tommy McGuire
Labels: software development, notation, scala, programming language
I have been playing off and on with Scala, and overall I like the language so far. It seems to decently satisfy the constraints of compatibility with the JVM and those of a reasonably modern language. However, while reading chapter six of Programming in Scala, I think I have spotted a potentially irritating problem.

One thing I note about Scala is that its designers went out of their way to fight the verbosity inherent in Java. Many of the syntactic features of Scala appear to be present only to make common cases terse. (That is both a strength and weakness of Perl, although I usually find it a weakness. To recognize it in a language that has actually been designed is disquieting.)

Consider class parameters and the primary constructor built from them and the body of a class. These features allow a simple, minimal, immutable class to be written simply as:

class Rational(n:Int, d:Int) {
override def toString = n + "/" + d
}

The n and d parameters automatically become final fields of the generated class, set by the generated primary constructor. Disassembling the class with javap -private produces:

Compiled from "test.scala"
public class Rational extends java.lang.Object implements scala.ScalaObject{
private final int d;
private final int n;
public Rational(int, int);
public java.lang.String toString();
public int $tag() throws java.rmi.RemoteException;
}


One downside to class parameters is that, as fields, they cannot be accessed by other objects of the same class as in:

def add(that:Rational):Rational = new Rational(n * that.d + that.n * d, d * that.d)

The expression "that.d" will not compile. For that and possibly other reasons, it is possible to introduce explicit fields into the class:

class Rational2(n:Int, d:Int) {
val numer = n
val denom = d
override def toString = numer + "/" + denom
}


Given this code, the expression "that.numer" would compile and work correctly. Further, Programming in Scala notes,

Even though n and d are used in the body of the class, given they are only used inside constructors, the Scala compiler will not emit fields for them. Thus, given this code the Scala compiler will generate a class with two Int fields, one for numer and one for denom.

javap shows the result:

Compiled from "test.scala"
public class Rational2 extends java.lang.Object implements scala.ScalaObject{
private final int denom;
private final int numer;
public Rational2(int, int);
public java.lang.String toString();
public int denom();
public int numer();
public int $tag() throws java.rmi.RemoteException;
}


However, what happens if, when you are introducing the explicit fields, you forget to remove all of the references to the class parameters?

class Rational3(n:Int, d:Int) {
val numer = n
val denom = d
override def toString = n + "/" + d
}

(Note the use of n and d in toString.) javap now produces:

Compiled from "test.scala"
public class Rational3 extends java.lang.Object implements scala.ScalaObject{
private final int denom;
private final int numer;
private final int d;
private final int n;
public Rational3(int, int);
public java.lang.String toString();
public int denom();
public int numer();
public int $tag() throws java.rmi.RemoteException;
}


There are four fields in the resulting object. In this case, there is no problem, since denom and d are initialized to the same values and are final (and likewise numer and n). But another reason to use explicit fields is to allow them to vary, to not be final.

class Rational4(n:Int, d:Int) {
var numer = n
var denom = d
override def toString = n + "/" + d
}

The resulting code makes numer and denom non-final, and other methods can alter them. However, toString will always print the values of n and d set when the object was constructed. Whoopsie.

Comments



Not a problem.

For an immutable Rational with accessible fields

class Rational(val num : Int, val den : Int)

or better yet

case class Rational(num : Int, den : Int)

for a mutable one

class Rational(var num : Int, var den : Int)

or better yet

case class Rational(var num : Int, var den : Int)

James Iry
2009-01-10

Ok, case classes are niiiiiiiice. And here I was just using them for algebraic data types. Of course, the "case" label is a little odd.

What about the next version of Rational:

class Rational8(n:Int, d:Int) {
private val g = gcd(n abs, d abs)
val numer = n / g
val denom = d / g
override def toString = numer + "/" + denom
private def gcd(a:Int, b:Int) :Int = if (b==0) a else gcd(b, a%b)
}

This class does not include n and d fields, but it would if I'd thinko-ed toString. Further, it has a g field that is only used in the primary constructor. Good thing memory is free.

I'm still concerned about language features that have unexpected consequences. For some reason, I am just reminded of the Mach interface generator, an IDL compiler. Mig had several keywords associated with parameters passed between processes, each with semantics (particularly performance-related semantics) that were not obvious from the keyword.

Tommy McGuire
2009-01-10

Ah, that is indeed slightly less than optimal in Scala

I solve it either with a companion object

object Rational {
def gcd(a : Int, b : Int) = ...
def apply(num : Int, den : Int) = {
val g = gcd(num, den)
new Rational(num / g, den / g)
}

class Rational private(val num : Int, val den : Int)

or with alternative names on the params - I usually prepend with an underscore so I don't accidentally use them. If I don't want gcd to be a field I just make sure it's scoped properly

class Rational (_num : Int, _den : Int) {
val (num, den) = {
val gcd = ..
(_num / gcd, _den / gcd)
}
}

James Iry
2009-01-10
active directory applied formal logic ashurbanipal authentication books c c++ comics conference continuations coq data structure digital humanities Dijkstra eclipse virgo electronics emacs goodreads haskell http java job Knuth ldap link linux lisp math naming nimrod notation OpenAM osgi parsing pony programming language protocols python quote R random REST ruby rust SAML scala scheme shell software development system administration theory tip toy problems unix vmware yeti
Member of The Internet Defense League
Site proudly generated by Hakyll.