For Verified to be mutable A must be invariant. If you look at the Mutable collections in Scala they are all invariant.

Here is an interesting example of both invariant and covariant type parameters in a class hierarchy:

scala>class X[+A](val x :A)

defined class X

scala>class Y[A](var a: A) extends X[A](a)

defined class Y

scala>val x: X[Any] = new Y[String]("hi")

x: X[Any] = Y@1732a4df

scala> x.asInstanceOf[Y[String]].a="ho"

This example is perfectly legal because no matter how X[Any] is used no illegal assignment in Y can occur. The interesting thing is that the object can be used in covariant usecases when only X is required. This is now the collections in Scala can work.

Here is a little example of collections invariance and covariance in action. In List the parameter is covariant but in Buffer it is invariant

In-, co- and contra- variance are the three types of variance expressible in Scala. I showed how this affects assignments and arguments being pass to methods in the last two topics. This looks at how the different types of variance influences how classes can be defined. In the last post we saw how the compiler complained about a method definition in a covariant class because the compiler recognized that such a definition was inherently dangerous and must be prohibited. The example was:

scala>class Output[+A] {def write(a : A) = () /*do write*/ }

< console>:5: error: covariant type A occurs in contravariant position in type A of value a

class Output[+A] {def write(a : A) = () /*do write*/ }

^

For an class like Output it does not make sense to have A be covariant so we changed A to be contravariant. However suppose we have a collection type class.

class Verified[+A] (assertion : (A) => Boolean, value : A){

assert(assertion(value))

def a = value

def a_=(a : A) = new Verified(assertion, a)

}

The previous definition is not legal because value and a in the parameter of a_= "occur in a contravariant position." What to do? Making A contravariant isn't an option:

class Verified[+A <: V,V](assertion : (V) => Boolean, val value : A){

assert(assertion(value))

/*

this is the key. Restrict possible types of

A Since B is a super (or equal) type of A

*/

defupdate[ B >: A <: V](a : B) = new Verified(assertion, a)

}

// example useage

scala>def notNull(obj : AnyRef) = obj != null

notNull: (obj: AnyRef)Boolean

scala>val v = new Verified(notNull, "hi")

v: Verified[java.lang.String,AnyRef] = Verified@307b37df

scala>val newV = v update (new Object())

newV: Verified[java.lang.Object,AnyRef] = Verified@36f72f09

// 3 is not legal because the type parameter 'V' is AnyRef. Int is a subclass of Any NOT AnyRef

Covariant parameters allow for an additional dimension of type compatibility:

val l : List[Object] = List("this is legal")

Contravariance provides the opposite:

// If the type parameter of list was contravariant this would be legal:

val l : List[String] = List(new Object())

As covariance is indicated by a '+' before the type contravariance is indicated by a '-'

scala>class X[-A]

defined class X

scala>val l : X[String] = new X[Object]

l: X[String] = X@66201d6d

I can almost hear the "cool... but why?". Following the lead in the Programming In Scala book. Consider OutputStream first and a method in a Collection second. (The following code is illegal but consider it)

class Output[+A] {def write(a : A) = () /*do write*/ }

def writeObject(out : Output[Object]) = out.write("hello")

/*

Uh oh you this only is for outputting lists not Objects

(certainly not the String that is actually written)

Runtime error for sure!

*/

writeObject(new Output[List[String]])

The previous example (if it would compile) would explode because an Output that can only write lists is passed to the method. In the example a String is written to the Output object. The Output[List[String]] cannot handle that.

Fortunately the compiler sees the definition of the class and recognizes this is an error waiting to happen and makes it illegal:

scala>class Output[+A] {def write(a : A) = () /*do write*/ }

< console>:5: error: covariant type A occurs in contravariant position in type A of value a

class Output[+A] {def write(a : A) = () /*do write*/ }

^

Consider the implications of making A contravariant?

// The definition of object is now legal

class Output[-A] {def write(a : A) = () /*do write*/ }

// this is now a safe method definition since the parameter of Output must be a Object or a super class

def writeObject(out : Output[Object]) = out.write("hello")

// Now this is illegal as it should be

scala> writeObject(new Output[List[String]])

< console>:8: error: type mismatch;

found : Output[List[String]]

required: Output[java.lang.Object]

writeObject(new Output[List[String]])

// this is legal...

scala> writeObject(new Output[Any])

