1 PO* - Scala (iteratory, leniwość, view bounds i konwersje)
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((x, i) => x > limit).map((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((x, i) => x > limit).map((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)