Skip to content

Instantly share code, notes, and snippets.

@edcote
Last active June 12, 2018 15:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edcote/72e5b8618c543ae48e000e617feb3545 to your computer and use it in GitHub Desktop.
Save edcote/72e5b8618c543ae48e000e617feb3545 to your computer and use it in GitHub Desktop.
Scala Notes

Scala Notes

Variables

Immutable and mutable

Scala has two kinds of variables, vals and vars:

  • val is immutable, cannot be reassigned
  • var is mutable, can be reassigned

Classes and Objects

Singleton objects

Scala as singleton objects, looks like class definition but uses keyword object.

Companion objects

When a singleton object shares the same name with a class, it is called that class's companion object.

Traits

Traits are similar to Java interfaces. They are used to define object types by specifying the 'signature' of the supported methods. A class can mix any number of traits (but only extend a single class).

Generic types and upper type bounds

In Scala, can use a type bound to limit the type of classes that can be parameterized. More information in Scala documentation

class Cage[P <: Pet](p: P) {
  def pet: P = p
}
object Main extends App {
  var dogCage = new Cage[Dog](new Dog)
  var catCage = new Cage[Cat](new Cat)
  /* Cannot put Lion in a cage as Lion is not a Pet. */
  //  var lionCage = new Cage[Lion](new Lion)

Constructors

Looks like message is passed as a parameter, but it can be used in the SayHi() method. It is actually a field in the class.

class Greeter(message: String) {
println("I am the primary constructor and the object is being instantiated")
  def SayHi() = println(message)
}
val greeter = new Greeter()
greeter.SayHi()

Apply function

Apply serves the purpose of closing the gap between OOP and FP paradigms. Each function in Scala can be represented by an object. Every object can also be treated as a function, provided it has an apply function. Also used for auxiliary constructors.

Using parameterized class

See http://blog.edmondcote.com/2017/05/use-of-hellaqueue-in-chisel.html for more information.

Functions and Functional Programming

To define a simple function

def max(x: Int, y: Init): Int = {
                          ^^ return type
  if (x < y)
    x
    ^^ return val
  else
    y
    ^^ return val
}

Functional style loop iteration

    args.foreach(arg => println(arg))
//               ^^ passing a function literal

    args.foreach((arg: String) => println(arg))
//                ^^ strict type inference

    args.foreach(println)
//               ^^ if functon literal has one statement and single argument
//                  use partially applied function shorthand

Anonymous functions

Functions are first class in Scala

() => println("I am an anonymous function!")

Here is a more elaborate example.

val concat_fruit = (x: String, y: String) => x + y // note, this is val not def

def apply_to_args(func: (String, String) => String, arg1: String, arg2: String): String = func(arg1,arg2) // pass anonymous function

assert(apply_to_args(concat_fruit, "apple", "orange") == concat_fruit("apple", "orange"))

Scala uses _ to act as a placeholder for parameters in the anonymous function.

List(1,2,3,4,5).foreach(print(_)) // is equivalent to ..
List(1,2,3,4,5).foreach( a => print(a))
val sum = List(1,2,3,4,5).reduceLeft(_+_) // is equivalent to ..
val sum = List(1,2,3,4,5).reduceLeft((a,b) =>  a + b)

Call by name notation

The type passed is substituted for the value inside the function. For example:

def f(x: => Int) = x * x
var y = 0
f { y +=1 ; y } // will execute like { y += 1; y } * { y += 1; y }

Simple currying example

The following implements what an append operation using currying:

def concat_curried(fruit: String)(veg: String): String = fruit + veg
val curried = concat_curried("apple")_
assert(curried("spinach") == "applespinach")

List operations

List comprehension using yield

Execute 'x + 1' on each element of list, return list.

val foo = Array(1,2,3,4,5)
val bar = for (x <- foo) yield x + 1
assert(bar.deep == Array(2,3,4,5,6).deep)

Zip

Explicit zip method, demo example:

val arr1 = Array(1,2,3)
val arr2 = Array(4,5,6)
assert(arr1.zip(arr2).deep == Array((1,4),(2,5),(3,6)).deep)

Zip with index, demo example:

assert(Array("foo","bar","baz").zipWithIndex.deep == Array(("foo",0),("bar",1),("baz",2)).deep)
val res = for ((y,x) <- Array("foo","bar","baz").zipWithIndex) yield (x,y) // reverse order
assert(res.deep == Array((0,"foo"),(1,"bar"),(2,"baz")).deep)

Map vs. For comprehension

For val b, we are passing a function to map.

val a = for (c <- Array(1,2,3)) yield c + 2
val b = Array(1,2,3).map(_ + 2) // not Map
assert (a.deep == b.deep)

Futures

A simple way to run an algorithm concurrently. It is said that a future returns "eventually". Here is a simplified example, from this site.

object Futures1 extends App {

  // 2 - create a Future
  val f = Future {
      sleep(500) // def sleep(time: Long) { Thread.sleep(time) }
      1 + 1
  }

  // 3 - this is blocking (blocking is bad)
  val result = Await.result(f, 1 second)
  println(result)
  sleep(1000)
}

Tutorial explains that the better approach to working with futures is to use a callback function. There are three callback methods: onComplete, onSuccess, and onFailure. In the example below, f.onComplete does not block.

object Example1 extends App {
    println("starting calculation ...")
    val f = Future {
        sleep(Random.nextInt(500))
        42
    }
    println("before onComplete")
    f.onComplete {
        case Success(value) => println(s"Got the callback, meaning = $value")
        case Failure(e) => e.printStackTrace
    }
    // do the rest of your work
    println("A ..."); sleep(100)
    println("B ..."); sleep(100)
    // [..]
    sleep(2000) // important to keep JVM from shutting down
}

A function can return a future.

object Cloud {
    def runAlgorithm(i: Int): Future[Int] = future {
        sleep(Random.nextInt(500))
        val result = i + 10
        println(s"returning result from cloud: $result")
        result
    }
}

You can run this algorithm in parallel and join once complete.

println("starting futures")
    val result1 = Cloud.runAlgorithm(10)
    val result2 = Cloud.runAlgorithm(20)
    val result3 = Cloud.runAlgorithm(30)

    println("before for-comprehension")
    val result = for {
        r1 <- result1
        r2 <- result2
        r3 <- result3
    } yield (r1 + r2 + r3)

    println("before onSuccess")
    result onSuccess {
        case result => println(s"total = $result")
    }

More information in this tutorial

Pattern matching

Pattern matching is similar to switch statement, but is more powerful.

def matchTest(x: Int): String = x match {
  case 1 => "one"
  case 2 => "two"
  case _ => "anything other than 1 or 2"
}

_ acts like a wildcard, it will match anything.

Macros

  • This code must be in separate object. Outside of compilation scope of where you want to use it.
  • Comment out uses of macro when changing the macro. Requires (I think) a two step compilation process.
package tests

import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros


object EngineTestMacros {
  /** Defines a ScalaTest using macro. */
  def defineTest(s: Any): Unit = macro defineTestMacro

  /** Implementation of defineTest. */
  def defineTestMacro(c: Context)(s: c.Expr[Any]): c.Expr[Unit] = {
    import c.universe._

    c.Expr(
      q"""
          val test: EngineTest = $s

          "Test '" + test.name + "'" should "not trigger assetions and pass golden check" in {
           // run test here        
          }
          Unit
       """)
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment