Thursday, November 5, 2009

Programming in Scala: Chapter 20 - Abstract Members

Some notes from chapter-20 of the book "Programming in Scala".

There can be 4 kind of abstract members in a class/trait:
vals (defined using val)
vars (defined using var)
methods (defined using def)
types (defined using type)

Classes can be abstract and traits by definition are abstract, but neither of these are abstract types in scala. An abstract type in scala is always a member of some class/trait.

A parameterless abstract method can be overriden by a val with same name but not viceversa, Why?
"val x" means, once its defined in a concreteObject, then client should get same value whenever concreteObject.x is called, if a parameterless method could override "val x" then that implementation may be such that concreteObject.x is not always the same.

An abstract var:
When you declare a var, you implicitly declare two defs, setter and getter. Notice that following two are same...
trait A {
var x: Int


trait A {
def x: Int
def x_=: Unit

Hence an abstract var can be overriden by a var or two defs.

Initializing abstract vals:

trait A {
val x: Int
val y = { require(x > 0); 1/x }

we can create an instance of the anonymous class that mixes above trait by following expression

new A { val x = expr }

Here expr will be evaluated only *after* the class is initialized, during initialization x will have its default value which is zero. So, following fails..

scala> val a = 20
a: Int = 20

scala> new A { val x = 2 * a }
java.lang.IllegalArgumentException: requirement failed
at scala.Predef$.require(Predef.scala:107)
at A$class.$init$(:7)
at $anon$1.(:8)
at .(:8)
at .()
at RequestResult$.(:3)
at RequestResult$.()
at RequestResult$result()
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Meth...

To overcome this, we have two ways

Pre-Initialized fields:
It lets you initialize a field of a subclass before the superclass is called. To do this simply place the field definition in braces before the superclass constructor. So this succeeds...

scala> new { val x = 2 * a } with A
res8: java.lang.Object with A = $anon$1@14d6015

pre-initialized fields are not restricted to anonymous classes, they can be used with objects and named classes also as in following example..

scala> object B extends { val x = 2 * a} with A
defined module B

scala> B.x
res9: Int = 40

Note: Because pre-initialized fields are initialized before the superclass constructor is called, their initializers can not refer to the object that is being constructed.

Lazy vals:
If you prefix a val definition with lazy modifier, the initializing expression on the right hand side is evaluated the first time the val is used. So we can redefine trait A as following..
trait A {
val x: Int
lazy val y = { require(x > 0); println("init x"); 1/x }

this works now...

scala> new A { val x = 2 * a }
res12: java.lang.Object with A = $anon$1@1b8737f

Caution: if the expression on the right hand side of lazy val produces a side effect, it will happen only once. As soon as the expression is evaluated once, its value will be memoized.

You can neither create an instance of an abstract type nor have an abstract type as a sypertype of another class. A work around is to have an abstract factory method along with the type, whose concrete implementation can create the instances of the concrete type. And, its usually a good idea to have factory methods in separate objects.

You can have a class member and a val member with same identifier in a class. For example, following compiles without any issue.
class A {
class B
val B = 5

BTW, if you wanted to refer to class B, you would write A#B and not A.B

No comments:

Post a Comment