scala-kurs-oppgaver



scala-kurs-oppgaver

1 4


scala-kurs-oppgaver


On Github arktekk / scala-kurs-oppgaver

SCALA KURS

Jon-Anders Teigen

Arktekk

@jteigen

♥ MASSE programmering ♥

♥ MASSE teori ♥

Pauser når dere føler for det!

Kloke hoder tenker best sammen

  • Jobb i par
  • Diskuter i par/felleskap
  • Spør om hjelp om dere sitter fast

Men først ...

  • Hvem er du ?
  • Hva har du lyst til å lære ?

Ladies and gentlemen, start your engines..

"hello"             // string
true false          // boolean
5                   // int
5.5                 // double
5f                  // float
1e30f               // float exponential
1.0e-100            // exponential double
0xA                 // 10 hex int
012                 // 10 octal int
1000L               // long
'a'                 // character
'\u0041'            // unicode character
"""a multi
line "string"
"""                 // multiline escaped string
<hello/>            // xml
'alright            // symbol

variabler

var foo = 5
foo = 6

// eksplisitt typet
var foo:Int = 5

values

val x = "Hello"

// eksplisitt typet
val x:String = "Hello"

metode

def foo:Int = {
  return 5
}

metode

def foo:Int = {
  5
}

// siste expression returneres alltid

metode

def foo = {
  5
}

// return type inference

metode

def foo = 5

// single expression

void/Unit metoder

def something(i:Int):Unit = {
  println("Do " + i)
}

void/Unit metoder (deprecated)

def something(i:Int) {
  println("Do " + i)
}

// returnerer alltid Unit (ingen =)

void/Unit uten parameter

def printSomething() = {
  println("something")
}

// () indikerer sideeffekt som konvensjon

nøstet definisjon

val nesting = {
  def plus(a:Int, b:Int) = a + b
  val x = {
    val y = 5 - 3
    y - 2    
  }
  val z = plus(x, 10)
  z / 2
}

lazy

lazy val person = {
  println("fra DB")
  DB.findPersonByPk(5)
}
println("etter")
person.name

// etter
// fra DB

equality

val a = new String("Hello")
val b = new String("Hello")

a == b // true (java equals)
a eq b // false (java ==)

string interpolation

val msg = "World"

s"Hello ${msg}"

Java

class Person {
  private final String name;
  private Integer age;

  public Person(String name, Integer age){
    this.name = name;
    this.age = age;
  }

  public String getName(){
    return name;
  }

  public Integer getAge(){
    return age;
  }

  public void setAge(Integer age){
    this.age = age;
  }

  public static Person create30(String name){
    return new Person(name, 30);
  }

  @Override
  public String toString(){
    return "["+name+","+age+"]";
  }
}

object / class

object Person {
  def create30(name:String) = new Person(name, 30)
}

class Person(val name: String, var age: Int){
  override def toString = s"[$name,$age]"
}

unified access

class Person(private val _name: String,
             private var _age: Int) {

  def name = _name    

  def age = _age    

  def age_=(a:Int){
    _age = a    
  }
}

unified access

val person = Person.create30("foo")

person.name // foo

person.age = 29
person.age // 29

Tupler

val ab = (5, "Hello")
val ab = 5 -> "Hello"

val abc = (5.5, "World", List(1, 2, 3))

ab._1 // 5
ab._2 // "Hello"

abc._3 // List(1, 2, 3)

collections

collection "literals"

val list   = List(1, 2, 3)
val map    = Map(1 -> "a", 2 -> "3")
val set    = Set("a", "b", "c")
val vector = Vector("a", "b", "c")
val array  = Array(1, 2, 3)
val range  = 1 to 10

mutable

immutable

val list = List(1, 2, 3)
val cons = 1 :: 2 :: 3 :: Nil

val map = Map(1 -> "Hello")
val helloWorld = map + (2 -> "World")
val world = helloWorld - 1

val vector = Vector(1, 2)
val appended = vector :+ 3
val prepended = 1 +: appended

imperative vs functional

case class Person(name:String, age:Int)

val people = List(Person("kid-a", 10),
                  Person("kid-b", 12),
                  Person("mom", 42),
                  Person("dad", 43))
var ages = new ListBuffer[Int]

for(person <- people){
  ages += person.age
}

val ages = people.map(person => person.age)

// List(10, 12, 42, 43)
var kids = new ListBuffer[Person]

for(person <- people){
  if(person.age < 18)
    kids += person
}

val kids = people.filter(person => person.age < 18)

// List(Person("kid-a", 10), Person("kid-b", 12))
case class Owner(pets:List[String])
val owners = List(Owner(List("Dog", "Cat")),
                  Owner(List("Fish")))

var pets = new ListBuffer[String]

for(owner <- owners){
  pets ++= owner.pets
}

val pets = owners.flatMap(owner => owner.pets)

// List("Dog", "Cat", "Fish")
var kids   = new ListBuffer[Person]
var adults = new ListBuffer[Person]

for(person <- people){
  if(person.age < 18)
    kids += person
  else
    adults += person
}

val (kids, adults) = people.partition(person => person.age < 18)

val (kids, adults) = people.partition(_.age < 18)

mer snacks

val first10 = list.take(10)

val dropped = list.drop(5)

case class Person(fornavn:String, etternavn:String)

val personer:List[Person] = ...

val familier:Map[String, List[Person]] =
  personer.groupBy(_.etternavn)

val fornavn:Map[String, List[String]] = 
  familier.mapValues(personer => personer.map(_.fornavn))

val sortert:Seq[(String, List[String])] =
  fornavn.toSeq.sortBy(_._1.size)

collections er funksjoner

val list = List(1,2,3)
list(0) // of its indexes

val map = Map("A" -> 1, "B" -> 2)
map("A") // of its keys

val set = Set(1,2,3)
set(1) // contains

Null ♥ ?

import java.util.HashMap
val map = new HashMap[Int, String]
map.put(0, null)
...
map.get(0) // null
map.get(1) // null

if(map.containsKey(0)){
  return map.get(0)
}

option

val map = Map(1 -> "Hello", 2 -> "World")
map.get(1) // Some("Hello")
map.get(0) // None

map(1) // "Hello"
map(0) // java.util.NoSuchElementException
trait Option[+A] {
  def get:A // eller java.util.NoSuchElementException
  def getOrElse(a:A):A
  def map(f:A => B):Option[B]
  def flatMap(f:A => Option[B]):Option[B]
}

