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

Podobne dokumenty
Przygotował: Jacek Sroka. PO* - Scala (typy uogólnione, listy)

Przygotował: Jacek Sroka. PO* - Scala (iteratory, leniwość, view bounds i konwersje)

Scala + NetBeans AKA: Nowoczesne obiektowe języki programowania i środowiska programistyczne na przykładzie Scali i środowiska NetBeans

Scala. Obiektowo-funkcyjny język programowania. Zbyszek Skowron

Programowanie obiektowe

Scala. Wprowadzenie do języka.

REKURENCJA W JĘZYKU HASKELL. Autor: Walczak Michał

Wstęp do programowania

Kurs programowania. Wykład 9. Wojciech Macyna. 28 kwiecień 2016

Programowanie Funkcyjne. Marcin Kubica Świder,

Wstęp do Programowania potok funkcyjny

Programowanie i projektowanie obiektowe

λ parametry. wartość funkcji suma = λ x y. x + y kwadrat = λ x. x * x K.M. Ocetkiewicz, 2008 WETI, PG 2 K.M. Ocetkiewicz, 2008 WETI, PG 3

Wstęp do Programowania potok funkcyjny

KOTLIN. Język programowania dla Androida

Język programowania Scala / Grzegorz Balcerek. Wyd. 2. Poznań, cop Spis treści

Kurs programowania. Wykład 9. Wojciech Macyna

Podstawy programowania. Podstawy C# Tablice

Języki i Paradygmaty Programowania

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

Programowanie funkcyjne wprowadzenie Specyfikacje formalne i programy funkcyjne

Programowanie. Lista zadań nr 15. Na ćwiczenia 11, 19 i 23 czerwca 2008

Platformy Programistyczne Podstawy języka Java

Kurs programowania. Wykład 1. Wojciech Macyna. 3 marca 2016

Elementy języka Haskell

Polimorfizm a klasy generyczne w języku Java. Zdzisław Spławski 1

Laboratorium 03: Podstawowe konstrukcje w języku Java [2h]

Składnia funkcji i Rekurencja w języku Haskell

Laboratorium z przedmiotu: Inżynieria Oprogramowania INEK Instrukcja 6

Informatyka 1. Wyrażenia i instrukcje, złożoność obliczeniowa

2. Klasy cz. 2 - Konstruktor kopiujący. Pola tworzone statycznie i dynamicznie - Funkcje zaprzyjaźnione - Składowe statyczne

Wstęp do Programowania potok funkcyjny

Listy, krotki, słowniki, funkcje

FP vs OOP? Mikołaj Fejzer

Specyfikacje formalne

UML a kod w C++ i Javie. Przypadki użycia. Diagramy klas. Klasy użytkowników i wykorzystywane funkcje. Związki pomiędzy przypadkami.

java.util.* :Kolekcje Tomasz Borzyszkowski

Szablony funkcji i szablony klas

Abstrakcyjny typ danych

Polimorfizm, metody wirtualne i klasy abstrakcyjne

Dynamiczne struktury danych

Klasy generyczne. ZbiórLiczb. ZbiórCzegokolwiek. Zbiór

Algorytmy i Struktury Danych. Anna Paszyńska

Technologie i usługi internetowe cz. 2

Języki programowania Haskell

Język skryptowy: Laboratorium 1. Wprowadzenie do języka Python

Wstęp do programowania

Paradygmaty programowania

Słowem wstępu. Część rodziny języków XSL. Standard: W3C XSLT razem XPath 1.0 XSLT Trwają prace nad XSLT 3.0

Programowanie w języku C++

Kolekcje w Javie cz. 1

Dawid Gierszewski Adam Hanasko

Automaty do zadań specjalnych. Olga Maciaszek-Sharma, Artur Kotow Wersja 1,

Definicje wyższego poziomu

Odczyt danych z klawiatury Operatory w Javie

Systemy GIS Tworzenie zapytań w bazach danych

Wstęp do Programowania potok funkcyjny

Przeciążenie operatorów

Tworzenie stron internetowych z wykorzystaniem HTM5, JavaScript, CSS3 i jquery. Łukasz Bartczuk

Scala + NetBeans AKA: Nowoczesne obiektowe języki programowania i środowiska programistyczne na przykładzie Scali i środowiska NetBeans

Oracle PL/SQL. Paweł Rajba.

Przygotował: Jacek Sroka. PO* Scala c.d. przygotował Jacek Sroka w oparciu o materiały Martina Oderskiego

Szablony klas, zastosowanie szablonów w programach

Kiedy potrzebne. Struktura (rekord) Struktura w języku C# Tablice struktur. struktura, kolekcja

