po* - scalasroka/archiwalne/2012po_gw/scala1.pdf · przygotował: jacek sroka 6 jak uruchamiać -...
TRANSCRIPT
Przygotował: Jacek Sroka
1
PO* - Scala
Przygotował: Jacek Sroka
2
Czemu Scala?
● Język wzorowany na Javie i Haskelu– nowoczesny obiektowy język programowania
– jednocześnie język funkcyjny
● Programy uruchamiają się na JVM– można swobodnie odwoływać się do istniejącego kodu i bibliotek Javy
● Silnie typowany– ciekawy system typów
● Zwięzła składnia i silniejsza abstrakcja = prostsze programy = mniej błędów
● Zaprojektowany do wyrażania powszechnych wzorców projektowych● Robi się trendy?
Przygotował: Jacek Sroka
3
Pierwszy przykład
object HelloWorld { def main(args: Array[String]) { println("Hello, world!") }}
> scalac HelloWorld.scala
> scala -classpath . HelloWorldHello, world!
● main() jest procedurą
● object deklaruje Singleton (klasa i egzemplarz)
● Nie ma potrzeby dla składowych statycznych
Przygotował: Jacek Sroka
4
Jak uruchamiać - shell
> scala This is a Scala shell. Type in expressions to have them evaluated. Type :help for more information.
scala> object HelloWorld { | def main(args: Array[String]) { | println("Hello, world!") | } | } defined module HelloWorld
scala> HelloWorld.main(null) Hello, world!
scala>:q
Przygotował: Jacek Sroka
5
Jak uruchamiać - skrypt
#!/bin/sh exec scala "$0" "$@" !# object HelloWorld { def main(args: Array[String]) { println("Hello, world! " + args.toList) } } HelloWorld.main(args)
> ./script.sh
Przygotował: Jacek Sroka
6
Jak uruchamiać - kompilacja
● Kompilacja do bytecodu Javy > scalac HelloWorld.scala
lub > scalac -d classes HelloWorld.scala
● Uruchamianie > scala HelloWorld
lub > scala -classpath classes HelloWorld
– argumentem powinien być obiekt najwyższego poziomu
– jeżeli występuje klauzula extends Application to wykonywane są wszystkie polecenia
object HelloWorld2 extends Application { println("Hello, world!") }– wpp wykonywana jest metoda main
Przygotował: Jacek Sroka
7
Wygodniejsze importy
import java.util.{Date, Locale}import java.text.DateFormatimport java.text.DateFormat._
object FrenchDate { def main(args: Array[String]) { val now = new Date val df = getDateInstance(LONG,Locale.FRANCE) println(df format now) }}
● Zwięzłe importowanie wielu klas z pakietu
● ”_” zamiast ”*”
● Trochę inne niż w javie importowanie składowych statycznych
● Funkcyjna składnia wywoływania metod i przekazywania parametrów
● Domyślnie importowane jest całe java.lang
Przygotował: Jacek Sroka
8
Wszystko jest obiektem
Włącznie z liczbami i funkcjami.
1 + 2 * 3 / x
(1).+(((2).*(3))./(x))
1.+(2)
(1).+(2) //żeby lexer nie wybrał najdłuższego dopasowanie dla identyfikatorów
Przygotował: Jacek Sroka
9
Funkcje też są obiektami
object Timer { def oncePerSecond(callback: () => Unit) { while (true) { callback(); Thread sleep 1000 } } def timeFlies() { println("time flies like an arrow...") } def main(args: Array[String]) { oncePerSecond(timeFlies) }}
object TimerAnonymous { def oncePerSecond(callback: () => Unit) { while (true) { callback(); Thread sleep 1000 } }
def main(args: Array[String]) { OncePerSecond(() => println("time flies like an arrow...")) }}
● Możemy przekazywać funkcje jako parametry
● ”() => Unit” to typ wszystkich funkcji bez argumentów i bez wartości zwrotnej
● Do wypisywanie użyliśmy predefiniowanej metody println() zamiast System.out
● Często wygodnie jest używać funkcji anonimowych
Przygotował: Jacek Sroka
10
Klasy
class Complex(real: Double, imag: Double) { def re() = real def im() = imag}
object ComplexNumbers { def main(args: Array[String]) { val c = new Complex(1.2, 3.4) println("imaginary part: " + c.im()) }}
class Complex(real: Double, imag: Double) { def re = real def im = imag}
class Complex(real: Double, imag: Double) { def re = real def im = imag override def toString() = "" + re + (if (im < 0) "" else "+") + im + "i"}
● Klasy w Scali mają parametrynew Complex(1.5, 2.3)
● Typy zwrotne metod zazwyczaj mogą być wywnioskowane przez kompilator
● Wywołując metody z zerową liczbą argumentów trzeba wstawiać puste nawiasy
● Metody można zdefiniować jako bezparametrowe
● Domyślną nadklasą jest scala.AnyRef
● Przedefiniowując metody trzeba to jawnie wskazać
Przygotował: Jacek Sroka
11
Warianty i dopasowywanie
abstract class Treecase class Sum(l: Tree, r: Tree) extends Treecase class Var(n: String) extends Treecase class Const(v: Int) extends Tree
//taką funkcją możemy//reprezentować środowisko//dla "x" zwraca 5 wpp wyjątek{ case "x" => 5 }
//alias dla typutype Environment = String => Int
● Wsparcie do reprezentacji drzew, np. XML
● Przykład: kalkulator z sumowaniem, stałymi całkowitymi i zmiennymi
● Const(5) zamiast new Const(5)
● Automatycznie są gettery np. dla c egzemplarza Const mamy c.v
● equals i hashCode działają na strukturze
● toString wypisuje "literał” np. Sum(Var(x,Const(1)))
● Można dopasowywać do wzorca
Przygotował: Jacek Sroka
12
Warianty i dopasowywanie c.d.
def eval(t: Tree, env: Environment): Int = t match { case Sum(l, r) => eval(l, env) + eval(r, env) case Var(n) => env(n) case Const(v) => v} //jak nic się nie dopasuje to wyjątek
def derive(t: Tree, v: String): Tree = t match { case Sum(l, r) => Sum(derive(l, v), derive(r, v)) case Var(n) if (v == n) => Const(1) //dopasowanie warunkowe case _ => Const(0) //guard}
def main(args: Array[String]) { val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) val env: Environment = { case "x" => 5 case "y" => 7 } println("Expression: " + exp) //Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) println("Evaluation with =5, y=7: " + eval(exp, env)) //Evaluation with x=5, y=7: 24 println("Derivative relative to x: " + derive(exp, "x")) //...to x: Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) println("Derivative relative to y: " + derive(exp, "y")) //..to y: Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1)))}
Przygotował: Jacek Sroka
13
Dopasowywanie wzorców vs polimorfizm
● Przy pomocy dopasowywania wzorców bardzo łatwo dodać nową metodę dla całej hierarchii. Dużo roboty jak nowy węzeł.
● Za pomocą polimorfizmu łatwo dodać nowy rodzaj węzła. Dużo roboty jak nowa metoda.
Przygotował: Jacek Sroka
14
Traits
trait Ord { def < (that: Any): Boolean def <=(that: Any): Boolean = (this < that) || (this == that) def > (that: Any): Boolean = !(this <= that) def >=(that: Any): Boolean = !(this < that)}
● Wzbogacanie klasy (interfejs z implementacją)
● Any to bardziej ogólny Object
Przygotował: Jacek Sroka
15
Traits c.d.
class Date(y: Int, m: Int, d: Int) extends Ord { def year = y; def month = m def day = d override def toString(): String = year + "" + month + "" + day
override def equals(that: Any): Boolean = that.isInstanceOf[Date] && { val o = that.asInstanceOf[Date] o.day == day && o.month == month && o.year == year }
def <(that: Any): Boolean = { if (!that.isInstanceOf[Date]) error("cannot compare " + that + " and a Date") val o = that.asInstanceOf[Date] (year < o.year) || (year == o.year && (month < o.month || (month == o.month && day < o.day))) }}
Przygotował: Jacek Sroka
16
Generyki
class Reference[T] { private var contents: T = _ def set(value: T) { contents = value } def get: T = contents}
object IntegerReference { def main(args: Array[String]) { val cell = new Reference[Int] cell.set(13) println("Reference contains the half of " + (cell.get * 2)) }}
● Parametryzowanie kodu typami
● Piszemy ogólny kod i nie musimy bez przerwy rzutować
● "_” to wartość domyślna dla zmiennej
Przygotował: Jacek Sroka
17
Quicksort
def sort(a: Array[Int]) { def swap(i: Int, j: Int) { val t = a(i); a(i) = a(j); a(j) = t }
def sort1(l: Int, r: Int) { val pivot = a((l + r) / 2) var i = l var j = r while (i <= j) { while (a(i) < pivot) i += 1 while (a(j) > pivot) j -= 1 if (i <= j) { swap(i, j) i += 1 j -= 1 } } if (l < j) sort1(l, j) if (j < r) sort1(i, r) }
if (a.length > 0) sort1(0, a.length - 1)}
● deklaracje zaczynają się zarezerwowanym słowem:def, var, val
● typ podajemy po symbolu ”i :”
● jak się da to kompilator domyśli się typu i nie trzeba go podawać
● Array[Int] zamiast Int[]
● a(i) zamiast a[i]
● zagnieżdżone funkcje mają dostęp do parametrów i zmiennych
Przygotował: Jacek Sroka
18
Quicksort c.d.
object sort {
def sort(a: Array[Int]) { //... }
def println(ar: Array[Int]) { def print1 = { def iter(i: Int): String = ar(i) + (if (i < ar.length-1) "," + iter(i+1) else "") if (ar.length == 0) "" else iter(0) } Console.println("[" + print1 + "]") }
def main(args: Array[String]) { val ar = Array(6, 2, 8, 5, 1) println(ar) sort(ar) println(ar) }
}
Przygotował: Jacek Sroka
19
Quicksort – wersja funkcyjna
def sort(a: Array[Int]): Array[Int] = { if (a.length < 2) a else { val pivot = a(a.length / 2) Array.concat(sort(a.filter(x => x < pivot)), a.filter(x => x == pivot), sort(a.filter(x => x > pivot))) } }
● Obiekty Array[T] mają metodę (pochodzącą z klasy Seq[T])def filter(p: T -> Boolean): Array[T]
● Można użyć składni a.filter(_ < pivot) a nawet (partially applied function) a.filter(pivot >)
– Inny sposób zapisania tej funkcji to x => pivot > x
● Operatory przekładają się na wywołania metod: a filter (pivot >)
● Dla podstawowych operatorów kompilator powinien generować dobry kod
● Ponieważ ciało funkcji jest wyrażeniem można pominąć {}
Przygotował: Jacek Sroka
20
Dyskusja
● zapotrzebowanie na pamięć● wielowątkowość
Przygotował: Jacek Sroka
21
Funkcje wyższego rzędu
def While (p: => Boolean) (s: => Unit) {
if (p) {s; While(p)(s)}
}
● tak naprawdę jest specjalna konstrukcja ● oba parametry są bezparametrowe● Unit to tak jakby void
● jak nie zwracamy żadnego wyniku to tak jak byśmy zwracali wartość unit ()
● nie trzeba podawać return, domyślnie wynikiem funkcji jest wynik ostatniego wyrażenia
Przygotował: Jacek Sroka
22
Wyrażenia
scala> 87+145res0: Int = 232
scala> 5+2*3res1: Int = 11
scala> "hello"+" world!"res2: java.lang.String = hello world!
scala> def pi = 3.141592pi: Double
scala> def radius = 10radius: Int
scala> 2*pi*radiusres7: Double = 62.83184
Przygotował: Jacek Sroka
23
Kiedy jest wyliczana wartość?
● wyrażenie w def x = e jest wyliczane dopiero jak jest potrzebne
● wyrażenie w val x = e jest wyliczane w chwili deklaracji
● zazwyczaj wyrażenia wyliczane są od lewej (redukcja)
(2*pi)*radius(2*3.141592)*radius6.183184*radius6.183184*1061.83184
Przygotował: Jacek Sroka
24
Proste funkcje
scala> def square(x: Double) = x*xsquare: (Double)Double
scala> square(2)res11: Double = 4.0
scala> square(5+3)res12: Double = 64.0
scala> square(square(4))res13: Double = 256.0
scala> def sumOfSquares(x: Double, y: Double) = square(x)+square(y)sumOfSquares: (Double,Double)Double
scala> sumOfSquares(3,2+2)res14: Double = 25.0
Przygotował: Jacek Sroka
25
Wyliczanie wartość funkcji
call-by-value
sumOfSquares(3, 2+2)sumOfSquares(3, 4)square(3)+square(4)3*3+square(4)9+square(4)9+4*49+1625
call-by-name
sumOfSquares(3, 2+2)square(3)+square(2+2)3*3+square(2+2)9+square(2+2)9+(2+2)*(2+2)9+4*49+1625
● Dla czystych funkcji zawsze ten sam wynik● call-by-value nie powtarza wyliczania argumentów
– Zazwyczaj bardziej wydajny
● call-by-name nie wylicza nieużywanych (niepotrzebnych) argumentów
Przygotował: Jacek Sroka
26
Wyliczanie wartości funkcji c.d.
● call-by-value łatwiej zapętlić scala> def loop: Int = loop loop: Int
scala> def first(x: Int, y: Int) = x first: (Int,Int)Int
scala> first(1,loop)
● call-by-name można wymusić scala> def constOne(x:Int, y: => Int) = 1 constOne: (Int,=> Int)Int scala> constOne(1,loop) res2: Int = 1
scala> constOne(loop,1)//pętla
Przygotował: Jacek Sroka
27
If-else
● Jak w Javie, ale w Scali jest również wyrażeniem (jak Javowy operator ternarny ... ? ... : ...)
scala> def abs(x: Double) = if (x >= 0) x else -x abs: (Double)Double
● Stałe logiczne i operatory są takie same jak w Javie
Przygotował: Jacek Sroka
28
Metoda Newtona
y x/y (y+x/y)/2
1 2/1 = 2 1.5
1.5 2/1.5 = 1.3333 1.4167
1.4167 2/1.4167 = 1.4118 1.4142
1.4142 ..... .....
Przygotował: Jacek Sroka
29
Przykład rozwiązania
● def sqrt(x: Double) = sqrtIter(1.0, x)
● def sqrtIter(guess: Double, x: Double): Double = if (isGoodEnough(guess, x)) guess else sqrtIter(improve(guess, x), x)
● def improve(guess: Double, x: Double) = (guess + x/guesss)/2
● def isGoodEnough(guess: Double, x: Double) = abs(square(guess) – x) < 0.001
Przygotował: Jacek Sroka
30
Zagnieżdżanie funkcji pomocniczych
def sqrt(x: Double) = {
def sqrtIter(guess: Double, x: Double): Double = if (isGoodEnough(guess, x)) guess else sqrtIter(improve(guess, x), x)
def improve(guess: Double, x: Double) = (guess + x/guesss)/2
def isGoodEnough(guess: Double, x: Double) = abs(square(guess) – x) < 0.001
sqrtIter(1.0, x)
}
//bloki w Scali są wyrażeniami o wartości kończącego wyrażenia
Przygotował: Jacek Sroka
31
Standardowa widoczność w zagnieżdżonych blokach
def sqrt(x: Double) = {
def sqrtIter(guess: Double): Double = if (isGoodEnough(guess, x)) guess else sqrtIter(improve(guess, x), x)
def improve(guess: Double) = (guess + x/guesss)/2
def isGoodEnough(guess: Double) = abs(square(guess) – x) < 0.001
sqrtIter(1.0)
}
Przygotował: Jacek Sroka
32
;
● każda definicja w bloku musi się kończyć „;”● domyślny średnik jest dodawany na końcu każdej linii, chyba że:
– linia kończy się słowem lub operatorem infiksowym, które nie byłyby dozwolone na końcu wyrażenia
– następna linia zaczyna się słowem od którego nie mogłoby się zaczynać wyrażenie
– jesteśmy otoczeni nawiasami
● def f(x: Int) = x + 1;f(1)+f(2)
● def g(x: Int) = x + 1g(1)+g(2)
● def h(x: Int) = {x+1}; h(1)+h(2) //tu średnik jest obowiązkowy
Przygotował: Jacek Sroka
33
;
● def m(x: Int) = x + ym(1)*m(2)
● def n(x: Int) = ( x //nawiasy powodują brak średnika + y)n(1)/n(2)
Przygotował: Jacek Sroka
34
Rekursja ogonowa
● def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
● gcd(14, 21)if (21 == 0) 14 else gcd(21, 14 % 21)if (false) 14 else gcd(21, 14 % 21)gcd(21, 14 % 21)gcd(21, 14)if (14 == 0) 21 else gcd(14, 21 % 14)gcd(14, 21 % 14)gcd(14, 7)if (7 == 0) 14 else gcd(7, 14 % 7)gcd(7, 14 % 7)gcd(7, 0)if (0 == 0) 7 else gcd(0, 7 % 0)7
Przygotował: Jacek Sroka
35
Rekursja ogonowa
● def factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n-1)
● factorial(5)if (5 == 0) 1 else 5 * factorial(5 1)5 * factorial(5 1)5 * factorial(4)5 * (4 * factorial(3))5 * (4 * (3 * factorial(2)))5 * (4 * (3 * (2 * factorial(1))))5 * (4 * (3 * (2 * (1 * factorial(0))))5 * (4 * (3 * (2 * (1 * 1))))120