object None extends Option[Nothing]
case class Some[A](value:A) extends Option[A]

NullPointerException ♥ ?

class Stuff {
  /* can be null */    
  public String getIt(){ ... }
}
val stuff:Option[String] = Option(stuff.getIt)

oppgavetid

https://github.com/arktekk/scala-kurs-oppgaver/tree/master/buzzword

funksjoner

val length:String => Int =
  in => in.length

def takesAFun(f:String => Int) = {
  val word = "Hello World"
  f(word)
}

takesAFun(length)

takesAFun(in => in.length)

takesAFun(_.length)

metoder ~= funksjoner

def length(in:String) = in.length

takesAFun(length)
val noArg:() => Int =
  () => 10

def takesNoArg(f:() => Int) = {
  val anInt = f()
  5 + anInt
}

takesNoArg(noArg)

takesNoArg(() => 10)

call-by-name

def callByName(f: => Int) = {
  val anInt = f
  5 + anInt
}

callByName(5)

def callByNameIgnore(f: => Int){
  5 + 10
}

callByNameIgnore(throw new Exception("who cares?"))
// ingen exception kastet

parameter-set

def multiple(name:String)(age:Int) {
  println(name + " is " + age + " years old")
}

multiple("Mr Java")(99)

multiple("Mr Scala"){
  5 + 10
}
def debug(name:String)(expr: => String){
  val logger = Logger.getLogger(name)
  if(logger.isDebugEnabled){
    logger.debug(expr)    
  }
}

debug("database"){
  DB.reallyHeavyOperation.mkString(",")
}

partial application

def printIt(name:String) = println("Look, its " + name)

val partiallyApplied:String => Unit = printIt _

partiallyApplied("pretty cool")
// Look, its pretty cool
val later = new ListBuffer[() => Unit]

def register(f: => Unit){
  later += f _
}

register(println("Hello"))
register(println("World"))
register(throw new Exception("Oh noes"))

println("Go!")
later.foreach(f => f())
/*
Go!
Hello
World
java.lang.Exception: Oh noes
  at ....
*/

traits

trait Mult {
  def mult(a:Int, b:Int) = a * b
}

trait Add {
  def add(a:Int, b:Int) = a + b
}

object Calc extends Mult with Add

Calc.mult(4, Calc.add(1, 2))

stackable modifications

trait Rule {
  def convert(int:Int) = int.toString
}

trait Fizz extends Rule {
  override def convert(int:Int) =
    if(int % 3 == 0) "Fizz" else super.convert(int)
}

trait Buzz extends Rule {
  override def convert(int:Int) =
    if(int % 5 == 0) "Buzz" else super.convert(int)
}

trait FizzBuzz extends Rule {
  override def convert(int:Int) =
    if(int % 3 == 0 && int % 5 == 0) "FizzBuzz"
    else super.convert(int)
}

object FizzTest extends Rule with Fizz
(0 to 15).foreach(FizzTest.convert)

object BuzzTest extends Rule with Buzz
(0 to 15).foreach(BuzzTest.convert)

object FizzBuzzTest extends Rule with Fizz with Buzz with FizzBuzz
(0 to 15).foreach(FizzBuzzTest.convert)

object BuzzFizzTest extends Rule with FizzBuzz with Fizz with Buzz
(0 to 15).foreach(BuzzFizzTest.convert)

pattern matching & case classes

sealed trait Tree
case class Branch(left:Tree, right:Tree) extends Tree
case class Leaf(value:Int) extends Tree

def sum(tree:Tree):Int = tree match {
  case Branch(left, right) => sum(left) + sum(right)
  case Leaf(value) => value
}

val tree = Branch(
    Branch(Leaf(1), Leaf(2)),
    Branch(Leaf(3), Leaf(4)))

sum(tree)

pattern matching og exceptions

case class SomeException(why:String) extends Exception(why)

try{
  somethingThatCanThrowAnException()
} catch {
  case SomeException(why) => println("Thats why: " + why)
  case ex:Exception => ex.printStackTrace
}

a.k.a 'Enrich my Library'

adapter pattern

class PlusMinus(i:Int){
  def +- (o:Int) = i-o to o+i
}

new PlusMinus(5) +- 2
//Range(3, 4, 5, 6, 7)

implicit conversions

implicit class PlusMinus(i:Int){
  def +- (o:Int) = i-o to o+i
}

5 +- 2
//Range(3, 4, 5, 6, 7)
class PlusMinus(i:Int){
  def +- (o:Int) = i-o to o+i
}

implicit def plusMinus(i:Int) = new PlusMinus(int)

5 +- 2
//Range(3, 4, 5, 6, 7)

Oppgavetid :-)

Skriv ditt eget testrammeverk!

https://github.com/arktekk/scala-kurs-oppgaver/tree/master/testframework

CanBuildFrom ???

def map [B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[Set[A], B, That]): That
trait Traversable[+A] extends TraversableLike[A, Traversable[A]] with ... {
  ...
}

// alle implementasjonene
trait TraversableLike[+A, +Repr] {

  def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
    val b = bf(repr)
    b.sizeHint(this) 
    for (x <- this) b += f(x)
    b.result
  }
  ...
}

implicit instanser av CanBuildFrom

object BitSet extends BitSetFactory[BitSet] {  
  def newBuilder = immutable.BitSet.newBuilder  
  implicit def canBuildFrom: CanBuildFrom[BitSet, Int, BitSet] = bitsetCanBuildFrom
}

object Set extends SetFactory[Set] {
  def newBuilder[A] = immutable.Set.newBuilder[A]
  implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Set[A]] = setCanBuildFrom[A]
}

val b2:BitSet = BitSet(1, 2, 3).map(i => i * 2)
val Set[String] = b2.map(i => i.toString)

[usecase] vs full

trait Set[A]{
  def map [B](f: (A) ⇒ B): Set[B] // [use case] 

  def map [B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[Set[A], B, That]): That
}

din egen collection

package lst

import collection.mutable.{Builder}
import collection.immutable.LinearSeq
import collection.LinearSeqOptimized
import collection.generic.{GenericTraversableTemplate, SeqFactory}

object Lst extends SeqFactory[Lst] {
  def newBuilder[A]: Builder[A, Lst[A]] = new Builder[A, Lst[A]] {
    private[this] var lst: Lst[A] = Empty

    def +=(elem: A) = {
      lst = Cons(elem, lst)
      this
    }

    def clear() { lst = Empty }

    def result() = lst
  }
}
sealed trait Lst[+A]
  extends LinearSeq[A]
  with GenericTraversableTemplate[A, Lst]
  with LinearSeqOptimized[A, Lst[A]] {

  override def companion = Lst
}

