Saturday, November 7, 2009

Programming in Scala: Chapter 21 - Implicit Conversions and Parameters

Some notes from chapter-21, Programming in Scala.

Implicits are a way of "adding new methods" to a class. With implicits, you can make one object behave like other.

They are defined like regular methods except that they start with a keyword "implicit".

On finding something like x op y, compiler checks to see if op is defined on x or if there is an implicit converter available such that object returned from convert(x) has op and it replaces x with convert(x).

Rules - Here are 4 rules that govern implicit conversions.

1.Marking Rule: Only definitions marked implicit are available.
Scope Rule: An inserted implicit conversion must be in scope as a single identifier, or be associated with the source or target type of the conversion.

"Single Identifier" means compiler will not try to use someVariable.convert, it has to be available as a single identifier. One usual practice is to define all the implicits in an object Preamble and client code can simply do "import Preamble._" to make all of them available.
However there is one exception to this rule, the compiler will also look for implicit definitions in the companion object of source or target type.For example if you pass a Dollar to a method that expects Euro, compiler will look inside companion objects of Dollar and Euro to see if there is any implicit available to convert Dollar to Euro.

2.Non-Ambiguity rule:
If there are two or more implicits available to do same conversion, compiler will raise an error.

3.One-at-a-time Rule: The compiler will never try to rewrite x + y into convert2(convert1(x))

4.Explicit-First rule: x + y will never be re-written if + is already defined for x.

Note: Usually name of the implicit does not matter.

Where are implicits tried?

They are tried in three places.

1.conversion to an expected type: For example you have a String and you might want to pass it to a function that expects RandomAccessSeq[Char].

2.conversion of the receiver of a selection: If a method is called on an object that doesn't have it for example x + y, if x of a type that doesn't define + then implicits will be tried. One very interesting example of this is in supporting the syntax like.. Map(1 -> "one", 2 -> "two", 3 -> "three"), -> is a method in a class named ArrowAssoc and scala.Predef contains an implicit to do the conversion. Its defined as follow...
package scala
object Predef {
class ArrowAssoc[A](x: A) {
def -> [B](y: B): Tuple2[A, B] = Tuple2(x, y)
implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] =
new ArrowAssoc(x)

This kind of "rich wrappers" pattern is pretty common in libraries that provide syntax-like extensions to the language.

3.implicit parameters:
You can define method like
def meth(a: A)(implicit b:B, c:C)

You can call meth like a regular method by providing all the parameter list, or you can optionally leave out the whole implicit parameter list as following.

In this case, compiler will look for implicit vals defined of type B,C to insert automatically. So one should make them available like..

implicit val bVal = new B..
implicit val cVal = new C..

As a style rule, it is best to use a custom named type in the types of implicit parameters. For example it is not advised to have following..
def meth(a: A)(implicit b: String)
because String is a very common type and there might be implicit values available of type String that you don't know about so you're better off wrapping the String in a custom type.

View bounds:
Look at the following method...
def maxListImpParm[T](elements: List[T])
(implicit orderer: T => Ordered[T]): T =
elements match {
case List() =>
throw new IllegalArgumentException("empty list!")
case List(x) => x
case x :: rest =>
val maxRest = maxListImpParm(rest)(orderer)
if (orderer(x) > maxRest) x
else maxRest

Since orderer is implicit, it can be left out in the method body at both the places and following code means the same..
def maxListImpParm[T](elements: List[T])
(implicit orderer: T => Ordered[T]): T =
elements match {
case List() =>
throw new IllegalArgumentException("empty list!")
case List(x) => x
case x :: rest =>
val maxRest = maxListImpParm(rest) //(orderer) is implicit
if (x > maxRest) x //orderer(x) is implicit
else maxRest

Notice, in the second definition, there is no mention of orderer and hence the name does not matter. Since this kind of pattern is very common in scala, scala lets you leave out the name of this parameter and shorten the method header by using a view bound.. above method with new signature will be written as..

def maxListImpParm[T <% Ordered[T]](elements: List[T])

Here [T <% Ordered[T]], means "Any T can be used as long as there is an implicit available to convert it to Ordered[T]". By default an identity implicit converter is always available that would convert Ordered[T] to Ordered[T].

Debugging the Implicits:
Sometimes you might wonder why the compiler did not find an implicit conversion that you think should apply. In that case it helps to write the conversion out explicitly. If that also gives an error message, you then know why the compiler could not apply your implicit.

If above works, then you know one of the other rules(such as scope rule) is preventing the use of converter by the compiler.

When you are debugging a program, it can sometimes help to see what implicit conversions the compiler is inserting. The Xprint: typer option to the compiler/interpreter is useful for this. If you run scalac with this option, then the compiler will show you what your code looks like after all implicit conversions have been added by the type checker.

As a word of warning, implicits can make code confusing if they are used too frequently. Thus, before adding a new implicit conversion, first ask whether you can achieve a similar effect through other means, such as inheritance, mixin composition, or method overloading. If all of these fail, however, and you feel like a lot of your code is still tedious and redundant, then implicits might just be able to help you out.

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

Programming in Scala: Chapter 19 - Type Parameterization

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

Type parameterization allows you to write generic classes and traits in scala.

Whether a type parameter is covariant, contravatiant or nonvariant, its called parameter's variance.

The + or - symbols, you can place, before the type parameters(to make it covariant,contravariant respectively) are called variance annotations.

In general, If a classes type parameter is also one of the argument's parameter of a method, its not possible to have that parameter covariant. It is not allowed because of being unsafe. See the book for plenty of example describing why.

Methods can also declare type parameters. So we can't have unsafe thing like..
class Queue[+T] {
def append(x: T)

but, can have
class Queue[T] {
def append[U <: T](x: U)

This sort of design is called type-driven design, where the type of class/trait "guides" its details and implementation.

Liskov Substitution Principle: In type driven design: it is safe to assume that a type T is subtype of type U if you can substitute a value of type T wherever a value of U is required. The principle holds if T supports all operations that of U and require less but more than the corresponding operation in U.

Note: Queue is not a type but type constructor; Queue[String], Queue[Int] etc are types.