1 Scala + NetBeans AKA: Nowoczesne obiektowe języki programowania i środowiska programistyczne na przykładzie Scali i środowiska NetBeans (spotkanie 6)
2 Iteratory Iteratory to imperatywne wersje strumieni Po danych poruszamy się metodami hasnext() i next() (może nie być struktury danych) trait Iterator[+A] { def hasnext: Boolean def next: A... Przykład val it: Iterator[Int] = Iterator.range(1, 100) while (it.hasnext) { val x = it.next println(x * x)
3 Przydatne metody iteratorów Iteratory posiadają wiele metod wzorowanych na listach append def append[b >: A](that: Iterator[B]): Iterator[B] = new Iterator[B] { def hasnext = Iterator.this.hasNext that.hasnext def next = if (Iterator.this.hasNext) Iterator.this.next else that.next Czemu służy składnia Iterator.this.hasNext? map def map[b](f: A => B): Iterator[B] = new Iterator[B] { def hasnext = Iterator.this.hasNext def next = f(iterator.this.next)
4 Przydatne metody iteratorów c.d. flatmap def flatmap[b](f: A => Iterator[B]): Iterator[B] = new Iterator[B] { private var cur: Iterator[B] = Iterator.empty def hasnext: Boolean = if (cur.hasnext) true else if (Iterator.this.hasNext) {cur = f(iterator.this.next); hasnext else false def next: B = if (cur.hasnext) cur.next else if (Iterator.this.hasNext) { cur = f(iterator.this.next); next else error("next on empty iterator") foreach def foreach(f: A => Unit): Unit = while (hasnext) { f(next)
5 Przydatne metody iteratorów c.d. filter def filter(p: A => Boolean) = new BufferedIterator[a] { private val source = Iterator.this.buffered private def skip { while (source.hasnext &&!p(source.head)) { source.next def hasnext: Boolean = { skip; source.hasnext def next: A = { skip; source.next def head: A = { skip; source.head gdzie: trait BufferedIterator[+A] extends Iterator[A] { def head: A Skoro jest map, flatmap, filter i foreach to są też for-comprehensions i for-loops for (i <- Iterator.range(1, 100)) println(i * i)
6 Przydatne metody iteratorów c.d. zip def zip[b](that: Iterator[B]) = new Iterator[(a, b)] { def hasnext = Iterator.this.hasNext && that.hasnext def next = {Iterator.this.next, that.next
7 Konstruowanie iteratorów Najprostszy iterator to Iterator.empty object empty extends Iterator[Nothing] { def hasnext = false def next = error("next on empty iterator") Iterator na tablicy def fromarray[a](xs: Array[A]) = new Iterator[A] { private var i = 0 def hasnext: Boolean = i < xs.length def next: A = if (i < xs.length) { val x = xs(i); i += 1; x else error("next on empty iterator")
8 Konstruowanie iteratorów c.d. Iterator na przedziale Iterator.range def range(start: Int, end: Int) = new Iterator[Int] { private var current = start def hasnext = current < end def next = { val r = current if (current < end) current += 1 else error("end of iterator") r Nieskończony ciąg licz całkowitych (w praktyce iterujący w kółko po reprezentacji int) def from(start: Int) = new Iterator[Int] { private var last = start - 1 def hasnext = true def next = { last += 1; last
9 Przykład zastosowań Wypisywanie tablicy xs: Array[Int] Iterator.fromArray(xs) foreach (x => println(x)) for (x <- Iterator.fromArray(xs)) println(x) Znajdywanie indeksów elementów tablicy o wartości większej niż zadana stała import Iterator._ fromarray(xs).zip(from(0)).filter(case (x, i) => x > limit).map(case (x, i) => i)?
10 Przykład zastosowań Wypisywanie tablicy xs: Array[Int] Iterator.fromArray(xs) foreach (x => println(x)) for (x <- Iterator.fromArray(xs)) println(x) Znajdywanie indeksów elementów tablicy o wartości większej niż zadana stała import Iterator._ fromarray(xs).zip(from(0)).filter(case (x, i) => x > limit).map(case (x, i) => i) import Iterator._ for ((x, i) <- fromarray(xs) zip from(0); if x > limit) yield i
11 Wbudowana leniwość Przykład: obsługa bazy pracowników, gdzie każdy pracownik ma szefa i zespół. Z gorliwą inicjalizacją większość danych jest od razu wczytywana do pamięci case class Employee(id: Int, name: String, managerid: Int) { val manager: Employee = Db.get(managerId) val team: List[Employee] = Db.team(id) Z leniwą inicjalizacją nie case class Employee(id: Int, name: String, managerid: Int) { lazy val manager: Employee = Db.get(managerId) lazy val team: List[Employee] = Db.team(id)
12 Przykład c.d. Poniższa implementacja bazy informuje kiedy wczytywane są dane. object Db { val table = Map(1 -> (1, "Haruki Murakami", 1), 2 -> (2, "Milan Kundera", 1), 3 -> (3, "Jeffrey Eugenides", 1), 4 -> (4, "Mario Vargas Llosa", 1), 5 -> (5, "Julian Barnes", 2)) def team(id: Int) = { for (rec <table.values.tolist; if rec._3 == id) yield rectoemployee(rec) def get(id: Int) = rectoemployee(table(id)) private def rectoemployee(rec: (Int, String, Int)) = { println("[db] fetching " + rec._1) Employee(rec._1, rec._2, rec._3)
13 Leniwość a cykliczne zależności Bez leniwości rekursja przy inicjalizacji wartości lokalnych jest niedozwolona class Symbols(val compiler: Compiler) { import compiler.types._ val Add = new Symbol("+", FunType(List(IntType, IntType), IntType)) val Sub = new Symbol("-", FunType(List(IntType, IntType), IntType)) class Symbol(name: String, tpe: Type) { override def tostring = name + ": " + tpe class Types(val compiler: Compiler) { import compiler.symtab._ abstract class Type case class FunType(args: List[Type], res: Type) extends Type case class NamedType(sym: Symbol) extends Type case object IntType extends Type
14 Leniwość a cykliczne zależności c.d. Tu będzie błąd w chwili wykonania class Compiler { val symtab = new Symbols(this) val types = new Types(this) A tu nie (zostanie wybrana właściwa kolejność) class Compiler { lazy val symtab = new Symbols(this) lazy val types = new Types(this)
15 Implicit parameters Półgrupa abstract class SemiGroup[A] { def add(x: A, y: A): A Monoid abstract class Monoid[A] extends SemiGroup[A] { def unit: A Implementacje object stringmonoid extends Monoid[String] { def add(x: String, y: String): String = x.concat(y) def unit: String = "" object intmonoid extends Monoid[Int] { def add(x: Int, y: Int): Int = x + y def unit: Int = 0
16 Implicit parameters Funkcja sumująca listy wartości z dowolnego monoidu def sum[a](xs: List[A])(m: Monoid[A]): A = if (xs.isempty) m.unit else m.add(xs.head, sum(xs.tail)(m)) sum(list("a", "bc", "def"))(stringmonoid) sum(list(1, 2, 3))(intMonoid) Co zrobić żeby drugi parametr był odgadywany tak samo jak zazwyczaj kompilator odgaduje typ?
17 Implicite parameters Ostatnia lista parametrów może być wyróżniona słowem implicit def sum[a](xs: List[A])(implicit m: Monoid[A]): A = if (xs.isempty) m.unit else m.add(xs.head, sum(xs.tail)) Takim samym słowem trzeba też wyróżnić obiekty oraz deklaracje implicit object stringmonoid extends Monoid[String] { def add(x: String, y: String): String = x.concat(y) def unit: String = "" implicit object intmonoid extends Monoid[Int] { def add(x: Int, y: Int): Int = x + y def unit: Int = 0 Scala wybierze najbardziej pasującą wartość z miejsca wywołania metody.
18 Konwersje Jeżeli wyrażenie zwraca wartość innego typu niż spodziewany Scala spróbuje skorzystać z dostępnych konwersji implicit def int2ordered(x: Int): Ordered[Int] = new Ordered[Int] { def compare(y: Int): Int = if (x < y) -1 else if (x > y) 1 else 0
19 View Bounds Można parametryzować typami, dla których istnieją wymagane konwersje. def sort[a <% Ordered[A]](xs: List[A]): List[A] = if (xs.isempty xs.tail.isempty) xs else { val {ys, zs = xs.splitat(xs.length / 2) merge(ys, zs) To lukier dla (żądamy konwersji) def sort[a](xs: List[A])(implicit c: A => Ordered[A]): List[A] =...
20 View Bounds c.d. Kontynuująć def merge[a <% Ordered[A]](xs: List[A], ys: List[A]): List[A] = if (xs.isempty) ys else if (ys.isempty) xs else if (xs.head < ys.head) xs.head :: merge(xs.tail, ys) else if ys.head :: merge(xs, ys.tail) To lukier dla def merge[a](xs: List[A], ys: List[A])(implicit c: A => Ordered[A]): List[A] = if (xs.isempty) ys else if (ys.isempty) xs else if (c(xs.head) < ys.head) xs.head :: merge(xs.tail, ys) else if ys.head :: merge(xs, ys.tail)(c)
21 Współbieżność
Sygnały i monitory Każdy egzemplarz AnyRef może być użyty jako monitor def synchronized[a] (e: => A): A def wait() def wait(msec: Long) def notify() def notifyall() Nie ma gwarancji, że obudzony wątek natychmiast się wykona (trzeba w pętli sprawdzić czy zaszły warunki do kontynuowania) class BoundedBuffer[A](N: Int) { var in = 0, out = 0, n = 0, elems = new Array[A](N) def put(x: A) = synchronized { while (n >= N) wait() elems(in) = x ; in = (in + 1) % N ; n = n + 1 if (n == 1) notifyall() def get: A = synchronized { while (n == 0) wait() val x = elems(out) ; out = (out + 1) % N ; n = n - 1 if (n == N - 1) notifyall() x 22
23 Sygnały i monitory c.d. Przykład użycia import scala.concurrent.ops._... val buf = new BoundedBuffer[String](10) spawn { while (true) { val s = producestring ; buf.put(s) spawn { while (true) { val s = buf.get ; consumestring(s) gdzie spawn jest zdefiniowane w scala.concurrent.ops def spawn(p: => Unit) { val t = new Thread() { override def run() = p t.start()
SyncVars Zmienne z get, set i unset (get blokuje dopóki nie ma wartości) package scala.concurrent class SyncVar[A] { private var isdefined: Boolean = false private var value: A = _ def get = synchronized { while (!isdefined) wait() value def set(x: A) = synchronized { value = x; isdefined = true; notifyall() def isset: Boolean = synchronized { isdefined def unset = synchronized { isdefined = false 24
25 Futures Wartość wyliczane współbieżnie, żeby w przyszłości zostać użyte Zastosowanie import scala.concurrent.ops._... val x = future(somelengthycomputation) anotherlengthycomputation val y = f(x()) + g(x()) Definicja w scala.concurrent.ops def future[a](p: => A): Unit => A = { val result = new SyncVar[A] fork { result.set(p) (() => result.get)
26 Inicjowanie współbieżności Współbieżne wykonanie pary obliczeń i zwrócenie pary wyników def par[a, B](xp: => A, yp: => B): (A, B) = { val y = new SyncVar[B] spawn { y set yp (xp, y.get) Można wykonać wiele obliczeń, każde wyróżnione numerkiem def replicate(start: Int, end: Int)(p: Int => Unit) { if (start == end) () else if (start + 1 == end) p(start) else { val mid = (start + end) / 2 spawn { replicate(start, mid)(p) replicate(mid, end)(p) Zastosowanie: przetwarzanie wszystkich elementów tablicy def parmap[a,b](f: A => B, xs: Array[A]): Array[B] = { val results = new Array[B](xs.length) replicate(0, xs.length) { i => results(i) = f(xs(i)) results
27 Semafory package scala.concurrent class Lock { var available = true def acquire = synchronized { while (!available) wait() available = false def release = synchronized { available = true notify()
28 Kanały asynchroniczne class Channel[A] { class LinkedList[A] { var elem: A = _ var next: LinkedList[A] = null private var written = new LinkedList[A] //czubek listy z którego czytamy private var lastwritten = written //pusty początek listy przez który dopisujemy private var nreaders = 0 def write(x: A) = synchronized { lastwritten.elem = x lastwritten.next = new LinkedList[A] lastwritten = lastwritten.next if (nreaders > 0) notify() def read: A = synchronized { while (written.next == null) { nreaders = nreaders + 1; wait(); nreaders = nreaders - 1 val x = written.elem written = written.next x
29 Kanały synchroniczne class SyncChannel[A] { private var data: A = _ private var reading = false private var writing = false def write(x: A) = synchronized { while (writing) wait() data = x writing = true if (reading) notifyall() else while (!reading) wait() def read: A = synchronized { while (reading) wait() reading = true while (!writing) wait() val x = data writing = false reading = false notifyall() x
30 Serwer obliczeniowy Przy pomocy kanału można zaimplementować serwer obliczeniowy z odgórnie ustaloną liczbą workerów (np. tyle co mamy rdzeni) class ComputeServer(n: Int) { private abstract class Job { type T def task: T //obliczenie do wykonania def ret(x: T) //metoda zwracająca wyni private val openjobs = new Channel[Job]() private def processor(i: Int) { while (true) { val job = openjobs.read job.ret(job.task)...
31 Serwer obliczeniowy c.d.... def future[a](p: => A): () => A = { val reply = new SyncVar[A]() openjobs.write{ new Job { type T = A def task = p def ret(x: A) = reply.set(x) () => reply.get spawn(replicate(0, n) { processor )
32 Serwer obliczeniowy c.d. Przykład użycia: object Test with Executable { val server = new ComputeServer(1) val f = server.future(41 + 1) println(f())
33 Mailbox Sygnatura class MailBox { def send(msg: Any) def receive[a](f: PartialFunction[Any, A]): A def receivewithin[a](msec: Long)(f: PartialFunction[Any, A]): A Funkcja częściowa przekazywana jako parametr jest wykonywana jeżeli jest pasująca wiadomość, wpp. jest wstrzymywana dopóki ta wiadomość się nie pojawi Wiadomościami mogą być dowolne obiekty jest wyróżniona wiadomość case object TIMEOUT
34 Mailbox: przykład użycia Bufor z miejscem na jedną wartość class OnePlaceBuffer { private val m = new MailBox // An internal mailbox private case class Empty, Full(x: Int) // Types of messages we deal with m send Empty // Initialization def write(x: Int) { m receive { case Empty => m send Full(x) def read: Int = m receive { case Full(x) => m send Empty; x
35 Mailbox: przykładowa implementacja Receiver reprezentuje obsługę komunikatu, msg jest ustawiane jak dopasowano pasujący komunikat private abstract class Receiver extends Signal { def isdefined(msg: Any): Boolean var msg = null Pamiętamy listy czekających wiadomości i Receiverów private val sent = new LinkedList[Any] private var lastsent = sent private val receivers = new LinkedList[Receiver] private var lastreceiver = receivers
36 Mailbox: przykładowa implementacja c.d. Przy wysyłaniu sprawdzamy czy jest pasujący Receiver i jeżeli tak to go wzbudzamy def send(msg: Any) = synchronized { var r = receivers, r1 = r.next while (r1!= null &&!r1.elem.isdefined(msg)) { r = r1; r1 = r1.next if (r1!= null) { r.next = r1.next; r1.elem.msg = msg; r1.elem.notify else { lastsent = insert(lastsent, msg)
37 Mailbox: przykładowa implementacja c.d. Przy pobieraniu najpierw sprawdzamy czy któraś z przechowywanych wiadomości pasuje do funkcji, wpp. tworzony jest nowy Receiver i zapamiętywany na liście i wątek zasypia aż do jego wzbudzenia. Po obudzeniu wątek odpala f dla wiadomości zapamiętanej na msg def receive[a](f: PartialFunction[Any, A]): A = { val msg: Any = synchronized { var s = sent, s1 = s.next while (s1!= null &&!f.isdefinedat(s1.elem)) { s = s1; s1 = s1.next if (s1!= null) { s.next = s1.next; s1.elem else { val r = insert(lastreceiver, new Receiver { def isdefined(msg: Any) = f.isdefinedat(msg) ) lastreceiver = r r.elem.wait() r.elem.msg f(msg)
Mailbox: zastosowanie class ReadersWriters { val m = new MailBox private case class Writers(n: Int) { m send this private case class Readers(n: Int) { m send this Writers(0) Readers(0) def startread = m receive { case Writers(0) => m receive { case Readers(n) => Readers(n+1); Writers(0) def startwrite = m receive { case Writers(0) => m receive { case Readers(0) => Writers(1) def endread = m receive { case Readers(n) => Readers(n-1) def endwrite = m receive { case Writers(1) => Writers(0); Readers(0) 38
39 Aktorzy Esencją aktora jest wątek posiadający mailbox Przykład z aukcją (ze skryptu) będzie na labie
40 Lift
41 Najważniejsze cechy security, maintainability, scalability, performance productivity Zaczerpnięcie dobrych pomysłów z istniejących rozwiązań Seaside wysoce ziarnista sesja i bezpieczeństwo Rails szybko widoczne efekty pracy Django gotowe CRUD i jeszcze trochę Wicket wygodne dla projektanta podejście do wzorców (Lift View First) aplikacje są pakowane jako WAR i osadzane na kontenerach Servlet 2.4 działają na Tomccie 5.5.xx oraz Jetty 6.0