case object Empty extends Lst[Nothing]{
  override def isEmpty = true
}

final case class Cons[A](override val head: A, override val tail: Lst[A]) extends Lst[A]{
  override def isEmpty = false
}

collections

Traversable

  • base trait for all scala collections
  • implementerer masse felles metoder kun via foreach (over 50)

trait Traversable[+A]{
  def foreach[U](f:A => U):Unit    
}

Iterable

trait Iterable[+A] extends Traversable[A]{
  def iterator:Iterator[A]
}

Seq

  • ordnet
  • indeksert
  • IndexedSeq: optimalisert random access & length
  • LinearSeq: optimalisert head / tail

trait Seq[+A] extends Iterable[A]{
  def apply(idx:Int):A
  def length:Int
  def iterator:Iterator[A]
}

companion objects m/magisk "apply"

object List {
  def apply[A](elems:A*):List[A] = ...
}

List(1, 2, 3)

object Map {
  def apply[A, B](elems:(A, B)*):Map[A, B] = ...
}

Map(1 -> "a", 2 -> "b")

Set

  • ingen duplikate elementer
  • SortedSet: sortert
  • BitSet extends Set[Int]: raskt og bruker lite minne

trait Set[A] extends Iterable[A] {
  def +(elem:A):Set[A]
  def -(elem:A):Set[A]
  def contains(elem:A):Boolean
  def iterator:Iterator[A]
}

Map

trait Map[A, +B] extends Iterable[(A, B)]{
  def +[B1 >: B](kv:(A, B1)):Map[A, B1]
  def -(key:A):Map[A, B]
  def get(key:A):Option[B]
  def iterator:Iterator[A]
}

mutable

mutable

  • Stooooort utvalg med forskjellige optimaliseringer
  • Synkronisering via traits (stackable modifications)!

import collection.mutable._

val myMap = new HashMap[Int, String] with SynchronizedMap[Int, String]
val myBuffer = new ListBuffer[String] with SynchronizedBuffer[String]
val mySet = new HashSet[String] with SynchronizedSet[String]

immutable

immutable

List

  • single linket liste
  • LinearSeq
  • O(1) head / prepend / tail
  • O(n) random access
  • Nil og :: (Cons)

Vector & HashMap

  • Vector extends IndexedSeq
  • Hash tries (tre-struktur)
  • Clojure / Rich Hickey / Phil Bagwell
  • O(log32(n))
  • Ekstremt god general-purpose datastruktur

Stream

  • lazy list
  • tail er evaluert kun når den er aksessert
  • muligjør uendelige lister !!

sieve of eratosthenes

def sieve(s: Stream[Int]): Stream[Int] =
  Stream.cons(s.head, sieve(s.tail filterNot { _ % s.head == 0 }))

def primes = sieve(Stream.from(2))

Oppgavetid :-)

Implementer metodene i List selv

https://github.com/arktekk/scala-kurs-oppgaver/tree/master/list

Dispatch

  • request builder - definerer request
  • handler - håndterer respons
  • executor - gjør selve kallet
import dispatch._

val request = url("http://www.yr.no/place/Norway/Telemark/Sauherad/Gvarv/forecast_hour_by_hour.xml")
val handler = request.as_str
val result = Http(handler)

request

url("http://www.yr.no") / "place" / "Norway" / "Telemark" 
    / "Sauherad" / "Gvarv" / "forecast_hour_by_hour.xml"

url("http://sporing.posten.no/sporing.html") <<? Map("q" -> "123123123")

handlers

val http = new Http
val request = url("http://scala-lang.org")

http(request >>> System.out) // til OutputStream

http(request as_str) // som string

