PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4))...

52
Przygotował: Jacek Sroka 1 PO* - Scala (typy uogólnione, listy) przygotował Jacek Sroka w oparciu o materiały Martina Oderskiego

Transcript of PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4))...

Page 1: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

1

PO* - Scala (typy uogólnione, listy)

przygotował Jacek Srokaw oparciu o materiały Martina Oderskiego

Page 2: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

2

Przykład: stos

abstract class IntStack { def push(x: Int): IntStack = new IntNonEmptyStack(x, this) def isEmpty: Boolean def top: Int def pop: IntStack}

class IntEmptyStack extends IntStack { def isEmpty = true def top = error("EmptyStack.top") def pop = error("EmptyStack.pop")}

class IntNonEmptyStack(elem: Int, rest: IntStack) extends IntStack { def isEmpty = false def top = elem def pop = rest}

No to teraz jeszcze raz dla stosu napisów?

Page 3: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

3

Typy uogólnione

abstract class Stack[A] { def push(x: A): Stack[A] = new NonEmptyStack[A](x, this) def isEmpty: Boolean def top: A def pop: Stack[A]}

class EmptyStack[A] extends Stack[A] { def isEmpty = true def top = error("EmptyStack.top") def pop = error("EmptyStack.pop")}

class NonEmptyStack[A](elem: A, rest: Stack[A]) extends Stack[A] { def isEmpty = false def top = elem def pop = rest}

val x = new EmptyStack[Int]val y = x.push(1).push(2)println(y.pop.top)

Page 4: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

4

Metody polimorficzne

● Metody i ich parametry też można parametryzować typem

def isPrefix[A](p: Stack[A], s: Stack[A]): Boolean = { p.isEmpty || p.top == s.top && isPrefix[A](p.pop, s.pop)}

val s1 = new EmptyStack[String].push("abc")val s2 = new EmptyStack[String].push("abx").push(s1.top)println(isPrefix[String](s1, s2))

● Niezależnie od generyczności klasy