JAVA W SUPER EXPRESOWEJ PIGUŁCE

Część 4 życie programu

Programowanie RAD Delphi

Podstawowe części projektu w Javie

Wykład 3 Składnia języka C# (cz. 2)

Programowanie obiektowe

Java: kilka brakujących szczegółów i uniwersalna nadklasa Object

Obiektowy Caml. Paweł Boguszewski

Obliczenia na stosie. Wykład 9. Obliczenia na stosie. J. Cichoń, P. Kobylański Wstęp do Informatyki i Programowania 266 / 303

JavaScript funkcyjność

kiedy znowu uzyska sterowanie, to podejmuje obliczenie od miejsca, w którym poprzednio przerwała, i z dotychczasowymi wartościami zmiennych,

Wykład 4: Klasy i Metody

Wstęp do programowania. Różne różności

Programowanie Komputerów

Interfejsy. Programowanie obiektowe. Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki Politechniki Wrocławskiej

Wykład 8. Moduły. Zdzisław Spławski Programowanie funkcyjne 1

DIAGRAMY SYNTAKTYCZNE JĘZYKA TURBO PASCAL 6.0

Wstęp do programowania. Listy. Piotr Chrząstowski-Wachtel

Algorytmy i. Wykład 3: Stosy, kolejki i listy. Dr inż. Paweł Kasprowski. FIFO First In First Out (kolejka) LIFO Last In First Out (stos)

Dynamiczny przydział pamięci w języku C. Dynamiczne struktury danych. dr inż. Jarosław Forenc. Metoda 1 (wektor N M-elementowy)

Technologie cyfrowe semestr letni 2018/2019

Składowane procedury i funkcje

Programowanie w Javie 1 Wykład i Ćwiczenia 3 Programowanie obiektowe w Javie cd. Płock, 16 października 2013 r.

Klasy abstrakcyjne i interfejsy

Kiedy i czy konieczne?

Bloki anonimowe w PL/SQL

Ada 95 #1/5 - typy. Typy skalarne. Hierarchia typów w Adzie. Typ znakowy. Typy dyskretne. Plan wykładu

Programowanie w C++ Wykład 6. Katarzyna Grzelak. 1 kwietnia K.Grzelak (Wykład 6) Programowanie w C++ 1 / 43

Klasa jest nowym typem danych zdefiniowanym przez użytkownika. Najprostsza klasa jest po prostu strukturą, np

Metody Kompilacji Wykład 7 Analiza Syntaktyczna

Swift (pol. jerzyk) nowy język programowania zaprezentowany latem 2014 r. (prace od 2010 r.)

Jzyk C++ cz 3. Jarosław Gramacki Instytut Informatyki i Elektroniki ( $)*)+' *, - ( ' )*'.' '',*/ *, ','*0) 1 / ) %*+ 2'' 2" ( $%%) )'20 )*0) 1 / )

Visual Basic for Application (VBA)

Operacje wykonywane są na operandach (argumentach operatorów). Przy operacji dodawania: argumentami operatora dodawania + są dwa operandy 2 i 5.

SQL, LIKE, IN, CASE, EXISTS. Marcin Orchel

Semantyka i Weryfikacja Programów - Laboratorium 3

Transkrypt:

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

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?

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)

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

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 BST def 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

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)

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

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].

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

10 Subtyping co-variant subtyping class Stack[+A] {... Jeśli T jest podtypem S to Stack[T] jest podtypem Stack[S] contra-variant subtyping class 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

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] = x y(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! 11

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.

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

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]

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

16 Hierarchia klas

17 Przykład Jaki ma typ? if (true) 1 else true

18 Przykład Jaki ma typ? if (true) 1 else true AnyVal (oraz Any)

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

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 scala case 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))

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)

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 scala trait 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)

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'

24 Przykład: funkcje Deklaracja: val plus1: (Int => Int) = (x: Int) => x + 1 plus1(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)

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!

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()

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()

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

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)???

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)

31 Definicja w pakiecie scala Klasa List jest zdefiniowana w pakiecie scala z parametrem co-variant package scala abstract 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

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 32

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)

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)

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

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)

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

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")

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

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))

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))

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)

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) //gdzie package scala object List {... def range(from: Int, end: Int): List[Int] = if (from >= end) Nil else from :: range(from + 1, end)

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

45 Agregowanie Motywacja sum(list(x1,..., xn )) = 0 + x1 +... + xn product(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)

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

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)...)

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

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

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

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 < n takie że i+j jest pierwsza Dla n=7 i 2 3 4 4 5 6 6 j 1 2 1 3 2 1 5 i+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))

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))