http(request <> ((xml:Elem) => xml \ "foo" \ "bar") // håndtert som xml

import tagsoup.TagSoupHttp._
http(request </> ((xml:NodeSeq) => xml \\ "body" \ "@href") // vasket html og håndtert som xml

executors

  • Threadsafe m/threadpool Http / new Http with thread.Safety
  • Current Thread new Http
  • NIO new nio.Http
  • Google App Engine new gae.Http
import xml._
import dispatch._

val http = new Http
def parse(xml:Elem) = 
  for {
    consignment <- xml \ "Consignment"
    totalweight <- consignment \ "TotalWeight"
  } yield totalweight.text

http(url("http://beta.bring.no/sporing/sporing.xml") <<? 
    Map("q" -> "TESTPACKAGE-AT-PICKUPPOINT") <> parse)

<ConsignmentSet xmlns="http://www.bring.no/sporing/1.0">
  <Consignment consignmentId="SHIPMENTNUMBER">
    <TotalWeight unitCode="kg">16.5</TotalWeight>
      ..

// List(16,5)

Oppgavetid :-)

https://github.com/arktekk/scala-kurs-oppgaver/tree/master/music

for-comprehensions

  • konsis måte å jobbe med "collections" og lignende
  • haskell, python, erlang og mange andre har varianter
  • syntax sukker for
    • foreach
    • map
    • flatMap
    • withFilter
  • lettere ?

foreach

for {
  a <- List(1, 2)
  b <- List(3, 4)
} println(a + b)

List(1, 2).foreach {
  a => List(3, 4).foreach {
    b => println(a + b)
  }
}

map

for {
  a <- List(1, 2)
} yield a + 1

List(1, 2).map(a => a + 1)

flatMap

for {
  a <- List(1, 2)
  b <- List(3, 4)
  c <- List(5, 6)
} yield a + b + c


List(1, 2).flatMap {
  a => List(3, 4).flatMap {
    b => List(5, 6).map {
      c => a + b + c
    }
  }
}

withFilter

for {
  a <- List(1, 2)
  b <- List(3, 4)
  if a + b < 5
} yield a * b

List(1, 2).flatMap {
  a => List(3, 4).withFilter {
    b => a + b < 5
  }.map{
    b => a * b
  }
}

pattern matching

val R = "\\d+".r
for {
  R(a) <- List("123", "abc", "321")    
} yield a

List("123", "abc", "321").withFilter {
  case R(a) => true
  case _ => false
}.map{
  case R(a) => a
}

inline variabler

for {
  a <- List(1, 2)
  b = a + 1
  c <- List(3, 4)
} yield a + b + c

List(1, 2).map { a =>
  val b = a + 1
  (a, b)
}.flatMap { case (a, b) =>
  List(3, 4).map {
    c => a + b + c
  }
}

fordeler og ulemper ?

  • skrive
  • lese
  • forstå
  • forstå logikken

implicits

  • conversions
  • parameters

bruk/patterns

  • pimp/enhance-my-library
  • adapters
  • type-classes
  • constraints
  • JavaConversions & JavaConverters
  • Manifests

Jorge's lover

Thou shalt only use implicit conversions for one of two (2) reasons:

Pimping members onto an existing type "Fixing" a broken type hierarchy

pimp-my-library

Pimping members onto an existing type
class StringOps(s:String){
  def toInt = java.lang.Integer.parseInt(s)
}

implicit def augmentString(s:String):StringOps = new StringOps(s)

val i = "543".toInt

// augmentString("543").toInt

implicit classes

implicit class StringOps(s:String){
  def toInt = java.lang.Integer.parseInt(s)
}

scala.Predef

  • alltid importerte typer, metoder og implicits
  • et lite utvalg av de 78
type List[+A] = collection.immutable.List[A]
type Pair[+A, +B] = Tuple2[A, B]

implicit def augmentString(s:String):StringOps = new StringOps(s)

final class ArrowAssoc[A](val x: A) {
  @inline def -> [B](y: B): Tuple2[A, B] = Tuple2(x, y)
}
implicit def any2ArrowAssoc[A](x: A) = new ArrowAssoc(x)

inspiser implicits i scope med REPL

> :implicits
> :implicits -v

adapters

  • Jorge's lov #2 "fixing a broken type hierarchy"
  • Er dette eksemplet i henhold ?
object Runnables {
  implicit def function2Runnable(f:() => Unit) = new Runnable{
    def run() { f() }
  }
}
import Runnables._

SwingUtilities.invokeLater(() => println("Too convenient ?"))

Java interop m/ implicits

JavaConversions

implicit conversions mellom scala og java collections

import collection.JavaConversions._

val list:java.util.List[String] = Seq("hello", "world")

val seq:Seq[String] = list

JavaConverters

asScala og asJava metoder på java og scala collections

import collection.JavaConverters._

val list:java.util.List[String] = Seq("Hello", "World").asJava

val seq:Seq[String] = list.asScala

diskusjon

  • hvilken er lettest å bruke ?
  • hvilken er lettest å lese/forstå ?
  • bryter JavaConversions med Jorge's lov #2 ?

implicit parameters

implicit val msg = "Hello"

def sayHello(s:String)(implicit m:String) = m + " " + s

sayHello("World")

> Hello World

implicitly

gir deg tilgang til implicit parameters

val ordering = implicitly[Ordering[Int]]

ordering.compare(1, 2)

> -1
// implementasjon
def implicitly[A](implicit a:A):A = a

view bounds

  • definerer at vi skal kunne se på A som om den var en B
  • dvs: at det finnes implicit conversion A => B
object Min {
  def min[A <% Ordered[A]](a1:A, a2:A) = 
    if(a1 < a2) a1 else a2

  // sukker for
  def min[A](a1:A, a2:A)(implicit ev:A => Ordered[A]) = ...
}

case class Num(i:Int)

Min.min(Num(1), Num(2))
No implicit view available from MinRun.Num => Ordered[MinRun.Num].
[error]   Min.min(Num(1), Num(2))
[error]          ^

context bounds

  • definerer at det finnes implicit parameter Ordering[A]
object Min {
  def min[A : Ordering](a1:A, a2:A) =
    if(implicitly[Ordering[A]].lt(a1, a2)) a1 else a2

  // sukker for
  def min[A](a1:A, a2:A)(implicit ev:Ordering[A]) = ...
}

Min.min(Num(1), Num(2))

// veldig fin for å kalle videre..
def min2[A : Ordering](a1:A, a2:A) = Min.min(a1, a2)

evidence types

  • bruk typesystemet til å bevise ting
class Foo[A](a:A){
  def int(implicit ev:A =:= Int):Int = a 
}

new Foo(0).int

new Foo("Hello").int
// error: Cannot prove that java.lang.String =:= Int

classtag

  • reified generics (runtime type)
  • påkrevd for instansiering av Arrays
def newInstance[A](implicit c:reflect.ClassTag[A]):A = 
  manifest.runtimeClass.asInstanceOf[Class[A]].newInstance

newInstance[java.util.ArrayList[String]]


object Array {
  def apply(elms:A*)[A : ClassManifest]:Array[A] = ...
}

def create[A](a:A) = Array(a)            // kompilerer ikke
def create[A : ClassTag](a:A) = Array(a) // ok

not found

trait Msg[A]{
  def msg(a:A)
}

object MittApi {
  def needsMsg[A : Msg](a:A){ ... }
}

// brukers kode
MittApi.needsMsg("Hello")

/*
could not find implicit value for evidence parameter of type implicitstuff.Msg[java.lang.String]
    MittApi.needsMsg("Hello")
            ^
*/

not found

import annotation.implicitNotFound

@implicitNotFound("Du må definere/importere en implicit instans av Msg[${A}]")
trait Msg[A]{
  def msg(a:A)
}

object MittApi {
  def needsMsg[A : Msg](a:A){ ... }
}

// brukers kode
MittApi.needsMsg("Hello")

/*
Du må definere/importere en implicit instans av Msg[java.lang.String]
    MittApi.needsMsg("Hello")
            ^
*/

implicit resolution

  • local
  • imported / package object
  • companion

http://eed3si9n.com/revisiting-implicits-without-import-tax

type classes

  • ad-hoc polymorfi
  • haskell
  • unngår problemet med tunge arve-hierarki
  • kan ha flere instanser per type
  • Comparable vs Comparator
  • bruker tar instans av type class som implicit parameter

java.util.Comparator som type class

implicit object IntComparator extends java.util.Comparator[Int]{
  def compare(a:Int, b:Int) = a - b 
}
def myCompare[T](a:T, b:T)(implicit comarator:java.util.Comparator[Int]) = 
  comparator.compare(a, b)

myCompare(1,2)

myCompare("Hello", "World")
// error: could not find implicit value for evidence parameter 
//          of type java.util.Comparator[java.lang.String]

parametere og conversions kombinert

implicit class Syntax[A](a:A){
  def === (other:A)(implicit c:java.util.Comparator[A]) = 
    c.compare(a, other) == 0
}

5 === 4

pattern matching

  • "switch på steroider"
  • destructuring & matching
  • vanlig blant funksjonelle språk som haskell, erlang, ml osv

eksempel

  • gitt en List[List[Int]]
  • når det første element er er en liste hvor første element er er 1, 2 eller 3, return det tallet
  • når det andre elementer i lista er en liste, returner det andre elementet i den listen hvis det eksisterer
  • ellers returner 0
public int f(List<List<Integer>> l){
  if(l != null){
    if(l.size() >= 1){
      List<Integer> l2 = l.get(0);
      if(l2 != null && l2.size() >= 1){
        Integer i = l2.get(0);
        if(i == 1 || i == 2 || i == 3)
          return i;
      }
    }
    if(l.size() >= 2){
      List<Integer> l2 = l.get(1);
      if(l2 != null && l2.size() >= 2)
        return l2.get(1);      
    }
  }
  return 0;
}
def f(l:List[List[Int]]) = l match {
  case List(List(x @ (1 | 2 | 3), _*), _*) => x
  case List(_, List(_, x, _*),_*) => x
  case _ => 0
}

wildcard

x match {
  case _ =>
}

variable

x match {
  case y =>
}

typed

x match {
  case _:Foo =>
  case a:C =>
  case b:p.C =>
  case c:T#C =>
  case d:Singleton.type => 
  case e:(A with B with C) =>
}

binding

x match {
  case y @ _ =>
}

literal

x match {
  case "hello" =>
  case 5 =>
  case true =>
  case 'A' =>
  case 5.5 =>
}

@switch

(x: @switch) match {
  case 1 =>
  case 2 =>
  case 3 =>
}

@switch

(x: @switch) match {
  case 1 =>
  case 2 =>
  case "3" =>
}

// error: could not emit switch for @switch annotated match

stable identifier ?

val hello = "hello"
"world" match {
  case hello => // matcher
}

stable identifier

val Hello = "hello"
"world" match {
  case Hello => // matcher ikke
}

stable identifier

def f(x:Int, y:Int) = x match {
  case `y` =>
}

constructor

case class Foo(a:String, b:Int)

x match {
  case Foo(aString, anInt) =>
}

constructor

case class Foo(a:String, b:Int)

x match {
  case Foo("Hello", 5) =>
  case Foo(aString, anInt) =>
}

Algebraiske Data Typer

sealed trait Tree
case class Branch(l:Tree, r:Tree) extends Tree
case class Leaf(v:Int) extends Tree

def sum(t:Tree):Int = t match {
  case Branch(l, r) => sum(l) + sum(r)
  case Leaf(v) => v
}

val tree = Branch(
  Branch(Leaf(1), Leaf(2)),
  Branch(Leaf(3), Leaf(4)))
sum(tree)

tuple

x match {
  case (first, second) =>
}

tuple

x match {
  case (first, second, third) =>
}

sequences

x match {
  case List(a, b) =>
  case List(a, b, c, _*) =>
}

sequences

x match {
  case List(a, b) =>
  case List(a, b, c, d @ _*) =>
}

extractors

def unapply(a:A):Boolean

def unapply(a:A):Option[B]

def unapplySeq(a:A):Option[Seq[B]]

unapply

object Empty {
  def unapply(a:String):Boolean = a.trim.size == 0
}

x match {
  case Empty() =>
}

unapply

object Even {
  def unapply(a:Int):Option[Int] =
    if(a % 2 == 0) Some(a) else None
}

x match {
  case Even(even) => 
}
class Name{
  String first; String last;
  public Name(first:String, last:String){
    this.first = first; this.last = last;    
  }
}
object FirstLast {
  def unapply(name:Name):Option[(String, String)] = 
    Some(name.first, name.last)
}

(new Name("scala", "kurs")) match {
  case FirstLast(first, last) =>
}

unapplySeq

object Csv {
  def unapplySeq(s:String) = Some(s.split(",").toSeq)
}

"1,2,3" match {
  case Csv(a, b, c) => a+"-"+b+"-"+c
}

extractor tree

sealed trait Tree
object Branch {
  private class Impl(val l:Tree, r:Tree) extends Tree
  def apply(l:Tree, r:Tree):Tree = new Impl(l, r)
  def unapply(t:Tree) = t match {
    case i:Impl => Some(i.l, i.r)
    case _ => None    
  }
}
object Leaf {
  private class Impl(val v:Int) extends Tree
  def apply(v:Int):Tree = new Impl(v)
  def unapply(t:Tree) = t match {
    case i:Impl => Some(i.v)
    case _ => None    
  }
}
def sum(tree:Tree):Int = tree match {
  case Branch(left, right) => sum(left) + sum(right)
  case Leaf(value) => value
}

infix

case class XX(a:String, b:Int)

x match {
  case a XX b =>
}

infix

case class ::[A](head:A, tail:List[A]) extends List[A] {
  def ::[B >: A](b:B):List[B] = ::(b, this)
}

(1 :: 2 :: 3 :: Nil) match {
  case a :: b :: c :: Nil =>
}

infix

case class ::[A](head:A, tail:List[A]) extends List[A] {
  def ::[B >: A](b:B):List[B] = ::(b, this)
}

(1 :: 2 :: 3 :: Nil) match {
  case ::(1, ::(2, ::(3, Nil))) =>
}

infix

object -> {
  def unapply[A, B](ab:(A, B)):Option[(A, B)] =
    Some(ab)
}

(1 -> 2) match {
  case a -> b => 
}

(1 -> 2 -> 3) match {
  case a -> b -> c =>
}

infix

case class EX(l:List[String], num:Int, s:String)

x match {
  case l EX (num, s) => 
}

infix

case class EX(l:List[String], num:Int, s:String)

x match {
  case a :: b :: c EX (num, s) => 
}

alternatives

x match {
  case 1 | 2 =>
  case _:Foo | _:Bar =>
}

alternatives

try {
  // throw something
} catch {
  case e @ (_:ExA | _:ExB) => // multicatch
}

xml

<foo>bar</foo> match {
  case <foo>{what}</foo> =>
}

guards

x match {
  case y if y > 2 =>
}

partial functions

val pos:PartialFunction[Int, String] = {
  case n if n > 0 => n.toString
}

pos.isDefinedAt(5) // true
pos.isDefinedAt(-1) // false

pos(5)  // "5"
pos(-1) // java.util.NoSuchElementException

partial functions

val pos:PartialFunction[Int, String] = {
  case n if n > 0 => n.toString
}

val notPos:PartialFunction[Int, String] = {
  case n if n <= 0 => "Not positive"
}

val all = pos orElse notPos

for(i <- -5 to 5)
  println(all(i))
val Num = "(\\d+)".r
List("123", "abc", "321").collect{
  case Num(num) => num.toInt
}
// List(123, 321)

functions

List(-2, -1, 0, 1, 2).foldLeft(0){
  case (a, e) => if e < 0 => a
  case (a, e) => a + e
}

assignment

val (minors, adults) = people.partition(_.age < 18)

val Email = "(.+)@(.+)".r
val EMail(name, domain) = "foo@bar.com"

for-comprehensions

object Even {
  def unapply(i:Int) = 
    if(i % 2 == 0) Some(i) else None
}

for{
  Even(number) <- List(1, 2, 3, 4)
} yield number

erasure

val list:List[Any] = List(1, 2, 3)

list match {
  case x:List[String] => "Strings" // matcher
  case x:List[Int] => "Ints"
}

don't

val a:Option[Int] = ...

a match {
  case Some(i) => Some(i * 2)
  case _ => None
}

do

val a:Option[Int] = ...

a.map(i => i * 2)

don't

val a:Option[String] = ...
val b:Option[String] = ...

(a, b) match {
  case (Some(hello), Some(world)) => Some(hello + " " + world)
  case _ => None
}

do

val a:Option[String] = ...
val b:Option[String] = ...

for{
  hello <- a
  world <- b
} yield hello + " " + world

Oppgavetid :-)