object PolyTest extends Application { def dup[T](x: T, n: Int): List[T] = if (n == 0) Nil else x :: dup(x, n – 1) println(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference}

Page 5: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

5

Ograniczanie typu

● Dowolny typ to czasami za dużo

abstract class Set[A] { def incl(x: A): Set[A] def contains(x: A): Boolean}

//dla implementacji z drzewami BSTdef contains(x: A): Boolean = if (x < elem) left contains x ^ < not a member of type A.

● Można go ograniczyć (do typów które da się porównywać)

trait Set[A <: Ordered[A]] { def incl(x: A): Set[A] def contains(x: A): Boolean}

Page 6: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

6

Ograniczanie typu c.d.

/** A class for totally ordered data. */trait Ordered[A] { /** Result of comparing ‘this’ with operand ‘that’. * returns ‘x’ where * x < 0 iff this < that * x == 0 iff this == that * x > 0 iff this > that */ def compare(that: A): Int

def < (that: A): Boolean = (this compare that) < 0 def > (that: A): Boolean = (this compare that) > 0 def <= (that: A): Boolean = (this compare that) <= 0 def >= (that: A): Boolean = (this compare that) >= 0 def compareTo(that: A): Int = compare(that)}

Page 7: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

7

Parametryzowane drzewa BST

class EmptySet[A <: Ordered[A]] extends Set[A] { def contains(x: A): Boolean = false def incl(x: A): Set[A] = new NonEmptySet(x, new EmptySet[A], new EmptySet[A])}

class NonEmptySet[A <: Ordered[A]] (elem: A, left: Set[A], right: Set[A]) extends Set[A] { def contains(x: A): Boolean = if (x < elem) left contains x else if (x > elem) right contains x else true def incl(x: A): Set[A] = if (x < elem) new NonEmptySet(elem, left incl x, right) else if (x > elem) new NonEmptySet(elem, left, right incl x) else this}//brakujące parametry typu w wywołaniach konstruktorów są uzupełniane tak

jak dla metod

Page 8: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

8

Drzewa BST na liczbach

● Element drzewa

case class Num(value: Double) extends Ordered[Num] { def compare(that: Num): Int = if (this.value < that.value) -1 else if (this.value > that.value) 1 else 0 }

● Poprawne użycie

val s = new EmptySet[Num].incl(Num(1.0)).incl(Num(2.0)) s.contains(Num(1.5))

● Niepoprawne użycie

val s = new EmptySet[java.io.File] ^ java.io.File does not conform to type parameter bound Ordered[java.io.File].

Page 9: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

9

View bounds

● A co jeśli nie możemy decydować o nadklasie? trait Set[A <% Ordered[A]] ...

class EmptySet[A <% Ordered[A]] ... class NonEmptySet[A <% Ordered[A]] ...

● O konwersjach i view bounds jeszcze opowiemy

Page 10: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

10

Subtyping

● co-variant subtypingclass Stack[+A] {...

– Jeśli T jest podtypem S to Stack[T] jest podtypem Stack[S]

● contra-variant subtypingclass Stack[-A] {...

– Jeśli T jest podtypem S to Stack[S] jest podtypem Stack[T]

● non-variant subtyping(domyślnie)

– nie ma zależności

Page 11: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

11

Czemu co-variant nie jest domyślne?

● W świecie funkcyjnym mogłoby być● Jeżeli obiekty mogą zmieniać stan trzeba kontrolować typy w chwili wykonania

class Array[A] { def appply(index: Int): A def update(index: Int, elem: A)}//Scala stara się to mapować na natywne tablice środowiska

val x = new Array[String](1)val y: Array[Any] = xy(0) = new Rational(1, 2) //lukier syntaktyczny dla //y.update(0, new Rational(1, 2))

– czyli w tablicy napisów znalazłaby się liczba!

● W Javie dla tablic mamy co-variant subtyping i dodatkową kontrolę w trzeciej linijce (ArrayStoreException)

– Ze względu na non-variant subtyping w Scali druga linia jest niedozwolona

– Tak! W Javie dla tablic jest co-variant mimo że dla innych typów jest non-variant!

Page 12: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

12

Statyczne ograniczanie co-variant subtyping

● Parametr co-variant może być w Scali użyty jedynie jako typ: atrybutu klasy, wartości zwrotnej metody, argument jakiejś innej klasy co-variant

● Nie może jako typ parametru metody (bo metody mogą zmieniać stan obiektu) class Array[+A] { def apply(index: Int): A def update(index: Int, elem: A) ^ covariant type parameter A appears in contravariant position.

● A co z metodami nie zmieniającymi stanu (stos jest w pełni funkcyjny)

class Stack[+A] {def push(x: A): Stack[A] = ^ covariant type parameter A appears in contravariant position.

Page 13: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

13

Co-variant subtyping i niezmieniające stanu metody

● Można uogólnić metodę push

class Stack[+A] { def push[B >: A](x: B): Stack[B] = new NonEmptyStack(x, this)

● T >: S oznacza, że T może być nadtypem S

– można łączyć oba zapisy T >: S <: U

● Czyli w wyniku push możemy dostać zbiór bardziej ogólnego typu

● Rozwiązując problem parametrów co-variant uogólniliśmy nasze rozwiązanie

Page 14: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

14

Najmniejsze typy

● W Scali typami nie można parametryzować obiektów

– dlatego musimy zdefiniować klasę EmptyStack[A]

– dla stosów co-variant moglibyśmy sobie poradzić mając podtyp wszystkich typów

● Typ Nothing jest najmniejszym typem i nie zawiera żadnej wartości (czyli Stack[Nothing] nie ma żadnych elementów)

object EmptyStack extends Stack[Nothing] { ... }

● val s = EmptyStack.push("abc").push(new AnyRef())

– EmptyStack jest typu Stack[Nothing], więc ma metodępush[B >: Nothing](elem: B): Stack[B]

– z type inference wiemy, że B to String, więc wynikiem jest Stack[String]push[B >: String](elem: B): Stack[B]

– teraz B to AnyRef, więc wynikiem jest Stack[AnyRef]

Page 15: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

15

Null

● Nothing jest podtypem wszystkich typów

● Null jest podtypem AnyRef i wszystkich jego podtypów

● Null posiada tylko jedną wartość null

● Czyli null jest kompatybilne ze wszystkimi obiektami, ale nie z wartościami typów podstawowych

Page 16: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

16

Hierarchia klas

Page 17: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

17

Przykład

Jaki ma typ?

if (true) 1 else true

Page 18: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

18

Przykład

Jaki ma typ?

if (true) 1 else true

AnyVal (oraz Any)

Page 19: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

19

Podsumowanie

abstract class Stack[+A] { def push[B >: A](x: B): Stack[B] = new NonEmptyStack(x, this) def isEmpty: Boolean def top: A def pop: Stack[A]}

object EmptyStack extends Stack[Nothing] { def isEmpty = true def top = error("EmptyStack.top") def pop = error("EmptyStack.pop")}

class NonEmptyStack[+A](elem: A, rest: Stack[A]) extends Stack[A] { def isEmpty = false def top = elem def pop = rest}

Page 20: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

20

Krotki

● Czasami funkcja zwraca kilka wartości

case class TwoInts(first: Int, second: Int)def divmod(x: Int, y: Int): TwoInts = new TwoInts(x / y, x % y)

● Scala ma wbudowany typ generyczny do reprezentowania krotek

package scalacase class Tuple2[A, B](_1: A, _2: B)def divmod(x: Int, y: Int) = new Tuple2[Int, Int](x / y, x % y)

● Są też krotki o większej liczbie elementów (oczywiście są jakieś ograniczenia)

● Dostęp do elementów krotki (tak jak do innych case classes)– val xy = divmod(x, y)

println("quotient: " + xy._1 + ", rest: " + xy._2)

– divmod(x, y) match { case Tuple2(n, d) => println("quotient: " + n + ", rest: " + d)}//we wzorcach nie używamy type parameters (Tuples2[Int, Int](n,d))

Page 21: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

21

Lukier syntaktyczny

● Zamiast Tuplen(x1,..., xn) można napisać (x1, ..., xn)

● def divmod(x: Int, y: Int): (Int, Int) = (x / y, x % y)

● divmod(x, y) match { case (n, d) => println("quotient: " + n + ", rest: " + d)}

Page 22: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

22

Funkcje

● Wszystko w Skali jest obiektem● Funkcje w Scali są zwykłymi wartościami● Czyli funkcje są obiektami

– (T1, ..., Tn) => S

– Functionn[T1, ..., Tn, S]

● Przykładowy trait dla funkcji z jednym argumentem

package scalatrait Function1[-A, +B] { def apply(x: A): B}

– f(x) to skrót dla f.apply(x)

– to dlatego Array miało metodę apply

class Array[A] { def apply(index: Int): A //stąd skrót a(i) odpowiada a.apply(i) def update(index: Int, elem: A)}

Page 23: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

23

Tu się przydaje contra-variant subtyping

● val f: (AnyRef => Int)val g: (String => Int)g("abc")

● Dla funkcji mamy co-variant dla wartości zwrotnej i contra-variant dla wartości argumentów

– S=>T jest podtypem S'=>T' jeżeli S' jest podtypem S i T jest podtypem T'

Page 24: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

24

Przykład: funkcje

● Deklaracja: val plus1: (Int => Int) = (x: Int) => x + 1plus1(2)

● Rozwija się do:

val plus1: Function1[Int, Int] = new Function1[Int, Int] { def apply(x: Int): Int = x + 1}plus1.apply(2)

● new Function1[Int, Int] {...} to klasa anonimowa

val plus1: Function1[Int, Int] = { class Local extends Function1[Int, Int] { def apply(x: Int): Int = x + 1 } new Local: Function1[Int, Int]}plus1.apply(2)

Page 25: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

25

Dalszy lukier syntaktyczny

● Zamiast ((x,y)=>x+y)) można pisać (_ * _)

– kolejne _ reprezentują kolejne parametry funkcji

● Uwaga: listy parametrów funkcji i krotki to nie to samo!

Page 26: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

26

Listy

Są podobne do tablic, ale● niemutowalne● mają rekurencyjną budowę● udostępniają więcej operacji

val fruit = List("apples", "oranges", "pears")val nums = List(1, 2, 3, 4)val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1))val empty = List()

Page 27: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

27

Typ listy

● Tak jak tablice są homogeniczne

● Ich typ zapisujemy List[T]

val fruit: List[String] = List("apples", "oranges", "pears")val nums : List[Int] = List(1, 2, 3, 4)val diag3: List[List[Int]] = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1))val empty: List[Int] = List()

Page 28: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

28

Jeszcze trochę lukru

● Można korzystać z notacji (tak naprawdę poprzedni zapis się do niej sprowadza) Nil, ::

val fruit = "apples" :: ("oranges" :: ("pears" :: Nil))

● :: jest prawostronnie łączny

val nums = 1 :: 2 :: 3 :: 4 :: Nil val diag3 = (1 :: 0 :: 0 :: Nil) :: (0 :: 1 :: 0 :: Nil) :: (0 :: 0 :: 1 :: Nil) :: Nil val empty = Nil

● List ma też podstawowe operacje

empty.isEmpty = true fruit.isEmpty = false fruit.head = "apples" fruit.tail.head = "oranges" diag3.head = List(1, 0, 0) nums.reverse = 4::3::2::1::Nil nums indexOf 5 = -1 nums contains 5 = false

Page 29: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

29

Listy i pattern matching

● Nil – lista pusta

● x::xs – lista z głową i ogonem

● x::Nil – lista z jednym elementem

● List() – lista pusta

● List(x) – lista z jednym elementem

● List(x1,x2,x3) – lista z trzema elementami

● 1::2::3::xs – co najmniej trzy elementy, zaczyna się od 1,2,3

● List(1::xs) – ???

Page 30: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

30

Przykład: sortowanie

● isEmpty, head i tail oraz rekurencja pozwalają zdefiniować na listach wszystkie operacje, np. sortowanie przez wstawianie

def isort(xs: List[Int]): List[Int] = if (xs.isEmpty) Nil else insert(xs.head, isort(xs.tail))

● :: to tak naprawdę case class

def isort(xs: List[Int]): List[Int] = xs match { case List() => List() case x :: xs1 => insert(x, isort(xs1)) }

def insert(x: Int, xs: List[Int]): List[Int] = xs match { case List() => List(x) case y :: ys => if (x <= y) x :: xs else y :: insert(x, ys) }

Page 31: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

31

Definicja w pakiecie scala

● Klasa List jest zdefiniowana w pakiecie scala z parametrem co-variant

package scalaabstract class List[+A] {

● Przykładowe implementacje metod

def isEmpty: Boolean = this match { case Nil => true case x :: xs => false}

def head: A = this match { case Nil => error("Nil.head") case x :: xs => x}

def tail: List[A] = this match { case Nil => error("Nil.tail") case x :: xs => xs}

Page 32: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

32

Inne funkcje

● Długość listy(oczywiście da się zastosować rekursję ogonową)

def length: Int = this match { case Nil => 0 case x :: xs => 1 + xs.length}

● Ostatni element na liście

def last: A = this match { case Nil => error("Nil.last") case x :: Nil => x case x :: xs => xs.last}

● Wszystko bez ostatniego elementu

def init: A = this match { case Nil => error("Nil.last") case x :: Nil => Nil case x :: xs => x::xs.init}

Page 33: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

33

Inne funkcje c.d.

● Pierwsze n, wszystko bez pierwszych n oraz podział

def take(n: Int): List[A] = if (n == 0 || isEmpty) Nil else head :: tail.take(n-1)

def drop(n: Int): List[A] = if (n == 0 || isEmpty) this else tail.drop(n-1)

def split(n: Int): (List[A], List[A]) = (take(n), drop(n))

– xs.drop(m).take(n – m) //daje elementy od m+1 do n

– def apply(n: Int): A = drop(n-1).head//czyli działa lukier xs.apply(3) = xs(3)

● Z pary list tworzy listę par (ucina dłuższą listę)

def zip[B](that: List[B]): List[(A,B)] = if (this.isEmpty || that.isEmpty) Nil else (this.head, that.head) :: (this.tail zip that.tail)

Page 34: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

34

Operator ::

● Operatory kończące się dwukropkiem są szczególne

– Są traktowane jako metody prawego operandu

x :: y = y.::(x) zamiast x + y = x.+(y)

– Mimo to zachowują kolejność wyliczania operandów od lewej

D :: E tłumaczy się do {val x = D; E.::(x)}

– Są prawostronnie łączne

x :: y :: z = x :: (y :: z) zamiast x + y + z = (x + y) + z

● Przykładowa definicja

def ::[B >: A](x: B): List[B] = new scala.::(x, this)

Page 35: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

35

Konkatenacja i odwracanie

● ::: to operator konkatenacji (też kończy się dwukropkiem)

– xs ::: ys ::: zs =xs ::: (ys ::: zs) =zs.:::(ys).:::(xs)

– def :::[B >: A](prefix: List[B]): List[B] = prefix match { case Nil => this case p :: ps => this.:::(ps).::(p)}

● Odwrócona lista

def reverse[A](xs: List[A]): List[A] = xs match { case Nil => Nil case x :: xs => reverse(xs) ::: List(x)}

– Ta implementacja jest kwadratowa (bo konkatenacja jest liniowa względem długości pierwszego operandu), ale można lepiej

● ++ to konkatenacja dla dowolnych obiektów iterowalnych

Page 36: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

36

Merge sort

def msort[A](less: (A, A) => Boolean)(xs: List[A]): List[A] = {

def merge(xs1: List[A], xs2: List[A]): List[A] =

if (xs1.isEmpty) xs2

else if (xs2.isEmpty) xs1

else if (less(xs1.head, xs2.head)) xs1.head :: merge(xs1.tail, xs2)

else xs2.head :: merge(xs1, xs2.tail)

val n = xs.length/2

if (n == 0) xs

else merge(msort(less)(xs take n), msort(less)(xs drop n))

}

msort((x: Int, y: Int) => x < y)(List(5, 7, 1, 3))

val intSort = msort((x: Int, y: Int) => x < y)

val reverseSort = msort((x: Int, y: Int) => x > y)

Page 37: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

37

scala.math.Ordering[T]

● Klasa ze standardowej biblioteki zbierająca porządki scala.math.Ordering[T] z metodą ord.lt(x,y)

– np. Ordering.Int lub Ordering.String

Page 38: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

38

Wbudowane sortowanie

val dania = List("Pad Thai", "Bun Nem", "Bun Dau Phu")

dania sortWith (_.length < _.length)

//List("Bun Nem", "Pad Thai", "Bun Dau Phu")

dania sorted

//List("Bun Dau Phu", "Bun Nem", "Pad Thai")

Page 39: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

39

Typowe operacje wyższego rzędu

● Inny rodzaj polimorfizmu● Często spotykane, warto znać i stosować

● Przetworzenie każdego elementu przy pomocy jakiejś funkcji

● Wydobycie wszystkich elementów spełniających zadane kryterium

● Agregacja wszystkich elementów przy pomocy jakiegoś operatora

Page 40: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

40

map

● Motywacja – skalowanie każdego elementu przez jakiś współczynnik

def scaleList(xs: List[Double], factor: Double): List[Double] = xs match { case Nil => xs case x :: xs1 => x * factor :: scaleList(xs1, factor)}

● Uogólniona definicja z List

def map[B](f: A => B): List[B] = this match { case Nil => this case x :: xs => f(x) :: xs.map(f)}

● Skalowanie jeszcze raz

def scaleList(xs: List[Double], factor: Double) = xs map (x => x * factor)

● Zwracanie kolumny z tablicy reprezentowanej jako lista wierszy

def column[A](xs: List[List[A]], index: Int): List[A] = xs map (row => row(index))

Page 41: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

41

foreach

● Podobna do map, ale nie produkuje wyniku (tylko dla efektów ubocznych)

def foreach(f: A => Unit) { this match { case Nil => () case x :: xs => f(x); xs.foreach(f) }}

● Przykładem efektu ubocznego jest wypisywanie

xs foreach (x => println(x))

Page 42: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

42

filter

● Motywacja – zwracanie wszystkich elementów dodatnich

def posElems(xs: List[Int]): List[Int] = xs match { case Nil => xs case x :: xs1 => if (x > 0) x :: posElems(xs1) else posElems(xs1)}

● Uogólniona definicja z List

def filter(p: A => Boolean): List[A] = this match { case Nil => this case x :: xs => if (p(x)) x :: xs.filter(p) else xs.filter(p)}

● Elementy dodatnie jeszcze raz

def posElems(xs: List[Int]): List[Int] = xs filter (x => x > 0)

Page 43: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

43

Kwantyfikatory

● Definicja

def forall(p: A => Boolean): Boolean = isEmpty || (p(head) && (tail forall p))def exists(p: A => Boolean): Boolean = !isEmpty && (p(head) || (tail exists p))

● Przykład: sprawdzanie czy liczba jest pierwsza

def isPrime(n: Int) = List.range(2, n) forall (x => n % x != 0)

//gdziepackage scalaobject List { ... def range(from: Int, end: Int): List[Int] = if (from >= end) Nil else from :: range(from + 1, end)

Page 44: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

44

Dygresja: zakresy

● To rodzaj Sequence, ale nie przechowują elementów tylko sposób wyliczania

– val r1: Range = 1 until 3 //1,2

– val r2: Range = 1 to 3 //1,2,3

– 10 to 1 by -2 //10,8,6,4,2

Page 45: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

45

Agregowanie

● Motywacja

sum(List(x1 , ..., xn )) = 0 + x1 + ... + xnproduct(List(x1 , ..., xn )) = 1 * x1 * ... * xn

● Implementacja wprost:

def sum(xs: List[Int]): Int = xs match { case Nil => 0 case y :: ys => y + sum(ys)}

def product(xs: List[Int]): Int = xs match { case Nil => 1 case y :: ys => y * product(ys)}

Page 46: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

46

reduceLeft

● Wspólny motyw

List(x1 , ..., xn ).reduceLeft(op) = (...(x1 op x2 ) op ... ) op xn

def sum(xs: List[Int]) = (0 :: xs) reduceLeft {(x, y) => x + y}def product(xs: List[Int]) = (1 :: xs) reduceLeft {(x, y) => x * y}

● Przykładowa definicja

def reduceLeft(op: (A, A) => A): A = this match { case Nil => error("Nil.reduceLeft") case x :: xs => (xs foldLeft x)(op)}

def foldLeft[B](z: B)(op: (B, A) => B): B = this match { case Nil => z case x :: xs => (xs foldLeft op(z, x))(op)}

– foldLeft można też użyć bezpośrednio

– (List(x1 , ..., xn ) foldLeft z)(op) = (...(z op x1 ) op ... ) op xn

– def sum(xs: List[Int]) = (xs foldLeft 0) {(x, y) => x + y}def product(xs: List[Int]) = (xs foldLeft 1) {(x, y) => x * y}

Page 47: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

47

reduce...

● Redukcję można też wykonywać w prawo

List(x1 , ..., xn ).reduceRight(op) = x1 op ( ... (xn−1 op xn )...)(List(x1 , ..., xn ) foldRight acc)(op) = x1 op ( ... (xn op acc)...)

● Przykładowa definicja

def reduceRight(op: (A, A) => A): A = this match { case Nil => error("Nil.reduceRight") case x :: Nil => x case x :: xs => op(x, xs.reduceRight(op))}

def foldRight[B](z: B)(op: (A, B) => B): B = this match { case Nil => z case x :: xs => op(x, (xs foldRight z)(op))}

● Synonimy

def /:[B](z: B)(f: (B, A) => B): B = foldLeft(z)(f)def :\[B](z: B)(f: (A, B) => B): B = foldRight(z)(f)

– Co pasuje do zasady że operator kończący się : jest wywoływany dla prawego operandu(z /: List(x1 , ..., xn ))(op) = (...(z op x1 ) op ... ) op xn(List(x1 , ..., xn ) :\ z)(op) = x1 op ( ... (xn op z)...)

Page 48: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

48

flatten

● Łączy listy z listy list

def flatten[A](xs: List[List[A]]): List[A] = (xs :\ (Nil: List[A])) {(x, xs) => x ::: xs}

● A co z alternatywną definicją (która działa szybciej)?(konkatenacja działa proporcjonalnie do dł pierwszego operandu)

def flatten[A](xs: List[List[A]]): List[A] = ((Nil: List[A]) /: xs) ((xs, x) => xs ::: x)

● Prawdziwy flatten jest w obiekcie List

List.flatten

Page 49: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

49

Liniowe reverse

● Odgadnijmy czym zastąpić ?

def reverse: List[A] = (z? /: this)(op?)

● Nil= Nil.reverse // by specification= (z /: Nil)(op) // by the template for reverse= (Nil foldLeft z)(op) // by the definition of /:= z // by definition of foldLeft

● List(x)= List(x).reverse // by specification= (Nil /: List(x))(op) // by the template for reverse, with z = Nil= (List(x) foldLeft Nil)(op) // by the definition of /:= op(Nil, x) // by definition of foldLeft // czyli op(x,y) = y :: x

Page 50: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

50

Liniowe reverse

● Czyli

def reverse: List[A] = ((Nil: List[A]) /: this) {(xs, x) => x :: xs}

– adnotacja typu jest potrzebna

● Nil= Nil.reverse // by specification= (z /: Nil)(op) // by the template for reverse= (Nil foldLeft z)(op) // by the definition of /:= z // by definition of foldLeft

● List(x)= List(x).reverse // by specification= (Nil /: List(x))(op) // by the template for reverse, with z = Nil= (List(x) foldLeft Nil)(op) // by the definition of /:= op(Nil, x) // by definition of foldLeft

Page 51: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

51

Przykład

● Przy pomocy tych funkcji można rozwiązać wiele problemów dla których imperatywnie potrzebowaliśmy zagnieżdżonych pętli

● Przykład: dla danego n znaleźć wszystkie pary 1 <= j < i < ntakie że i+j jest pierwsza

– Dla n=7 i 2 3 4 4 5 6 6 j 1 2 1 3 2 1 5i+j 3 5 5 7 7 7 11

● Rozwiązanie w dwóch krokach

– Generujemy pary i,j

– Filtrujemy

List.range(2, n) .map(i => List.range(1, i).map(j => (i, j))) .foldRight(List[(Int, Int)]()) {(xs, ys) => xs ::: ys} .filter(pair => isPrime(pair._1 + pair._2))

Page 52: PO* - Scala (typy uogólnione, listy)sroka/dydaktyka/po_gw/scala3.pdfprintln(dup[Int](3, 4)) println(dup("three", 3))//scala ma dobry algorytm type inference} Przygotował: Jacek Sroka

Przygotował: Jacek Sroka

52

Przykład c.d.

● Kombinacja mapowania i konkatenacji jest tak często spotykana, że jest skrót (lub przy pomocy naszego flatten)

abstract class List[+A] { ... def flatMap[B](f: A => List[B]): List[B] = this match { case Nil => Nil case x :: xs => f(x) ::: (xs flatMap f) }}

● Nowe rozwiązanie

List.range(2, n) .flatMap(i => List.range(1, i).map(j => (i, j))) .filter(pair => isPrime(pair._1 + pair._2))