In this example Output[Any] can be passed to the method. This makes sense. If the Output object knows how to write Any oject then it knows how to write an Object; its all good.

Wednesday, March 24, 2010

In Java most parameterized types are considered to be "invariant". What does that mean? Here is an example to explain:

/*

This is an example of a parameterized class that with an invariant parameter B

In both Scala and Java parameters are invariant by default.

*/

scala>class Invariant[B]

defined class Invariant

scala>var x : Invariant[Object] = new Invariant[Object]

x: Invariant[java.lang.Object] = Invariant@2e0c5575

/*

Note: Invariant[String] cannot be assigned to Invariant[Object]

even though logically it seems like it should be.

This is the effect of invariance. Covariant parameters do not have

this restriction.

*/

scala>var x : Invariant[Object] = new Invariant[String]

< console>:6: error: type mismatch;

found : Invariant[String]

required: Invariant[java.lang.Object]

var x : Invariant[Object] = new Invariant[String]

^

scala>class Sub[A]extends Invariant[A]

defined class Sub

/*

Since Sub is a subclass of Invariant it can be assigned

(but not Sub[String])

*/

scala>val x : Invariant[Object] = new Sub[Object]

x: Invariant[java.lang.Object] = Sub@26ced1a8

Assignment compatibility has multiple dimensions: the object type and the types of the parameters. Unlike object type the compatibility of the type-parameters can be covariant, contravariant and invariant. Java has invariant parameters and that is demonstrated by the previous example. Covariant parameters allow subclassing. Contravariant parameters need their own topic.

Monday, March 22, 2010

Continuing on with operators, There is a special type of operator in Scala. It is an operator that ends with =. If a class has operation (methods with an operator identifer) the = can be appended to the effectively creating a new method. In truth a new method is not created instead the compiler rewrites the line.

For example. If a method (like Int) defines + then a method call += can be used. It can be used to mutate a variable:

scala>var i = 1

i: Int = 1

scala> i += 1

scala> i

res3: Int = 2

To illustrate this is not a special case for Int the next example defines several operations and demonstrates in place variable mutation.

scala>caseclass MyClass(i:Int) {

| def +(j:Int) = new MyClass(j + i)

| def -(j:Int) = new MyClass(i - j)

| def ^(j:Int) = MyClass(j)

| def +|(j:Int) = new MyClass(j + i / 3)

| }

defined class MyClass

scala>var c = MyClass(1)

c: MyClass = MyClass(1)

scala> c+=6

scala> c

res5: MyClass = MyClass(7)

scala> c -= 2

scala> c

res7: MyClass = MyClass(5)

scala> c ^= 10

scala> c

res23: MyClass = MyClass(10)

scala> c +|= 5

scala> c

res25: MyClass = MyClass(8)

Here are several more examples using existing classes in Scala. They are all immutable examples.

scala>var l = Set(1,2,3)

l: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

scala> l += 10

scala> l

res7: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 10)

scala>var seq = Seq(5,6,3)

seq: Seq[Int] = List(5, 6, 3)

scala> seq :+= 10

scala> seq

res9: Seq[Int] = List(5, 6, 3, 10)

scala> seq +:= 10

scala> seq

res11: Seq[Int] = List(10, 5, 6, 3, 10)

scala>var list = List(32)

list: List[Int] = List(32)

scala> list ::= 12

scala> list

res13: List[Int] = List(12, 32)

Note: assignment operators can also be defined as methods to mutate an object

Friday, March 19, 2010

Since Scala allows one to define the behavior of operators there are some rules involving operators and assignment like +=. One of the standard method calls in most languages is i += 1.

Since i+=1(no spaces) is also valid, Scala has some rules regarding how statements like i+=1 should be broken up. Obviously we know it should be 'i' '+=' and '1'. So there is a special class of characters called operators. I don't know all of them but a few are: + - ^ * / % ! | & =( ':' is sort of part of this group but has some special properties as well).

These characters can be method names but they cannot be combined with other identifier characters.

Update: These characters can be combined with other identifier characters if there is an under score so:

def x+ = 3 // not valid

def x_+ = 3 // valid

def +x = 3 // not valid

However these characters are special because they can be combined in a special way with '=' for a special assignment construct as shown in the next post.

Tuesday, March 16, 2010

Very simple but useful are the methods assert, require and assume which are built into the Predef object. As you might expect they are methods for performing certain checks during runtime to verify certain conditions. They do not use the Java assert framework and therefore are always evaluated regardless of whether or not assertions are enabled.