Skriv ditt eget webrammeverk! (thats right)

webframework

typesystemet

  • declaration-site variance
  • bounds
  • type variabler mm.
  • turing complete!

variance (wikipedia)

Within the type system of a programming language, covariance and contravariance refers to the ordering of types from narrower to wider and their interchangeability or equivalence in certain situations (such as parameters, generics, and return types).

  • covariant: converting from wider (double) to narrower (float).
  • contravariant: converting from narrower (float) to wider (double).
  • invariant: Not able to convert.

sub/super typing

alle typer er

  • subtyper av seg selv
  • supertyper av seg selv

invariance

type X[T] // T er invariant

X[A] kan benyttes for X[B] hvis A == B

+covariance

type X[+T] // + betyr at T er covariant

X[A] kan benyttes for X[B] hvis A er subtype av B

-contravariance

type X[-T] // - betyr at T er contravariant

X[A] kan benyttes for X[B] hvis A er supertype av B

arvehierarki (gammelt nytt)

class SuperType
class TheType extends SuperType
class SubType extends TheType

def x(theType:TheType){ ... }

x(new SuperType) // ikke ok 
x(new TheType)
x(new SubType)   // ok

invariant

class Invariant[A](a:A){
  def get:A = a
  def set(a:A){ ... }
}

def in(invariant:Invariant[TheType]){ ... }

