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

Podobne dokumenty
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

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

Wstęp do programowania

Scala. Wprowadzenie do języka.

Wstęp do Programowania potok funkcyjny

Kurs programowania. Wykład 9. Wojciech Macyna

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

Programowanie Funkcyjne. Marcin Kubica Świder,

Wstęp do Programowania potok funkcyjny

Programowanie i projektowanie obiektowe

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

Platformy Programistyczne Podstawy języka Java

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

Listy, krotki, słowniki, funkcje

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

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

Programowanie funkcyjne wprowadzenie Specyfikacje formalne i programy funkcyjne

Laboratorium z przedmiotu: Inżynieria Oprogramowania INEK Instrukcja 6

Języki programowania Haskell

λ 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

KOTLIN. Język programowania dla Androida

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

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

Elementy języka Haskell

Odczyt danych z klawiatury Operatory w Javie

Podstawy programowania. Podstawy C# Tablice

Specyfikacje formalne

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

FP vs OOP? Mikołaj Fejzer

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

Szablony funkcji i szablony klas

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

Programowanie obiektowe

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

Paradygmaty programowania

Języki i Paradygmaty Programowania

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

Programowanie Komputerów

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

Dawid Gierszewski Adam Hanasko

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

Systemy GIS Tworzenie zapytań w bazach danych

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

Podstawowe części projektu w Javie

Abstrakcyjny typ danych

Wstęp do programowania

Algorytmy i Struktury Danych. Anna Paszyńska

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)

Składnia funkcji i Rekurencja w języku Haskell

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

Kolekcje w Javie cz. 1

Programowanie w języku C++

Programowanie RAD Delphi

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

Wstęp do Programowania potok funkcyjny

Obiektowy Caml. Paweł Boguszewski

Polimorfizm, metody wirtualne i klasy abstrakcyjne

Bloki anonimowe w PL/SQL

Przeciążenie operatorów

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

Wstęp do ruby dla programistów javy

java.util.* :Kolekcje Tomasz Borzyszkowski

TYPY GENERYCZNE (GENERICS)

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

Szablony klas, zastosowanie szablonów w programach

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

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

Gramatyki atrybutywne

Metody Kompilacji Wykład 7 Analiza Syntaktyczna

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

Technologie i usługi internetowe cz. 2

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

JavaScript funkcyjność

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

Wstęp do programowania

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

Kiedy i czy konieczne?

PODSTAWY BAZ DANYCH 13. PL/SQL

PARADYGMATY PROGRAMOWANIA Wykład 3

Dynamiczne struktury danych

Lista, Stos, Kolejka, Tablica Asocjacyjna

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

Visual Basic for Application (VBA)

Właściwości i metody obiektu Comment Właściwości

Metody Kompilacji Wykład 3

EPI: Interfejs Graficzny 2011/2012 Laboratorium nr 2 Programowanie obiektowe

Programowanie obiektowe

Wstęp do programowania

JAVA W SUPER EXPRESOWEJ PIGUŁCE

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

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

Stałe definiuje się używając funkcji define. Przykład: define( PODATEK, 22); define( INSTALACJAOS, 70); define( MS, Microsoft );

Definicje wyższego poziomu

Podstawy Programowania C++

Wstęp do Programowania potok funkcyjny

Programowanie obiektowe

Transkrypt:

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

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 generyczne 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 Polimorficzne metody 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 konst. 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ła 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! 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 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

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

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

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

20 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'

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

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

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

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

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

26 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ć rekurencję 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 27

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

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

30 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

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

32 Typowe operacje wyższego rzędu 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

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

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

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

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

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

38 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

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

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

41 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

42 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

43 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(x => (i, x))).foldright(list[(int, Int)]()) {(xs, ys) => xs ::: ys.filter(pair => isprime(pair._1 + pair._2))

44 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(x => (i, x))).filter(pair => isprime(pair._1 + pair._2))