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:
- Scala IDE (Eclipse)
- IntelliJ IDEA
- Inne:
Cechy¶
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:
= *= += -= /=
...
- Arytmetyczne:
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ą.
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 pakietuprivate[this]
- private do poziomu instancjiprotected
- protected (do poziomu klasy i podklas)protected[package]
- protected do poziomu pakietuprotected[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
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._
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.