in(new Invariant[SuperType]) // ikke ok
in(new Invariant[TheType])
in(new Invariant[SubType])   // ikke ok

+covariant

class Covariant[+A](a:A){
  def get:A = a
  def set(a:A){ ... } // ikke ok, contravariant position
}

def co(covariant:Covariant[TheType]){ ... }

co(new Covariant[SuperType]) // ikke ok
co(new Covariant[TheType])
co(new Covariant[SubType])   // ok

-contravariant

class Contravariant[-A](a:A){
  def get:A = a // ikke ok, covariant position
  def set(a:A){ ... }
}

def contra(contravariant:Contravariant[TheType]){ ... }

contra(new Contravariant[SuperType]) // ok
contra(new Contravariant[TheType]) 
contra(new Contravariant[SubType])   // ikke ok

type bounds

  • upper bound
  • lower bound

upper bound

  • beskriver subtype relasjon
A <: B // A subtype av B
class Foo
class Bar extends Foo 

class Foos[F <: Foo](var init:F){
  def set(f:F){ init = f }
  def get = init
}

val bars = new Foos[Bar](new Bar)
bars.set(new Bar)
val f:Bar = bars.get

// found Foo, required Bar  
bars.set(new Foo) 

// type arguments [String] do not conform to class 
// Foos's type parameter bounds [F <: Foo]
val strings = new Foos[String]("")

lower bound

  • beskriver supertype relasjon
A >: B // A supertype av B
class Foo
class Bar extends Foo

class Generator[F >: Bar]{
  def next:F = new Bar
}

val gen = new Generator[Foo]
val foo:Foo = gen.next

eksempel med List

sealed trait Lst[A]
case object Empty extends Lst[Nothing]
case class Cons[A](head:A, tail:Lst[A]) extends Lst[A]

Cons("Hello", Empty)
sealed trait Lst[A]
case object Empty extends Lst[Nothing]
case class Cons[A](head:A, tail:Lst[A]) extends Lst[A]

Cons("Hello", Empty)
// found   : Empty.type (with underlying type object Empty)
// required: Lst[java.lang.String]
// Note: Nothing <: java.lang.String (and Empty.type <: Lst[Nothing]), 
// but trait Lst is invariant in type A.
// You may wish to define A as +A instead. (SLS 4.5)
//    Cons("Hello", Empty)
sealed trait Lst[+A] // ok, fixed it
case object Empty extends Lst[Nothing]
case class Cons[A](head:A, tail:Lst[A]) extends Lst[A]

Cons("Hello", Empty)
sealed trait Lst[+A]{
  def ::(a:A):Lst[A] = Cons(a, this)
}
case object Empty extends Lst[Nothing]
case class Cons[A](head:A, tail:Lst[A]) extends Lst[A]

// i bruk
"Hello" :: Empty
sealed trait Lst[+A]{
  def ::(a:A):Lst[A] = Cons(a, this)
}
case object Empty extends Lst[Nothing]
case class Cons[A](head:A, tail:Lst[A]) extends Lst[A]

/*
covariant type A occurs in contravariant position in type A of value a
     def ::(a:A):Lst[A] = Cons(a, this)
            ^
*/
sealed trait Lst[+A]{
  def ::[B >: A](b:B):Lst[B] = Cons(b, this) // ok, fixed it
}
case object Empty extends Lst[Nothing]
case class Cons[A](head:A, tail:Lst[A]) extends Lst[A]

val lst:Lst[String] = "Hello" :: Empty    
val lst2:Lst[Any] = 1 :: "Hello" :: Empty

XML

  • <literal xml="support"/>
  • kan navigeres som en collection (for-comprehensions)
  • korrekt escaping
  • ganske immutable (men ikke helt)
  • snodig arvehierarki
