Saturday, September 06, 2008

No More Statics! Part 2

In a previous post, I explained how Scala's use of singleton objects is better than Java's use of static members. I was asked for some sample code after that post, so I thought I would throw some together. Let's look at a simple Java class.

class Foo {
private static int number = 1;

public static Foo create(String a) {
return new Foo("Some " + a);
}

private String a;
private int id = number++;

public Foo(String a) {
this.a = a;
}

@Override
public String toString() {
return "Foo #" + id + " is " + a;
}
}

This class keeps track of how many instances have ever been created. You construct a Foo with a name, and the Foo's name and id are part of its string representation. In addition, there is a create method that has been defined on the Foo class itself.

Scala doesn't have a "static" keyword. Instead, members that would otherwise be static are placed onto the so-called companion object.

object Foo {
var number:Int = 1;

def create(a:String) = new Foo("Some " + a)
}

class Foo(a:String) {
private val id:Int = Foo.number
Foo.number = Foo.number + 1

override def toString() = {
"Foo #" + id + " is " + a
}
}

Because Foo is the name of both an object and a class in the same package, they are allowed to access each other's private members. Basically, this makes the instance members of a singleton object equivalent to static members of an ordinary Java class. However, since the singleton object is a fully fledged object, it can be passed around in a way that Java classes normally can't be.

def createList(f : Foo.type) = {
List(f.create("One"), f.create("Two"))
}

Have you ever wanted a Java class' static members to obey an interface? Well, the singleton object can mix in Scala traits (Scala traits seem to take the place of both interfaces and mixins from other languages).

trait Creatable[A] {
def create(a:String) : A

def createDefault() : A = {
return create("Default")
}
}

object Foo extends Creatable[Foo] {
var number:Int = 1;

override def create(a:String) = new Foo(a)
}

And here's a whole sample program:

trait Creatable[A] {
def create(a:String) : A

def createDefault() : A = {
return create("Default")
}
}

object Foo extends Creatable[Foo] {
var number:Int = 1;

override def create(a:String) = new Foo(a)
}

class Foo(a:String) {
private val id:Int = Foo.number
Foo.number = Foo.number + 1

override def toString() = {
"Foo #" + id + " is " + a
}
}

def createList(f : Creatable[Foo]) = {
List(f.create("Three"), f.create("Four"))
}

println(Foo.create("One"))
println(Foo.create("Two"))
println(createList(Foo))
println(Foo.createDefault())

----------

Foo #1 is One
Foo #2 is Two
List(Foo #3 is Three, Foo #4 is Four)
Foo #5 is Default

Why are singleton objects better than static members? To begin with, Scala's singleton objects are at least as expressive as static class members, so you're not losing anything from Java. You define a singleton object differently that you define static members in Java, but you access them using notation identical to Java (i.e. Foo.bar(5) in both languages). In addition, you get some other nice features - first class object status and the ability to participate in the normal class hierarchy. As an added bonus, Scala's simpler syntax actually made the class/singleton-object pair shorter than the equivalent Java solution. Not bad!

8 comments:

Dan said...

I also want to point out that the code given here is not intended to be realistic at all. I can't say that I've ever had the need to keep track of the number of instances of a class (except maybe for debugging purposes). Mutable static variables in Java are generally a bad idea. This code is just to show that Scala can use singleton objects to do everything that Java can do with static members.

Unknown said...

Maybe I'm still too tired because of lack of sleep, but doesn't your example count the number of times toString() is called instead of counting instances that are created?

Dan said...

Thanks, quintesse. You are absolutely right. I guess that's what happens when I write my posts late at night. I went back and modified my original post; fixing the problem you mentioned and shortening the code (to make it more script-like and hopefully increase signal-to-noise).

Sylvain HENRY said...

Instead, members that would otherwise be static are placed onto the so-called companion class.

I think it should read "Companion object" instead of "Companion class".

Dan said...

hsyl20, you're correct. The companion class is, well the other half of the equation. A singleton object can have a companion class, and a class can have a companion object. Thanks for pointing out my mistake.

mateusz.fiołka said...
This comment has been removed by the author.
mateusz.fiołka said...

Few things I would change to make the code look more "scalish"

object Foo {
var number:Int = 1;

def create(a:String) = new Foo("Some " + a)
}

to


object Foo {
var number = 1

def create(a:String) = new Foo("Some " + a)
}

Type is automatically inferred so no type annotation needed here. Also no semicolon needed at the end.

override def toString() = {
"Foo #" + id + " is " + a
}

toString is side effects free so it would be better to make it look like a field, hence

override def toString = {
"Foo #" + id + " is " + a
}
also it is only one expression thus


override def toString = "Foo #" + id + " is " + a


I don't have time to read the whole article unfortunately ;). However using global state in singleton object is as bad as static state in java imho. I hope you did this only to show us the syntax ;). Happy Scala coding :).

Dan said...

Thanks for the suggestions, jau. Yes, static/global state is an anathema. I think there are a few (very few) cases where it is handy, statics are overused and generally create more problems than they solve. However, I do appreciate the use of singleton objects. If an object has no state, there's no need to construct more than one instance of it. Java allows you to do this by making the constructor private, constructing an instance in the static initializer, and giving people access to that instance. Scala makes it much easier.