Podstawy Języka Scala

2003 - A drunken Martin Odersky sees a Reese’s Peanut Butter Cup ad featuring somebody’s peanut butter getting on somebody else’s chocolate and has an idea. He creates Scala, a language that unifies constructs from both object oriented and functional languages. This pisses off both groups and each promptly declares jihad.

James Iry. A Brief, Incomplete, and Mostly Wrong History of Programming Languages.

Narzędzia

Konsola:
  • sbt console (z pakietu Scala Build Tools)
  • scala
IDE:
Inne:

Cechy

../_images/bosch.jpg

Hieronymus Bosch A visual guide to the Scala language oil on oak panels, 1490-1510. The left panel shows the functional features, the main one describes the type system, and the right the object oriented parts. (http://classicprogrammerpaintings.tumblr.com/)

  • Programowanie obiektowe (czyste)
  • Silny system typów, wywodzenie typów
  • Pattern matching
  • Traits
  • Funkcje wyższego rzędu
  • Currying
  • Domknięcia
  • Rekurencja ogonowa
  • Współpraca z Javą (kompilowany do bytecode-u)
  • Zorientowany na efektywną współbieżność i rozproszenie

Typy

Podstawowe typy:
  • Boolean
  • Char
  • Int, Long, Short, Byte
  • Double, Float
  • String
  • Symbol

Aliasy:

type Identifier = Int
type Callback = Int => Int
type Vector3 = (Int, Int, Int)

Operatory

Wybrane operatory:
  • Arytmetyczne: + - / % *
  • Relacyjne i logiczne: < > <= >= || && != ! ==
  • Bitowe: & | ^ ~ >> << >>> (unsigned right shift)
  • Przypisywanie: = *= += -= /= ...

Pełna lista: Operators


Etykiety i zmienne

Definicja etykiety:

val x = 5
val s = "spaceship"
val y : Int = 0

x = x + 1
<console>:4: error: reassignment to val

Definicja zmiennej:

var x = 5
var s = "spaceship"

x = x + 1
s = "whoosh"

Preferowane używanie etykiet. Dlaczego?

Wartość domyślna:

var x: Int = _
var y: Boolean = _
var s: String = _
var z: Any = _

Obiekty

Wszystko jest obiektem.

5.toString()

Operatory są związane z klasą obiektu

val x, y = 2

x + y
x.+(y)

Uproszczenie wywołań metod:

Uproszczenie wywołań metod

Usunięcie nawiasów

val x = 2
x.toString()
x.toString
x toString

Styl: arność 0, czyste funkcyjne wywołania (bez efektów ubocznych):

Zapis infiksowy

val s = "something something spaceship"
s.split(" ")
s split(" ")
s split " "

Styl: arność 1, czyste funkcyjne wywołanie; “obowiązkowo,” jeśli argument jest funkcją.

Scala Style Guide


Unit

val x = ()

Null:

val x = null

Bloki kodu

Definicja bloku kodu:

{ /*...*/ }

Blok zwraca ostatnią wartość:

{ 2 + 2 }
{ val x = 2; x }
{ val x = 2; val y = 2; x + y }
{ println("x") }

Zakres zmiennych w blokach:

val x = { 2 + 2 }       // x → 4
val y = { x + 2 }       // y → 6

{ val x = 8; x }        // x → 8
x                       // through a quirk of the console toplevel will allow it
                        // but should explode

{ x; val x = 8 }        // explosion

Metody

Definicja metody (na razie w oderwaniu od klasy/obiektu):

def increment(x: Int): Int = {
    return x + 1
}

def increment(x: Int): Int = {
    x + 1
}

def increment(x: Int) = {
    x + 1
}

def increment(x: Int) {x + 1}       // → unit

def increment(x: Int) = x + 1

Unit jako argument:

def f() = 5
def g() = ()

Parametr z wartością domyślną:

def add(x: Int, y: Int = 1) = { x + y }

add(1, 1)
add(1)

Zagnieżdżanie metod:

def outer(x: Int) = {
    def inner(y: Int) = {
        y + x
    }
    inner(x)
}

Funkcje

Definicja funkcji:

(x: Int) => x + 1
() => println("Hello World!")

val f1 = (x: Int) => x + 1
f1(5)

val f2 = () => println("Hello World!")
f2()

Funkcje są domyślnie anonimowe.

Funkcje są obiektami:

val f1 = (x: Int) => x + 1

f1.apply(1)
f1.toString()

Konwersja metody na funkcję:

def increment(x: Int) = {x + 1}
val f = increment _             // η-konwersja aka eta-konwersja aka magic

val fs = "s".toString _
fs()

Konwersja następuje samoczynnie:

def increment(x: Int) = {x + 1}

increment.apply(1)
increment.toString()

val x = increment
x(1)
x.apply(1)
x.toString()

Z funkcjami można robić wiele innych ciekawych rzeczy, które omówimy na osobnych zajęciach.

Różnice między funkcjami i metodami: Jim McBeath. Scala Functions vs Methods.

Klasy

Definicja klasy:

class Enterprise() {}

Domyślny konstruktor tworzący pola, metody, wykonujący kod:

class Enterprise() {                        // primary constructor
    val version = "NCC-1701"
    val captain = "Kirk, J. T."

    val maxShields = 100
    var shieldDmg = 0

    def shieldLvl() = {maxShields - shieldDmg}

    println("Split all the infinitives!")
}

val enterprise = new Enterprise()

Widoczność

  • domyślna globalna widoczność
  • private - private (do poziomu klasy)
  • private[package] - private do poziomu pakietu
  • private[this] - private do poziomu instancji
  • protected - protected (do poziomu klasy i podklas)
  • protected[package] - protected do poziomu pakietu
  • protected[this] - protected do poziomu instancji

private vs private[this]

Dostęp w dowolnej instancji klasy:

class Spaceship {
  private val name = "USS Enterprise"

  def compare (e: Spaceship): Boolean = e.name== this.name
}

Dostęp tylko we własnej instancji klasy:

class Spaceship {
  private[this] val name = "USS Enterprise"

  def compare (e: Spaceship): Boolean = e.name == this.name
}

Argumenty do konstruktora

class Enterprise(ver: String, capt: String) {
    val version = ver
    val captain = capt

    val maxShields = 100
    var shieldDmg = 0

    def shieldLvl() = {maxShields - shieldDmg}
}

val enterprise = new Enterprise("NCC-1701", "Kirk, J. T.")
enterprise.shieldDmg = 10
enterprise.shieldLvl()
enterprise.shieldLvl

Metody bez argumentów:

class Enterprise(ver: String, capt: String) {
    def version = ver
    def captain = capt

    val maxShields = 100
    var shieldDmg = 0

    def shieldLvl = {maxShields - shieldDmg}
}

val enterprise = new Enterprise("NCC-1701", "Kirk, J. T.")
enterprise.shieldDmg = 10
enterprise.shieldLvl

Konstruktory dodatkowe:

class Enterprise(ver: String, capt: String) {
    def this() = {
        this("NCC-1701", "Kirk, J. T.")        // First expression in method
    }
}

Dziedziczenie i przeciążanie:

abstract class Constitution() { // extends AnyRef
    def shipClass = "Constitution"
    def shipName = {}
}

class Enterprise(ver: String, capt: String) extends Constitution {
    override def shipName = "Enterprise"
}

Przeciążanie zmiennych:

abstract class Constitution() { // extends AnyRef
    var shipClass = "Constitution"
    def shipName = {}
}

class Enterprise(ver: String, capt: String) extends Constitution {
    override var shipClass = "Enterprise"
}

<console>:9: error: overriding variable shipClass in class Constitution of type java.lang.String;
variable shipClass cannot override a mutable variable
           override var shipClass = "Enterprise"
                        ^

Wielokrotne dziedziczenie (traits):

trait WarpSpeedCapability {
    var currentSpeed = 0
    def setCruisingSpeed = { currentSpeed = 2 }
    def setMaximumWarp = { currentSpeed = 9 }
}

class Enterprise(ver: String, capt: String) extends Constitution with WarpSpeedCapability {
    override def shipName = "Enterprise"
}

Rozwiązywanie konfliktów:

trait AuxiliaryCraft {
    val awayTeamType = "shuttlecraft"
    def sendAwayTeam = {
        // ...
    }
}

trait Transporter {
    val awayTeamType = "transporter"
    def sendAwayTeam = {
        // ...
    }
}

class Enterprise(ver: String, capt: String) extends AuxiliaryCraft with Transporter {
    override def awayTeamType = "transporter or shuttlecraft"
    override def sendAwayTeam = {
        // ...
    }
}

Nierozwiązywalny konflikt:

trait AuxiliaryCraft {
    var isAwayTeamSent = false
    def sendAwayTeam = {
        // ...
    }
}

trait Transporter {
    var isAwayTeamSent = false
    def sendAwayTeam = {
        // ...
    }
}

class Enterprise(ver: String, capt: String, maxShld: Int) extends AuxiliaryCraft with Transporter {
    override def sendAwayTeam = {
        // ...
    }
}

<console>:9: error: class Enterprise inherits conflicting members:
  variable isAwayTeamSent in class AuxiliaryCraft$class of type Boolean  and
  variable isAwayTeamSent in class Transporter$class of type Boolean
(Note: this can be resolved by declaring an override in class Enterprise.);
 other members with override errors are: isAwayTeamSent_=

Przeciążanie operatorów:

class C {
    def < (that: Any): Boolean = {
        // ...
    }
}

Obiekty statyczne:

Definicja obiektu statycznego (singleton):

object universe {
    val age: Long = 45L
    def getBlackHoles() = {
        // ...
    }
}

Scala nie pozwala na tworzenie statycznych elementów poza obiektem statycznym.

Obiekt statyczny jest tworzony przy pierwszym użyciu.

Companion object:

class Shipyard {
  def makeShip = ???
}

object Shipyard {
  def makeShipyard = ???
  def getAllShipyards = ???
}

“Funkcja main”:

object Something extends App {
   // Code goes here.
}

Typy generyczne

class RefCell[T] {
    private var obj: T = _
    def get: T = { obj }
    def set(value: T) = { obj = value }
}

val s = new RefCell[String]
s set "HelloWorld"
s get

val x = new RefCell[Int]
x set 5
x get

Stack[T] is only a subtype of Stack[S] if and only if S = T - wariancja

Kolekcje

Niezmienne:

  • krotki
  • listy
  • zbiory
  • mapy

Zmienne:

  • tabele
  • zbiory
  • listy
  • kolejki
  • stosy

Krotki

val pair = ("alpha", "beta")
val triple = ("alpha", "beta", "gamma")

val x = pair _1
val y = pair _2

val (x,y) = pair

val riap = pair swap

val pair = "alpha" -> "beta"

Listy:

val c = List(1, 2, 3, 4)
val c = 1 :: 2 :: 3 :: 4 :: Nil
val c = List(1, 2) ::: List(3, 4)

c(0)

c head
c tail
c last
c isEmpty
c length

c reverse
c iterator

c slice (0, 2)

Zbiory:

val s = Set(1, 2, 3, 4, 4)

s(1)
s contains 1

s - 1
s + 5
s + (6,7)
s empty

val a = Set(1, 2, 3, 4)
val b = Set(3, 4, 5, 6)

a union b       // union
a | b           // union
a ++ b          // union

a subsetOf b

a intersect b   // intersection
a & b           // intersection

a diff b        // difference
a &~ b          // difference
a -- b          // difference

Zakresy:

val r = Range(0, 10)

r(0)

val r = 1.to(10)
val r = 1.until(10)

val r = 1 to 10
val r = 1 until 10

r reverse
r iterator

Mapy/Tabele:

val m = Map("a" -> 1, "b" -> 2)
val m = Map(("a", 1), ("b", 2))

m("a")
m get "a"

m get "x"
m getOrElse ("x", -1)

m contains "a"

m + ("c" -> 3)
m + ("c" -> 3, "d" -> 4)

m ++ Map("c" -> 3, "d" -> 4)

m - "a"
m - ("a", "b")

m -- Map("c" -> 3, "d" -> 4)

m keys
m values

Zmienne

Tablice:

val arr = new Array[String](3)

arr(0) = "ala"
arr(1) = "ma"
arr(2) = "kota"

val arr = Array("ala", "ma", "kota")

val cube = ofDim[Int](3,3)
cube(0,0) = 1

Zmienne kolekcje:

import scala.collection.mutable.LinkedList
import scala.collection.mutable.Set
import scala.collection.mutable.Map
import scala.collection.mutable.StringBuilder

val s : StringBuilder = new StringBuilder()

s append "a"
s ++= "b"

println(s.mkString)

Kolekcje generyczne

val ls : List[String] = "a" :: "b" :: "c" :: Nil

Kowariancja: jeśli A jest podtypem B to T[A] jest podtypem T[B].

class Animal {}
class Dog extends Animal {}

val animals : List[Animal] = new Dog() :: new Dog() :: Nil

Wyrażenie warunkowe

if (x > 0)
    z = x
else
    z = -x

Pętle while:

while (x > 0)
    x -= 1

while (x > 0) {
    x -= 1
    println(x)
}

Pętle for:

l = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
for (i <- l)
    println(i)

for (i <- 1 to 10)
    println(i)

for (i <- 1 until 10)
    println(i)

for (i <- 1 to 10; j <- 1 to 10)
    println(i,j)

Pętle z filtrem:

for (i <- 1 until 10 if i % 2 == 0)
    println(i)

for (i <- 1 until 10 if i % 2 == 0; if i % 3 == 0)
    println(i)

Leniwe wykonanie pęlti:

val result = for (i <- 1 to 10 if i % 2 == 0) yield i
result(3)

Pętle są tłumaczone na wywołania metod, np.:

for (x <- lx; y <- ly; z <-lz)
    println(x, y, z)
lx.foreach(
    x => ly.foreach(
        y => lz.foreach(
            z => {
                println(x, y, z)
            }
        )
    )
)

Wszystko jest wyrażeniem (zwraca wartość).

val z = if (x > 0) x else -x    // z : Int

val u = for (i <- 1 to 10) i    // u : Unit

Pattern matching

Porównywanie wartości:

def f(x: Any) = {
    x match {
        case "1" => 1
        case "a" | "A" => 1
        case "b" | "B" => 2
        case _ => -1
    }
}

Porównywanie typów:

def f(x: Any) = {
    x match {
        case i: Int => i
        case s: String => {
            val lcase = s toLowerCase
            val ch = (lcase toCharArray) apply 0
            ch - 96
        }
        case _ => -1
    }
}

Porównywanie kolekcji:

def length (l: Any) = {
    l match {
         case Nil => "empty"
         case _ :: Nil => "one"
         case _ :: _ :: Nil => "two"
         case _ :: _ :: tail => "more than two"
         case _ => "probably not a list"
    }
}

Zaawansowane porównywanie (warunki):

def grade(avg: Double) : String = {
  avg match {
    case x if x < 3.0 => "ndst"
    case x if x < 3.5 => "dst"
    case x if x < 4.0 => "dst+"
    case x if x < 4.5 => "db"
    case x if x < 5.0 => "db+"
    case x if x == 5.0 => "bdb"
    case _ => "wtf"
  }
}

Zaawansowane porównywanie (case classes):

abstract class BTreeNode
case class Leaf(v:Int) extends BTreeNode
case class ConcreteNode(l: Leaf, r: Leaf, v:Int) extends BTreeNode

def getValue(node: BTreeNode) = {
    node match {
         case ConcreteNode(l, r, v) => v
         case Leaf(v) => v
    }
}

Pakiety

package pl.edu.put.Test

package pl {
    package edu {
        package put {
            package Test {
                // ...
            }
        }
    }
}

import scala.math._

Placeholder

val pair = (2, 3)
val (x, _) = pair
1::2::3::Nil map (e => -e)
1::2::3::Nil map (-_)

Ankieta?

Ankieta

Zadanie [Scala0]

Napisz “bibliotekę” zamieniającą wskazaną liczbę całkowitą na zapis w systemie
  • rzymskim (14 = XIV),
  • greckim alfabetycznym (14 = ΙΔʹ).

Umieść zadanie w repozytorium DSG.