<?xml version="1.0" encoding="utf-8"?>
<weatherdata>
  <forecast>
    <text>
      <location>
        <time from="2011-12-23" to="2011-12-24" type="obsforecast">
          <title>Friday and Saturday</title>
          <body>&lt;strong&gt;Telemark:&lt;/strong&gt; Våte veibaner og synkende 
          temperatur kan stedvis gi glattere veier fra natt til julaften.</body>
        </time>
        <time from="2011-12-24" to="2011-12-25">
          <title>Saturday and Sunday</title>
          <body>&lt;strong&gt;Østlandet og Telemark:&lt;/strong&gt; Vestlig bris. Stort sett
          pent vær. I kveld sørvestlig bris, økende til liten og periodevis stiv kuling 15 m/s på    
          kysten. Skyet eller delvis skyet. Oppholdsvær, men lengst vest regn, snø i høyden. Søndag
          kveld sørvestlig frisk bris 10, sterk kuling 20 på kysten. Spredt regn, snø i høyden. Nedbør
          vesentlig i vestlige områder.</body>
        </time>
        ...
case class Time(from:String, to:String, title:String, body:String)

val xml = XML.load(getClass.getResourceAsStream("forecast_hour_by_hour.xml"))

val forecasts = for {
  forecast <- xml      \  "forecast"
  text     =  forecast \  "text" // child select
  time     <- text     \\ "time" // deep select
  from     =  time     \ "@from" // attribute select
  to       =  time     \ "@to"
  title    =  time     \ "title"
  body     =  time     \ "body"
} yield Time(from.text, to.text, title.text, body.text)
<weatherdata>
  <forecast>
    <text>
      <location>{ forecasts.flatMap{ case Time(from, to, title, body) =>
        <time from={from} to={to}>
          <title>{title}</title>
          <body>{body}</body>
        </time> }}
      </location>
    </text>
  </forecast>
</weatherdata>
val obs = for {
  time  <- xml \\ "time"
  tpe   = time \ "@type" if tpe.text == "obsforecast"
  body  = time \ "body"
  title = time \ "title"
} yield title.text -> body.text

// (Friday and Saturday,<strong>Telemark:</strong> Våte veibaner og 
//    synkende temperatur kan stedvis gi glattere veier fra natt til julaften.)

Unfiltered

  • toolkit for å håndtere http request
  • bittelite (core er 1376 CLOC)
  • pattern matching, funksjoner
  • veldig nært HTTP
GET / HTTP/1.1
User-Agent: curl/7.19.7
Host: localhost:8080
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 357
Server: Jetty(7.6.0.v20120127)
class Example extends Plan {
  def intent = {
    case GET(Path("/")) => OK ~> Html5(<html>...</html>)    
  }
}
  • http api
  • synkront | async
  • servlets, jetty, netty...
  • websockets
  • uploads
  • oauth, oauth2
  • directives (høynivå api)

design

  • Intents
  • Plans
  • Request matchers
  • Response functions
  • Servers

Intent

// sync
type Intent[-A,-B] = PartialFunction[HttpRequest[A], ResponseFunction[B]]

//async
type Intent[-A,-B] =
  PartialFunction[HttpRequest[A] with Responder[B], Any]

trait Responder[+R] {
  def respond(rf: unfiltered.response.ResponseFunction[R])
}

Plan

trait Plan extends Filter {
  def intent:Intent[HttpServletRequest, HttpServletResponse]

  def doFilter(request: ServletRequest,
               response: ServletResponse,
               chain: FilterChain) = ...
}

request matchers

  • støtter det vanligste
  • extractors (pattern matching) / lett å lage egne
