Sunday, August 23, 2009

Programming In Scala: Chapter#8-15 notes

Ch8- Functions and Closures
Functions in scala are first class citizens like any other language supporting functional paradigms such as scheme, lisp etc. Which means you can do all the cool abstractions that SICP teaches you.

Function values are objects and hence can be assigned to variables.

"short" forms for writing function values, Let us try to see it through this example..

numbers.filter((x: Int) => x > 0)

We can remove the type parameter types because compiler can infer that from its usage.

numbers.filter((x) => x > 0)

We can remove the parentheses around a parameter whose type is inferred.

numbers.filter(x => x > 0)

You can use '_' as placeholders for one or more parameters as long as each parameter appears *only once* within the function literal. This is also called placeholder syntax.

numbers.filter(_ > 0)

Sometimes placeholder syntax wouldn't work when compiler don't have enough information to infer the missing parameter types. For example val f = _ + _ doesn't compile but val f = (_: Int) + (_: Int) does. Note that first '_' refers to first parameter, second to second parameter and so on.


Partially Applied functions:
Let us see an example..

scala> def sum(a: Int, b: Int, c:Int) = a + b + c
sum: (Int,Int,Int)Int

//Partially apply the method sum on 3,4 , this gives us
//a Partially Applied Function value which takes remaining
scala> val a = sum(_: Int, 3,4)
a: (Int) => Int =

//see, we can apply a on one remaining parameter
scala> a(5)
res24: Int = 12

//We can do the partial application also by not supplying
//any param and putting placeholders for all of them
scala> val a = sum(_: Int, _: Int, _: Int)
a: (Int, Int, Int) => Int =

//above scenario can also be written as following. notice '_' in this
//context here does not refer to a single parameter but the whole parameter
scala> val a = sum _
a: (Int, Int, Int) => Int =

scala> a(5,3,4)
res25: Int = 12

If you are writing a partially applied function expression in which you leave off all parameters, such as println _, you can express it more concisely by leaving off the underscore *if function is required at that point* in the code. For example, these three are ok.

numbers.foreach(println _) //note, '_' is not one param but whole param list
val a = sum _

but following is not, as its not required at this point in the code.
val a = sum


It supports closures, When a function on calling it returns a function that encloses one or more variable bindings defined in the parent function. These returned functions are called closures(they have bindings closed in them). In general, any function enclosing binding for one or more free variables is a closure. This technique is very powerful and you can do all sorts of data abstractions using them. For a primer on this one should read SICP chapter 3.

Repeated Parameters: def echo(args:String*) means that you can pass 0 or more strings when calling echo and they will be available in the echo definition in an Array[String] named args.

Due to limitation of jvm instruction set, tail-recursion is optimized only for direct tail recursion with the same function. Other indirect tail recursion cases such as mutually recursive functions, it is not optimized.

Ch9- Control Abstraction
Currying: A curried function is applied to multiple argument list instead of one. When you apply a curried function, you actually get multiple(equal to the number of argument lists it has) regular function invocations back to back.
def curriedSum(x:Int)(y:Int) = x + y
curriedSum(1)(2) = 3
Basically curriedSum(1) returns a function that is (y:Int)=>1+y and this function is applied on 2.

In any method invocation in scala in which you're passing in exactly one argument, you can opt to use curly braces to surround the argument instead of parentheses. This is provided, so that you could define control abstractions as functions and they can be called with argument in curly braces which looks more like built-in language construct. Though powerful, but I would rather simply use parentheses so that anyone reading the code *knows* without a doubt that a function is being called and this is *not* a built-in construct.

By-Name Parameter:
Let us say you want to pass an expression to a function, that you don't want to be evaluated at call time but rather inside the body of the function. You can do something like following(basically take a empty param function value as argument)..
def myMethod(proc: () => Boolean)

and the client code would look like..
myMethod(() => 5>3)

Instead you can use By-name params which start with '=>'. Here is how it'll look then..
def myMethod(proc: => Boolean)

and the client code would look like..
Note that, the expression (5 <3) will not be executed unless myMethod uses proc somewhere inside the body.

Ch10- Composition and Inheritance
For abstract methods, one does not need to specify the "abstract" keyword as that in java

override keyword is mandatory if you're overriding a method from the parent class, its optional if you're implementing an abstract method of the parent class.

Scala has same namespace for instance variable and methods names unlike java. That is you can't have a field and a method with same identifier in scala AND a field(val) can override a parameterless method(def) with same name. However, viceversa is not true that is a parameterless method can't override a field with same name.

In general, scala's inheritence and composition is pretty similar to java's. But there are some striking differences in the syntax and it offers a bag of tricks to reduce code size.

One of the important things to learn from this chapter is the design it offers in the end for exposing the abstract Element via factory methods without exposing any of the concrete subtypes of Element as follow...

