Uncategorized

Case Classes and Pattern Matching

Case Classes can be seen as plain and immutable data-holding objects that should exclusively depend on their constructor arguments.
  • Case classes can be pattern matched
  • Case classes automatically define hashcode and equals
  • Case classes automatically define getter methods for the constructor arguments
For example:
def describe(x: Any) = x match {
     case 5 => "five"
     case true => "truth"
     case "hello" => "hi!"
     case Nil => "the empty list"
     case _ => "something else"
}
Sequence patterns
You can match against sequence types like List or Array just like you match against case classes:
expr match {
     case List(0, _, _) => println("found it”) //change this to List(0, _*) if you don’t want to specify how long it should be
     case _ =>
}
Typed patterns
You can use a typed pattern as a convenient replacement for type tests and type casts:
def generalSize(x: Any) = x match {
     case s: String => s.length
     case m: Map[_, _] => m.size
     case _ => -1
}
Patterns in for expressions
for ((country, city) <- capitals)
     println("The capital of "+ country +" is "+ city)

 

Uncategorized

Classes and Objects

  • A class and its companion object can access each other’s private members
  • The difference between singleton objects and classes is that singleton objects cannot take parameters and classes can
  • A singleton object which does not share the same name with a companion object is called a standalone object. You can use standalone objects for collecting related utility methods together or defining an entry point to a Scala application
  • To run a Scala application you must supply the name of a standalone singleton object with a main method that takes one parameter, an Array[String], and has a result type of unit:
object Summer{
    def main(args: Array[String]) {
        ???
    }
}
  • A sealed class cannot have any new subclasses added except the ones in the same file
Uncategorized

Packages and Imports

  • Because Scala code is part of the Java ecosystem, it is recommended to follow Java’s reverse-domain-name convention for Scala packages that you release to the public
  • Different kinds of import:
// easy access to Fruit
import bobsdelights.Fruit

// easy access to all members of bobsdelights
import bobsdelights._

// easy access to all members of Fruits
import bobsdelights.Fruits._
          The first of these corresponds to Java’s single type import, the second to Java’s on-demand import. The only difference is that Scala’s on-demand imports are written with a trailing underscore instead of an asterisk
  • imports in Scala can appear anywhere, not just at the beginning of a compilation unit. Also, they can refer to arbitrary values. For instance:
def showFruit(fruit: Fruit) { 
    import fruit._
    println(name +"s are "+ color)
}
  • Imports in Scala can rename or hide members. This is done with an import selector clause enclosed in braces, which follows the object from which members are imported:
import Fruits.{Apple, Orange} //This imports just members Apple and Orange from object Fruits.
import Fruits.{Apple => McIntosh, Orange} //This imports the two members Apple and Orange from object Fruits. However, the Apple object is renamed to McIntosh. So this object can be accessed with either  Fruits.Apple or McIntosh. A renaming clause is always of the form “<original-name> => <new-name>”.
import Fruits.{Pear => _, _} //This imports all members of Fruits except Pear. A clause of the form “<original-name> => _” excludes <original-name> from the names that are imported.
Uncategorized

Traits

Traits are a fundamental unit of code reuse in Scala. A trait encapsulates method and field definitions, which can then be reused by mixing them into classes. Unlike class inheritance, in which each class must inherit from just one superclass, a class can mix in any number of traits.
  • Once a trait is defined, it can be mixed in to a class using either the extends or with keywords
  • If you wish to mix a trait into a class that explicitly extends a superclass, you use extends to indicate the superclass and with to mix in the trait. If you want to mix in multiple traits, you add more with clauses.
  • Traits can declare fields and maintain state. In fact, you can do anything in a trait definition that you can do in a class definition, and the syntax looks exactly the same, with only two exceptions. First, a trait cannot have any “class” parameters, i.e., parameters passed to the primary constructor of a class. Second difference is that whereas in classes, super calls are statically bound, in traits, they are dynamically bound. If you write “super.toString” in a class, you know exactly which method implementation will be invoked. When you write the same thing in a trait, however, the method implementation to invoke for the super call is undefined when you define the trait. Rather, the implementation to invoke will be determined anew each time the trait is mixed into a concrete class. This curious behaviour of super is key to allowing traits to work as stackable modifications
  • One major use of traits is to automatically add methods to a class in terms of methods the class already has. That is, traits can enrich a thin interface, making it into a rich interface
  • To enrich an interface using traits, simply define a trait with a small number of abstract methods—the thin part of the trait’s interface—and a potentially large number of concrete methods, all implemented in terms of the abstract methods. Then you can mix the enrichment trait into a class, implement the thin portion of the interface, and end up with a class that has all of the rich interface available
  • Traits let you modify the methods of a class, and they do so in a way that allows you to stack those modifications with each other
To trait, or not to trait?
  • If the behavior will not be reused, then make it a concrete class. It is not reusable behavior after all
  • If it might be reused in multiple, unrelated classes, make it a trait. Only traits can be mixed into different parts of the class hierarchy
  • If you want to inherit from it in Java code, use an abstract class
  • If efficiency is very important, lean towards using a class. Most Java runtimes make a virtual method invocation of a class member a faster operation than an interface method invocation. Traits get compiled to interfaces and therefore may pay a slight performance overhead
Uncategorized

Scala’s Hierarchy

  • Just as Any is a superclass of every other class, Nothing is a subclass of every other class
  • The root class Any has two subclasses: AnyVal and AnyRef. AnyVal is the parent class of every built-in value class in Scala. There are nine such value classes: Byte, Short, Char, Int, Long, Float, Double, Boolean, and Unit. The first eight of these correspond to Java’s primitive types, and their values are represented at run time as Java’s primitive values. The other value class, Unit, corresponds roughly to Java’s void type; it is used as the result type of a method that does not otherwise return an interesting result
Uncategorized

Composition and Inheritance

Composition means one class holds a reference to another, using the referenced class to help it fulfil its mission. Inheritance is the superclass/subclass relationship.
  • The ++ operation concatenates two arrays
  • The zip method picks corresponding elements in its two arguments and forms an array of pairs. For instance, this expression:
Array(1, 2, 3) zip Array("a", "b")
Will evaluate to:
Array((1, "a"), (2, "b"))
Uncategorized

Options

Options allow us to protect against NullPointerException exceptions.
Option[A] is a container for an optional value of type A. If the value of type A is present, the Option[A] is an instance of Some[A], containing the present value of type A. If the value is absent, the Option[A] is the object None.
For example:
def track(user: Option[User]) = match {
     case Some(u) => Tracker.track(u.id)
     case None => Tracker.track(GUEST)
}
More compact way:
Tracker.track(user map (_.id) getOrElse GUEST)
They can accept filters (conditions) as well:
def track(user: Option[User]) = match {
     case Some(u) if u.canTrack => Tracker.track(u.id)
     case _ => Tracker.track(GUEST)
}
More compact way:
Tracker.track(user filter (_.canTrack) map (_.id) getOrElse GUEST)
More robust:
def track(user: Option[User]) = match {
     case Some(u) if u.canTrack => Tracker.track(u.id)
     case None => Tracker.track(GUEST)
     case _ =>
}
More compact way:
if (user forall (_.canTrack))
     Tracker.track(user map (_.id) getOrElse GUEST)
Uncategorized

Control Abstraction

  • Reducing code duplication
    • higher-order functions are functions which take functions as parameters and are better for simplifying code
  • Currying
    • A curried function is applied to multiple argument lists, instead of just one:
      • eg. normal method: def normalSum(num1: Int, num2: Int) = num1 + num2
      • eg. curried method: def plainOldSum(x: Int, y: Int) = x + y What’s happening here is that when you invoke curriedSum, you actually get two traditional function invocations back to back. The first function invocation takes a single Int parameter named x, and returns a function value for the second function. This second function takes the Int parameter y
  • Writing new control structures
    • In languages with first-class functions, you can effectively make new control structures even though the syntax of the language is fixed. All you need to do is create methods that take functions as arguments
Uncategorized

Functions and Closures

  • Local functions: in Scala, you can define functions inside other functions. They will be inaccessible from outside the containing method. They can access the parameters of their enclosing functions
  • Function literals:
var increase = (x: Int) => x + 1
increase(10) //prints 11
  • foreach method takes a function as an argument and invokes that function on each of its elements:
val myList = List (1, 2, 3)
myList.foreach((x: Int) =>  (x))
  • filter method selects those elements of the collection that pass a test the user supplies:
val myList = List (1, 2, 3)
println(myList.filter((x: Int) => x > 2)) //prints 3
//or mix them with foreach:
myList.filter((x: Int) => x > 2).foreach((x: Int) => println(x)) //prints 3
  • One way to make function literals more brief, is to leave off the parameter types. This is called target typing because the targeted usage of an expression is allowed to influence the typing of that expression:
println(myList.filter((x) => x > 2)) // same as x => x > 2
  • Placeholder syntax: you can use underscores as placeholders for one or more parameters, as long as each parameter appears only one time within the function literal. Multiple underscores mean multiple parameters, not reuse of a single parameter repeatedly:
myList.filter(_ > 2)
  • Partially applied function: is an expression in which you don’t supply all of the arguments needed by the function. Instead, you supply some, or none of the needed arguments. You can replace an entire parameter list with an underscore:
//e.g.1:
myList.foreach((x: Int) => println(x)) //is same as:
myList.foreach(println _) //is same as:
myList.foreach(println)

//e.g.2:
def sum(a: Int, b:Int, c: Int) = a + b + c
val a = sum _
a(1, 2, 3) // 6
val b = sum(1, _ : Int, 3)
b(3) // 7
  • Closures: closures capture variables themselves, not the value to which the variables refer:
val more = 10
val addMore = (x: Int) => x + more //more is called a free variable, x is bounded variable
  • Special function call forms: Scala supports repeated parameters, named arguments, and default arguments:
    • Repeated parameters: Scala allows you to indicate last parameter of a function may be repeated:
def echo(args: String*) = 
    for(arg <- args) println (arg)

echo(“Hello”, “world”)

//you can pass an array of strings to this method this way:
val strArr = Array(“Hello”, “World”)
echo(strArr: _*) //this tells the compiler to pass each element of array as its own argument to echo, rather than all of it as one single argument
    • Named arguments: named arguments allow you to pass arguments to a function in a different order:
def speed(distance: Float, time: Float): Float = distance / time
speed(time = 10, distance = 100)
    • Default parameter values: Scala lets you specify default values for function parameters
def speed(distance: Float, time: Float = 50): Float = distance / time
speed(distance = 100) //2
  • Tail recursion: functions which call themselves as the last action, are called tail recursive. The Scala compiler detects tail recursion and replaces it with a jump back to the beginning of the function, after updating the function parameters with the new values. So, if the method is tail recursive, there won’t be any runtime overhead to be paid:
def approximate(guess: Double): Double = {
    if (isGoodEnough(guess)) guess
    else approximate(improve(guess))