object Path {
  def unapply[T](req: HttpRequest[T]) = Some(req.uri.split('?')(0))
}
object Seg {
  def unapply(path: String): Option[List[String]] = 
    path.split("/").toList match {
      case "" :: rest => Some(rest) // skip a leading slash
      case all => Some(all)
    }
}
GET /item/5 HTTP/1.1
User-Agent: curl/7.19.7
Host: localhost:8080
Accept: */*
def intent = {
  case Path(Seg(List("item", number))) => // number == "5"
    Ok ~> Html5(...)

  case Path(Seg(List("you", can, "have", many))) =>
    Ok ~> Html5(...)
}
GET /item?filter=foo&filter=bar&monkey=donkey HTTP/1.1
User-Agent: curl/7.19.7
Host: localhost:8080
Accept: */*
`
def intent = {
  case Path("/item") & QueryParams(q) =>
    val filter = q("filter") // Seq("foo", "bar")
    val monkey = q("monkey") // Seq("donkey")
    val nope   = q("nope")   // Seq()
    Ok ~> Html5(...)    
}

response functions

def intent = {
  case Path("/") =>
    ServiceUnavailable ~> TextXmlContent ~> 
      ResponseString("<error>not available</error>)
}
HTTP/1.1 503 Service Unavailable
Content-Type: text/xml; charset=utf-8
Content-Length: 28
Server: Jetty(7.6.0.v20120127)

<error>not available</error>

servers

  • jetty
  • netty
unfiltered.jetty.Http.local(8080).filter(new Example).run()

sql

slick

  • fra typesafe
  • "tabeller er scala collections"
object Coffees extends Table[(String, Int, Double)]("COFFEES") {
  def name  = column[String]("COF_NAME", O.PrimaryKey)
  def supID = column[Int]("SUP_ID")
  def price = column[Double]("PRICE")
  def * = name ~ supID ~ price
}

Coffees.insertAll(
  ("Colombian",         101, 7.99),
  ("Colombian_Decaf",   101, 8.99),
  ("French_Roast_Decaf", 49, 9.99))

val q = for {
  c <- Coffees if c.supID === 101
} yield (c.name, c.price)
case class Coffee(name: String, supID: Int, price: Double)

implicit val getCoffeeResult = GetResult(r => Coffee(r.<<, r.<<, r.<<))

Database.forURL("...") withSession {
  Seq(
    Coffee("Colombian", 101, 7.99),
    Coffee("Colombian_Decaf", 101, 8.99),
    Coffee("French_Roast_Decaf", 49, 9.99)
  ).foreach(c => sqlu"""
      insert into coffees values (${c.name}, ${c.supID}, ${c.price})
    """).execute)

  val sup = 101
  val q = sql"select * from coffees where sup_id = $sup".as[Coffee]
  //      A bind variable to prevent SQL injection ^
  q.foreach(println)
}

sqlτyped

val q = sql("select name, age from person")
val r:List[Int] = q() map (_ get "age") // List(36, 14)

q() map (_ get "salary")
// error: No field String("salary") in record
val q = sql("select name, age from person where age > ?")

q("30") map (_ get "name")
// error: type mismatch;
//   found   : String("30")
//   required: Int

q(30) map (_ get "name") // ok

json

json4s

  • wrapper for mange json bibliotek (lift, jackson etc.)
  • felles ast
  • veldig enkel og grei
sealed abstract class JValue
case object JNothing extends JValue // 'zero' for JValue
case object JNull extends JValue
case class JString(s: String) extends JValue
case class JDouble(num: Double) extends JValue
case class JDecimal(num: BigDecimal) extends JValue
case class JInt(num: BigInt) extends JValue
case class JBool(value: Boolean) extends JValue
case class JObject(obj: List[JField]) extends JValue
case class JArray(arr: List[JValue]) extends JValue

type JField = (String, JValue)
import org.json4s._
import org.json4s.native.JsonMethods._

parse(""" { "numbers" : [1, 2, 3, 4] } """)

// JObject(List((numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4))))))

argonaut

  • basert på scalaz
  • codecs for mapping mellom scala/json
  • zippers for navigasjon/oppdatering av json
  • lignende api som json4s i bunn - men masse mer
import argonaut._, Argonaut._

case class Person(name: String, age: Int, things: List[String])

implicit def PersonCodecJson =
  casecodec3(Person.apply, Person.unapply)("name", "age", "things")

val person = Person("Bam Bam", 2, List("club"))

val json: Json = person.asJson

val prettyprinted: String = json.spaces2

val parsed: Option[Person] = prettyprinted.decodeOption[Person]

rapture

  • samling av biblioteker / utilities
  • e.g io, crypt, json, xml ++
{
  "groups": [
    {
      "groupName": "The Beatles",
      "members": [
        { "name": "John Lennon", "born": 1940 },
        { "name": "Paul McCartney", "born": 1942 },
        { "name": "Ringo Starr", "born": 1940 },
        { "name": "George Harrison", "born": 1943 }
      ]
     }
  ]
}
import rapture._
import core._, io._, net._, uri._, json._, codec._

// Read a file into a string
import encodings.`UTF-8`
val src = uri"http://rapture.io/sample.json".slurp[Char]

// Parse it as Json
import jsonBackends.jackson._
val json = Json.parse(src)

// Auto-extract a `Group` into a case class structure
case class Member(name: String, born: Int)
case class Group(groupName: String, members: Set[Member])
json.groups(0).as[Group]

Dispatch

import dispatch._, Defaults._

val svc = url("http://api.hostip.info/country.php")
val country = Http(svc OK as.String)

// non blocking foreach
for (c <- country)
  println(c)

// blocking!
val c = country()

// map, flatMap etc. (non-blocking)
val length = for (c <- country) yield c.length

request

// legg til form-parameter
def myPostWithParams = myPost.addParameter("key", "value")

// POST + legg til form-parameter
def myPostWithParams = myRequest << Map("key" -> "value")

// POST body
def myPostWithBody = myRequest << """{"key": "value"}"""

// legg til query param
def myRequestWithParams = myRequest.addQueryParameter("key", "value")

// legg til query param
def myRequestWithParams = myRequest <<? Map("key" -> "value")

// PUT java.io.File
def myPut = myRequest <<< myFile

response

Http(request OK as.String) //200 range, eller Failure

Http(request > as.String) //aksepterer alle

def f(r:Response) = ...
Http(request > f) //bruk min egen funksjon

Futures

Future[A] er en representasjon av en verdi A som kan bli tilgjengelig på et eller annet tidspunkt

Future kan være i 2 tilstander

  • uferdig
  • ferdig

En ferdig future er enten 'successful' eller 'failed'

Mye brukt for asynkron / concurrent / parallell kode

  • non-blocking nettverks-kode
  • map/reduce
  • ...

Alle computations kjøres på en ExecutionContext

import scala.concurrent._
import ExecutionContext.Implicits.global

val work:Future[Result] = Future {
  ... // do heavy work returning Result
}
import scala.util.{Success, Failure}

work.onComplete{
  case Success(result) => ..
  case Failure(ex)     => ...
}

work.onSuccess{ case result => ... }
work.onFailure{ case ex:Exception => ... }

map, flatMap, filter, forEach og mye annet

val fa:Future[Int] = ...
def fb(a:A):Future[String] = ...

val x:Future[String] = for {
  a <- fa
  b <- fb(a) if a > 10
} yield b

error recovery / exception handling

val ok =  Future{ 2 / 0 } recover { case x:ArithmeticException => 0 }

for(r <- ok)
  println(r)
// 0

zipping

val fa:Future[A] = ...
val fb:Future[B] = ...

val fab:Future[(A, B)] = fa zip fb

sequencing

val listOfFuture:List[Future[Int]] = ...

val futureOfList:Future[List[Int]] = Future.sequence(listOfFuture)

promises

val p = Promise[Int]
val f = p.future
f.foreach(i => println(i))
// på et eller annet tidspunkt
p.success(5)

blocking await

import scala.concurrent.duration._

val f:Future[Int] = ...
val i:Int = Await.result(f, 5.seconds)

akka

Akka is a toolkit and runtime for building highly concurrent, distributed, and fault tolerant event-driven applications on the JVM.

  • actors
  • remoting
  • supervision

Actors er concurrent prosesser som kommuniserer via meldinger

import akka.actors._

case class Message(msg:String)

val system = ActorSystem()
// ActorRef er en nettverkstransparent referanse til en actor
val client:ActorRef = system.actorOf(Props[Client])

// ! sender melding til en actor
client ! Message("hi")

class Client extends Actor {
  // implementer all message handling i receive m/pattern matching
  def receive = {
    case Message(msg) => println(s"got $msg")
  }
}

50 million msg/sec on a single machine. Small memory footprint; ~2.5 million actors per GB of heap.

// instansier
system.actorOf(Props[SomeActor], name = "some-actor")

system.actorOf(Props(new SomeActor))

// lookup
system.actorSelection("/user/some-actor")

supervision

class Supervisor extends Actor {
  override val supervisorStrategy =
    OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
      case _: ArithmeticException      ⇒ Resume
      case _: NullPointerException     ⇒ Restart
      case _: Exception                ⇒ Escalate
    }

  val worker = context.actorOf(Props[Worker])

  def receive = {
    case n: Int => worker forward n
  }
}