abstract class Element {
private class ArrayElement(x: Array[String]) extends Element { ..}
private class LineElement(s: String) extends Element { ... }
object Element {
def elem(x: Array[String]) = new ArrayElement(x)
def elem(x: String) = new LineElement(x)

Ch11- Scala's Hierarchy
AnyRef is just an alias for java.lang.Object. By default, all the scala classes inherit AnyRef and a marker trait called ScalaObject.

Instead of ==, In scala AnyRef has a method eq that is final and does the reference equality check. Opposite of eq is ne.

scala.Null is subclass of every reference(not value) class and scala.Nothing is subclasses of every class in scala.

Ch12- Traits
Traits are fundamental unit of code reuse in scala, They contain method and field definitions, which can then be reused by mixing them into classes. One class can mixin with any number of traits.

Traits are defined exactly like class except the keyword use is trait instead of class. It can be mixed in to a class using either the extends or with keywords. If you use extends then superclass of the trait is automatically inherited.

A trait defines a type also. So you can have var or val whose type is a trait's name.

Traits can't have any parameters in its constructor.

In traits, super calls are dynamically bound, while in normal classes its statically bound.

Two major use of traits are...
> turning a thin interface into a rich one (by providing implementation of some of the methods)
> use them as stackable modifications: see this post.

To trait, or not to:
If the behavior will not be reused, then make it a concrete class. If the behavior will be reused in multiple unrelated classes then make it a trait. Use abstract class instead if you want to inherit it from java code, since there is no analogue to traits in java.
And BTW, a trait with only abstract members translates directly into a java interface.

Ch13 - Packages and Imports
In Scala, access modifier can be augmented with qualifiers. A modifier of the form private[X] or protected[X] means that access is private or protected "up to" X, where X designates some enclosing package, class or singleton object.

Other interesting thing about imports in scala are that they can appear anywhere, can refer to objects and also naming the imports.. some examples of import definitions would like following...

import search._ //imports everything inside package search
import search.DepthFirstSearch //imports definition of DepthFirstSearch
import search.{DepthFirstSearch => DFS} //importing same as above, but can refer as DFS.. renamed the import

Ch15 - Case Classes
They're used to provide pattern matching(a very general one).

Scala compiler adds some convenience methods to a case class-
  • You can construct the instance without writing the new keyword.
  • All the paratemers defined in the primary class constructor automatically get the val prefix, so that they are visible as fields automatically.
  • The compiler adds "natural" implementaions of hashCode(), toString() and equals() to your class.

match in scala vs switch in java -
  • match is an expression(always returns a value)
  • alternative expressions never "fall through" into the next case, no need for break.
  • if none of the patterns match then it throws MatchError exception

Mostly all patterns look exactly like the corresponding expression.

Type of Patterns:
  • Wildcard pattern (_): matches anything
  • Constant pattern : matches constants to themselves
  • Variable pattern : a variable in the pattern matches to any object and scala binds the variable to the matched object
  • Scala treats all the identifiers starting with a capital letters as constant, same is really a constant expression and not a variable expression. If you want to use an identifies starting with a small letter as constant expression then you'll have to enclose it in backquotes.
  • Constructor pattern: Assuming A is a case class we can use the constructor expression of this class as a pattern.
  • Sequence patterns : You can match against scala sequences like List or Array
  • Tupple patterns : You can match against tuples too
  • Typed patterns: They can be used as a convenient replacement for the type tests, such as *case x:String => whatver* .. this is something similar to x.isInstanceOf[String]. (Note: to cast something into string you would write x.asInstanceOf[String], as you can see they're rather verbose and that is because scala discourages their use, you should better use typed pattern)
  • Scala uses the erasure model of generics just like java, that means no information about the type argument is maintained at runtime, that is with the pattern matching you can match an object for being Map but not Map[Int, Int].
  • Variable binding pattern: with this you can bind the result of a pattern match to a variable. For example, the pattern *UnOp("abs", e @ UnOp("abs", _))* matching will bind e to result of pattern appearing after @.

Pattern guards:
In general, scala patterns are linear that is you can't write same variable twice in a pattern, if you need such thing anywhere.. look up pattern guards :)
Patterns are tried in the order they are written.

Sealed Class:
If a class is sealed, then only classes defined in the same file can inherit it. This lets scala help you fulfil your intent of not letting someone else define another case class inheriting it.

The best way to get value apart from optional value(Some(value)) is by using a match expression. It recommeded to use Optional values instead of nulls in scala.

Patterns Everywhere:
Patterns are allowed in many more places and not just in match expression.
  • Whenever you define a val or var, you can use pattern instead of simple identifier.
  • A sequence of cases in curly braces can be used anywhere a function value can be used.
  • If you're using a sequence of cases as function literal and you don't want to exhaust all the cases then make it a PartialFunction. Partial functions have a method "isDefinedAt" that can tell whether its defined for a particular input or not. Partial functions are recommended not be used unless there is a real good reason to use them.
  • Patterns can be used in for expressions also.

No comments:

Post a Comment