Update: Scala 2.8 has an annotation called elidable that will (when 2.8 is complete) allow one to remove method calls at compile time by setting a compiler flag. The assert, etc... methods are all marked with this flag and as a result can be removed at compile time for production environments.

Scala2.8

scala>var called = 0

called: Int = 0

scala> called

res0: Int = 0

/*

assert, require and assume have call by name parameters so the message is only

Monday, March 15, 2010

Unzip is a handy companion to partition. - Partition divides a traversable into two traversables by a boolean predicate. - Unzip divides a traversable into two by dividing each element into two parts (each becomes an element in one traversable). If an element is a Tuple2 then each tuple is divided into two otherwise a function is required to divide an element into two.

Wednesday, March 3, 2010

A further tip regarding using case statements to construct functions. If a case statement is assigned to a Function it will construct a Function object not a PartialFunction.

I suppose the question is why do you care about this since PartialFunction is a Function. The fact is that a PartialFunction is a Function1. But using a case statement you can construct a Function4 very easily.

Monday, March 1, 2010

This tip is mainly to document a 'GOTCHA' that I got caught by recently. It basically goes like this:Trait Y extends(or has self-type) X. Trait X defines some abstract method 'm'. The initialization code in Y accesses 'm'. Creation of an object new X with Y results in: *Boom* NullPointerException (on object creation).

The example in code:

scala>trait X { val x : java.io.File }

defined trait X

scala>trait Y {self : X => ; val y = x.getName}

defined trait Y

scala>new X with Y { val x = new java.io.File("hi")}

java.lang.NullPointerException

at Y$class.$init$(< console>:5)

at $anon$1.< init>(< console>:7)

...

At a glance it seems that x should override the abstract value x in trait X. However the order in which traits are declared is important. In this case first Y is configured then X. Since X is not yet configured Y throws an exception. There are several ways to work around this.Option 1:

trait X {val x : java.io.File}

trait Y {self : X => ; val y = x.getName}

/*

Declaring Y with X will work because Y is initialized after X

but remember that there may

be other reasons that X with Y is required.

Method resolution is one such reason

*/

new Y with X { val x = new java.io.File("hi")}

Option 2:

trait X { val x : java.io.File }

trait Y {self : X => ; def y = x.getName}

/*

Since method y is a 'def' x.getName will not be executed during initialization.

*/

scala>new X with Y { val x = new java.io.File("hi")}

res10: java.lang.Object with X with Y = $anon$1@7cb9e9a3

Option 3:

trait X { val x : java.io.File }

trait Y {self : X => ; lazyval y = x.getName}

/*

'lazy val' works for the same reason 'def' works: x.getName is not invoked during initialization

*/

scala>new X with Y { val x = new java.io.File("hi")}

res10: java.lang.Object with X with Y = $anon$1@7cb9e9a3

Option 4:

trait X {val x : java.io.File }

trait Y extends X {def y = x.getName}

/*

if Y extends X then a new Y can be instantiated

*/

new Y {val x = new java.io.File("hi")}

Two more warnings. First, the same error will occur whether 'x' is a def or a val or a var.

trait X { def x : java.io.File }

trait Y {self : X => ; val y = x.getName}

new X with Y { val x = new java.io.File("hi")}

Second warning: In complex domain models it is easy to have a case where Y extends X but the final object is created as: new X with Y{...}.

You will get the same error here because (I think) the compiler recognized that Y is being mixed in with X and therefore the X will be initialized as after Y instead of before Y.

First the code:

trait X { def x : java.io.File }

trait Y extends X { val y = x.getName}

new X with Y { val x = new java.io.File("hi")}

If the code instantiated new Y{...} the initialization would be X then Y. Because X can only be initialized once, the explicit declaration of new X with Y forces Y to be initialized before X. (X can only be initialized once even when it appears twice in the hierarchy).

This is a topic called linearization and will be addressed in the future.

Search This Blog

About Me

Jesse EicharI am a senior software developer at Camptocamp SA (Swiss office) and specialize in open-source geospatial Java projects. I am a member of the uDig steering committee and a contributor to Geotools and Mapfish. In addition I regularly work with Geoserver and Geonetwork.

In my free time I am a Scala enthusiast. I am working on the Scala IO incubator project and WebSpecs a Specs2 based testing framework for webapplications.