• Keine Ergebnisse gefunden

Deklarative (= fortgeschrittene) Programmierung Vorlesung WS 09,10; SS 12–14, 16

N/A
N/A
Protected

Academic year: 2022

Aktie "Deklarative (= fortgeschrittene) Programmierung Vorlesung WS 09,10; SS 12–14, 16"

Copied!
90
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Deklarative (= fortgeschrittene) Programmierung Vorlesung WS 09,10; SS 12–14, 16

Johannes Waldmann, HTWK Leipzig 13. Juli 2016

1 Einleitung

Formen der deklarativen Programmierung

• funktionale Programmierung:foldr (+) 0 [1,2,3]

foldr f z l = case l of

[] -> z ; (x:xs) -> f x (foldr f z xs)

• logische Programmierung:append(A,B,[1,2,3]).

append([],YS,YS).

append([X|XS],YS,[X|ZS]):-append(XS,YS,ZS).

• Constraint-Programmierung

(set-logic QF_LIA) (set-option :produce-models true) (declare-fun a () Int) (declare-fun b () Int)

(assert (and (>= a 5) (<= b 30) (= (+ a b) 20))) (check-sat) (get-value (a b))

Definition

deklarativ: jedes (Teil-)Programm/Ausdruck hat einenWert (. . . und keine weitere (versteckte)Wirkung).

Werte k¨onnen sein:

• “klassische” Daten (Zahlen, Listen, B¨aume. . . )

• Funktionen (Sinus, . . . )

(2)

Softwaretechnische Vorteile

. . . der deklarativen Programmierung

• Beweisbarkeit: Rechnen mit Programmen wie in der Mathematik mit Termen

• Sicherheit: es gibt keine Nebenwirkungen und Wirkungen sieht man bereits am Typ

• Wiederverwendbarkeit: durch Entwurfsmuster (=Funktionen h¨oherer Ordnung)

• Effizienz: durch Programmtransformationen im Compiler,

• Parallelisierbarkeit: durch Nebenwirkungsfreiheit Beispiel Spezifikation/Test

import Test.SmallCheck

append :: forall t . [t] -> [t] -> [t]

append x y = case x of [] -> y

h : t -> h : append t y associative f =

\ x y z -> f x (f y z) == f (f x y) z test1 = smallCheckI

(associative (append::[Int]->[Int]->[Int])) Ubung: Kommutativit¨at (formulieren und testen)¨

Beispiel Verifikation

app :: forall t . [t] -> [t] -> [t]

app x y = case x of [] -> y

h : t -> h : app t y Beweise

app x (app y z) == app (app x y) z Beweismethode: Induktion nachx.

• Induktionsanfang:x == []. . .

• Induktionsschritt:x == h : t. . .

(3)

Beispiel Parallelisierung (Haskell)

Klassische Implementierung von Mergesort sort :: Ord a => [a] -> [a]

sort [] = [] ; sort [x] = [x]

sort xs = let ( left,right ) = split xs sleft = sort left

sright = sort right in merge sleft sright wird parallelisiert durchAnnotationen:

sleft = sort left

‘using‘ rpar ‘dot‘ spineList sright = sort right ‘using‘ spineList

vgl.http://thread.gmane.org/gmane.comp.lang.haskell.parallel/

181/focus=202

Beispiel Parallelisierung (C#, PLINQ)

• Die Anzahl der 1-Bits einer nichtnegativen Zahl:

Func<int,int>f =

x=>{int s=0; while(x>0){s+=x%2;x/=2;}return s;}

226−1

X

x=0

f(x) Enumerable.Range(0,1<<26).Select(f).Sum()

• automatische parallele Auswertung, Laufzeitvergleich:

Time(()=>Enumerable.Range(0,1<<26).Select(f).Sum()) Time(()=>Enumerable.Range(0,1<<26).AsParallel()

.Select(f).Sum())

vgl.Introduction to PLINQhttps://msdn.microsoft.com/en-us/library/

dd997425(v=vs.110).aspx

(4)

Softwaretechnische Vorteile . . . der statischen Typisierung

The language in which you write profoundly affects the design of programs written in that language.

For example, in the OO world, many people use UML to sketch a design. In Haskell or ML, one writes type signatures instead. Much of the initial design phase of a functional program consists of writing type definitions.

Unlike UML, though, all this design is incorporated in the final product, and is machine-checked throughout.

Simon Peyton Jones, in: Masterminds of Programing, 2009; http://shop.oreilly.

com/product/9780596515171.do

Deklarative Programmierung in der Lehre

• funktionale Programmierung: diese Vorlesung

• logische Programmierung: inAngew. K¨unstl. Intell.

• Constraint-Programmierung: als Master-Wahlfach Beziehungen zu weiteren LV: Voraussetzungen

• B¨aume, Terme (Alg.+DS, Grundlagen Theor. Inf.)

• Logik (Grundlagen TI, Softwaretechnik) Anwendungen:

• Softwarepraktikum

• weitere Sprachkonzepte inPrinzipien v. Programmiersprachen

• Programmverifikation(vorw. f. imperative Programme) Konzepte und Sprachen

Funktionale Programmierung ist einKonzept. Realisierungen:

• in prozeduralen Sprachen:

– Unterprogramme als Argumente (in Pascal) – Funktionszeiger (in C)

(5)

• in OO-Sprachen: Befehlsobjekte

• Multi-Paradigmen-Sprachen:

– Lambda-Ausdr¨ucke in C#, Scala, Clojure

• funktionale Programmiersprachen (LISP, ML, Haskell) Die Erkenntnisse sind sprachunabh¨angig.

• A good programmer can write LISP in any language.

• Learn Haskell and become a better Java programmer.

Gliederung der Vorlesung

• Terme, Termersetzungssysteme algebraische Datentypen, Pattern Matching, Persi- stenz

• Funktionen (polymorph, h¨oherer Ordnung), Lambda-Kalk¨ul, Rekursionsmuster

• Typklassen zur Steuerung der Polymorphie

• Bedarfsauswertung, unendl. Datenstrukturen (Iterator-Muster)

• weitere Entwurfsmuster

• Code-Qualit¨at, Code-Smells, Refactoring Softwaretechnische Aspekte

• algebraische Datentypen, Pattern Matching, Termersetzungssysteme Scale: case class, Java: Entwurfsmuster Kompositum,

immutable objects, das Datenmodell von Git

• Funktionen (h¨oherer Ordnung), Lambda-Kalk¨ul, Rekursionsmuster Lambda-Ausdr¨ucke in C#, Entwurfsmuster Besucher

Codequalit¨at, code smells, Refaktorisierung

• Typklassen zur Steuerung der Polymorphie

Interfaces in Java/C# , automatische Testfallgenerierung

• Bedarfsauswertung, unendl. Datenstrukturen

(6)

Organisation der LV

• jede Woche eine Vorlesung, eine ¨Ubung

• Hausaufgaben (teilw. autotool)

https://autotool.imn.htwk-leipzig.de/shib/cgi-bin/Super.

cgi

Identifizierung und Authentifizierung ¨uber Shibboleth-IDP des HTWK-Rechenzentrums, wie bei OPAL

• Pr¨ufungszulassung: regelm¨aßiges (d.h. innerhalb der jeweiligen Deadline) und er- folgreiches (ingesamt≥50%der Pflichtaufgaben) Bearbeiten von ¨Ubungsaufgaben.

• Pr¨ufung: Klausur (ohne Hilfsmittel) Literatur

• Skripte:

– aktuelles Semesterhttp://www.imn.htwk-leipzig.de/˜waldmann/lehre.

html

– vorige Semesterhttp://www.imn.htwk-leipzig.de/˜waldmann/lehre-alt.

html

• Entwurfsmuster:http://www.imn.htwk-leipzig.de/˜waldmann/draft/

pub/hal4/emu/

• Maurice Naftalin und Phil Wadler:Java Generics and Collections, O’Reilly 2006

• http://haskell.org/(Sprache, Werkzeuge, Tutorials),http://book.realworldhaskell.

org/

Ubungen¨

• im Pool Z430, vgl.http://www.imn.htwk-leipzig.de/˜waldmann/etc/

pool/

• Beispiele f. deklarative Programmierung – funktional: Haskell mit ghci, – logisch: Prolog mit swipl,

(7)

– constraint: mit mathsat, z3

• Haskell-Entwicklungswerkzeuge

– (eclipsefp, leksah, . . . ,http://xkcd.org/378/)

– API-Suchmaschinehttp://www.haskell.org/hoogle/

• Commercial Uses of Functional Programminghttp://www.syslog.cl.cam.

ac.uk/2013/09/22/liveblogging-cufp-2013/

2 Daten

Wiederholung: Terme

• (Pr¨adikatenlogik)SignaturΣist Menge von Funktionssymbolen mit Stelligkeiten ein Termtin SignaturΣist

– Funktionssymbol f ∈ Σ der Stelligkeit k mit Argumenten (t1, . . . , tk), die selbst Terme sind.

Term(Σ) =Menge der Terme ¨uber SignaturΣ

• (Graphentheorie) ein Term ist ein gerichteter, geordneter, markierter Baum

• (Datenstrukturen)

– Funktionssymbol=Konstruktor, Term=Baum Beispiele: Signatur, Terme

• Signatur:Σ1 ={Z/0, S/1, f /2}

• Elemente vonTerm(Σ1):

Z(), S(S(Z())), f(S(S(Z())), Z())

• Signatur:Σ2 ={E/0, A/1, B/1}

• Elemente vonTerm(Σ2): . . .

(8)

Algebraische Datentypen

data Foo = Foo { bar :: Int, baz :: String } deriving Show

Bezeichnungen (benannte Notation)

• data Fooist Typname

• Foo { .. }ist Konstruktor

• bar, bazsind Komponenten x :: Foo

x = Foo { bar = 3, baz = "hal" } Bezeichnungen (positionelle Notation)

data Foo = Foo Int String y = Foo 3 "bar"

Datentyp mit mehreren Konstruktoren Beispiel (selbst definiert)

data T = A { foo :: Int }

| B { bar :: String, baz :: Bool } deriving Show

Bespiele (in Prelude vordefiniert) data Bool = False | True data Ordering = LT | EQ | GT Mehrsortige Signaturen

• (bisher) einsortige Signatur

Abbildung von Funktionssymbol nach Stelligkeit

• (neu) mehrsortige Signatur

– Menge von SortensymbolenS={S1, . . .}

– Abb. von F.-Symbol nach Typ

(9)

– Typist Element ausS×S

Folge der Argument-Sorten, Resultat-Sorte

Bsp.: S = {Z, B},Σ = {0 7→ ([], Z), p 7→ ([Z, Z], Z), e 7→ ([Z, Z], B), a 7→

([B, B], B)}.

• Term(Σ): konkrete Beispiele, allgemeine Definition?

Rekursive Datentypen data Tree = Leaf {}

| Branch { left :: Tree , right :: Tree } Ubung: Objekte dieses Typs erzeugen¨

(benannte und positionelle Notation der Konstruktoren) Daten mit Baum-Struktur

• mathematisches Modell: Term ¨uber Signatur

• programmiersprachliche Bezeichnung:algebraischer Datentyp (die Konstruktoren bilden eine Algebra)

• praktische Anwendungen:

– Formel-B¨aume (in Aussagen- und Pr¨adikatenlogik)

– Suchb¨aume (in VL Algorithmen und Datenstrukturen, injava.util.TreeSet<E>) – DOM (Document Object Model)https://www.w3.org/DOM/DOMTR

– JSON (Javascript Object Notation) z.B. f¨ur AJAXhttp://www.ecma-international.

org/publications/standards/Ecma-404.htm Bezeichnungen f ¨ur Teilterme

• Position: Folge von nat¨urlichen Zahlen

(bezeichnet einen Pfad von der Wurzel zu einem Knoten) Beispiel: f¨urt =S(f(S(S(Z())), Z()))

ist[0,1]eine Position int.

(10)

• Pos(t) =die Menge der Positionen eines Termst Definition: wennt=f(t1, . . . , tk),

dannPos(t) = {[]} ∪ {[i−1]++p|1≤i≤k∧p∈Pos(ti)}.

dabei bezeichnen:

• []die leere Folge,

• [i]die Folge der L¨ange 1 mit Elementi,

• ++den Verkettungsoperator f¨ur Folgen Operationen mit (Teil)Termen

• t[p] =der Teilterm vontan Positionp Beispiel:S(f(S(S(Z())), Z()))[0,1] =. . .

Definition (durch Induktion ¨uber die L¨ange vonp): . . .

• t[p:=s]: wiet, aber mit Termsan Positionp

Beispiel:S(f(S(S(Z())), Z()))[[0,1] :=S(Z)]x=. . . Definition (durch Induktion ¨uber die L¨ange vonp): . . . Operationen mit Variablen in Termen

• Term(Σ, V) =Menge der Terme ¨uber SignaturΣmit Variablen ausV Beispiel:Σ ={Z/0, S/1, f /2}, V ={y},f(Z(), y)∈Term(Σ, V).

• Substitutionσ: partielle AbbildungV →Term(Σ) Beispiel:σ1 ={(y, S(Z()))}

• eine Substitution auf einen Term anwenden:tσ:

Intuition: wiet, aber stattv immerσ(v) Beispiel:f(Z(), y)σ1 =f(Z(), S(Z())) Definition durch Induktion ¨ubert

(11)

Termersetzungssysteme

• Daten=Terme (ohne Variablen)

• ProgrammR=Menge von Regeln

Bsp:R={(f(Z(), y), y), (f(S(x), y), S(f(x, y)))}

• Regel=Paar(l, r)von Termen mit Variablen

• Relation→Rist Menge aller Paare(t, t0)mit – es existiert(l, r)∈R

– es existiert Positionpint

– es existiert Substitutionσ: (Var(l)∪Var(r))→Term(Σ) – so daßt[p] =lσundt0 =t[p:=rσ].

Termersetzungssysteme als Programme

• →RbeschreibteinenSchritt der Rechnung vonR,

• transitive und reflexive H¨ulle→R beschreibtFolgevon Schritten.

• Resultateiner Rechnung ist Term inR-Normalform (:=ohne→R-Nachfolger) dieses Berechnungsmodell ist im allgemeinen

• nichtdeterministischR1 ={C(x, y)→x, C(x, y)→y}

(ein Term kann mehrere→R-Nachfolger haben, ein Term kann mehrere Normalfor- men erreichen)

• nicht terminierendR2 ={p(x, y)→p(y, x)}

(es gibt eine unendliche Folge von→R-Schritten, es kann Terme ohne Normalform geben)

Konstruktor-Systeme

F¨ur TRSR ¨uber SignaturΣ: Symbols∈Σheißt

• definiert, wenn∃(l, r)∈R :l[] =s(. . .)(das Symbol in der Wurzel ists)

• sonstKonstruktor.

Das TRSRheißtKonstruktor-TRS, falls:

(12)

• definierte Symbole kommen linksnurin den Wurzeln vor Ubung: diese Eigenschaft formal spezifizieren¨

Beispiele:R1 ={a(b(x))→b(a(x))} ¨uberΣ1 ={a/1, b/1}, R2 ={f(f(x, y), z)→f(x, f(y, z)) ¨uberΣ2 ={f /2}:

definierte Symbole? Konstruktoren? Konstruktor-System?

Funktionale Programme sind ¨ahnlich zu Konstruktor-TRS.

Ubung Terme, TRS¨

• Geben Sie die Signatur des Terms√

a·a+b·ban.

• Geben Sie ein Elementt∈Term({f /1, g/3, c/0})an mitt[1] = c().

mitghci:

• data T = F T | G T T T | C deriving Show erzeugen Sie o.g. Terme (durch Konstruktoraufrufe)

DieGr¨oßeeines Termstist definiert durch

|f(t1, . . . , tk)|= 1 +Pk i=1|ti|.

• Bestimmen Sie|√

a·a+b·b|.

• Beweisen Sie∀Σ :∀t ∈Term(Σ) :|t|=|Pos(t)|.

Vervollst¨andigen Sie die Definition derTiefevon Termen:

depth(f()) = 0

k >0 ⇒ depth(f(t1, . . . , tk)) = . . .

• Bestimmen Siedepth(√

a·a+b·b)

• Beweisen Sie∀Σ :∀t ∈Term(Σ) : depth(t)<|t|.

F¨ur die SignaturΣ ={Z/0, S/1, f /2}:

• f¨ur welche Substitutionσ giltf(x, Z)σ =f(S(Z), Z)?

(13)

• f¨ur diesesσ: bestimmen Sief(x, S(x))σ.

Notation f¨ur Termersetzungsregeln: anstatt(l, r)schreibel→r.

Abk¨urzung f¨ur Anwendung von 0-stelligen Symbolen: anstattZ()schreibeZ.

• F¨urR={f(S(x), y)→f(x, S(y)), f(Z, y)→y}

bestimme alleR-Normalformen vonf(S(Z), S(Z)).

• f¨urRd =R∪ {d(x)→f(x, x)}

bestimme alleRd-Normalformen vond(d(S(Z))).

• Bestimme die SignaturΣdvonRd.

Bestimme die Menge der Terme ausTerm(Σd), dieRd-Normalformen sind.

• f¨ur die Signatur{A/2, D/0}:

definiere Termet0 =D, ti+1=A(ti, D).

Zeichnet3. Bestimme|ti|.

• f¨urS ={A(A(D, x), y)→A(x, A(x, y))}

bestimmeS-Normalform(en), soweit existieren, der Termet2, t3, t4. Zusatz: vonti allgemein.

Abk¨urzung f¨ur mehrfache Anwendung eines einstelligen Symbols:A(A(A(A(x)))) = A4(x)

• f¨ur{A(B(x))→B(A(x))}

¨uber Signatur{A/1, B/1, E/0}:

bestimme Normalform vonAk(Bk(E)) f¨urk = 1,2,3,allgemein.

• f¨ur{A(B(x))→B(B(A(x)))}

¨uber Signatur{A/1, B/1, E/0}:

bestimme Normalform vonAk(B(E)) f¨urk = 1,2,3,allgemein.

(14)

3 Programme

Funktionale Programme

. . . sind spezielle Term-Ersetzungssysteme. Beispiel:

Signatur:S einstellig,Z nullstellig,f zweistellig.

Ersetzungssystem{f(Z, y)→y, f(S(x0), y)→S(f(x0, y))}.

Starttermf(S(S(Z)), S(Z)).

entsprechendes funktionales Programm:

data N = Z | S N f :: N -> N -> N f x y = case x of

{ Z -> y ; S x’ -> S (f x’ y) } Aufruf:f (S (S Z)) (S Z)

Auswertung=Folge von Ersetzungsschritten→R Resultat=Normalform (hat keine

R-Nachfolger) Pattern Matching

data Tree = Leaf | Branch Tree Tree size :: Tree -> Int

size t = case t of { ... ; Branch l r -> ... }

• Syntax allgemein:case t of { <Muster> -> <Ausdruck> ; ... }

• <Muster>enth¨alt Konstruktoren und Variablen, entspricht linker Seite einer Term- Ersetzungs-Regel,<Ausdruck>entspricht rechter Seite

• Def.:tpaßtzum Musterl: es existiertσmitlσ=t

• dynamische Semantik: f¨ur das erste passende Muster wirdrσausgewertet

• statische Semantik: jedes<Muster> hat gleichen Typ wiet, alle <Ausdruck>

haben ¨ubereinstimmenden Typ.

Eigenschaften von Case-Ausdr ¨ucken eincase-Ausdruck heißt

• disjunkt, wenn die Muster nicht ¨uberlappen

(es gibt keinen Term, der zu mehr als 1 Muster paßt)

• vollst¨andig, wenn die Muster den gesamten Datentyp abdecken (es gibt keinen Term, der zu keinem Muster paßt)

(15)

Bespiele (f¨urdata N = F N N | S N | Z) -- nicht disjunkt:

case t of { F (S x) y -> .. ; F x (S y) -> .. } -- nicht vollst¨andig:

case t of { F x y -> .. ; Z -> .. } dataundcase

typisches Vorgehen beim Verarbeiten algebraischer Daten vom TypT:

• F¨ur jeden Konstruktor des Datentyps data T = C1 ...

| C2 ...

• schreibe einen Zweig in der Fallunterscheidung f x = case x of

C1 ... -> ...

C2 ... -> ...

• Argumente der Konstruktoren sind Variablen ⇒ Case-Ausdruck ist disjunkt und vollst¨andig.

Peano-Zahlen

data N = Z | S N plus :: N -> N -> N plus x y = case x of

Z -> y

S x’ -> S (plus x’ y) Aufgaben:

• implementiere Multiplikation, Potenz

• beweise die ¨ublichen Eigenschaften (Addition, Multiplikation sind assoziativ, kom-

(16)

Pattern Matching in versch. Sprachen

• Scala: case classes http://docs.scala-lang.org/tutorials/tour/

case-classes.html

• C# (7):https://github.com/dotnet/roslyn/blob/features/patterns/

docs/features/patterns.md

• Javascript?

Nicht verwechseln mit regular expression matchingzur String-Verarbeitung. Es geht um algebraische (d.h. baum-artige) Daten!

Ubung Pattern Matching, Programme¨

• F¨ur die Deklarationen

-- data Bool = False | True (aus Prelude) data T = F T | G T T T | C

entscheide/bestimme f¨ur jeden der folgenden Ausdr¨ucke:

– syntaktisch korrekt?

– statisch korrekt?

– Resultat (dynamische Semantik) – disjunkt? vollst¨andig?

1. case False of { True -> C } 2. case False of { C -> True } 3. case False of { False -> F F }

4. case G (F C) C (F C) of { G x y z -> F z } 5. case F C of { F (F x) -> False }

6. case F C of { F x -> False ; True -> False } 7. case True of { False -> C ; True -> F C } 8. case True of { False -> C ; False -> F C }

9. case C of { G x y z -> False; F x -> False; C -> True }

• Operationen auf Wahrheitswerten:

(17)

import qualified Prelude

data Bool = False | True deriving Prelude.Show not :: Bool -> Bool -- Negation

not x = case x of ... -> ...

... -> ...

Syntax: wenn nach of kein { folgt: implizite { ; } durch Abseitsregel (layout rule).

• (&&) :: Bool -> Bool -> Bool x && y = case ... of ...

Syntax: Funktionsname

– beginnt mit Buchstabe: steht vor Argumenten,

– beginnt mit Zeichen: zwischen Argumenten (als Operator)

Operator als Funktion:(&&) False True, Funktion als Operator:True ‘f‘ False.

• Listen von Wahrheitswerten:

data List = Nil | Cons Bool List deriving Prelude.Show and :: List -> Bool

and l = case l of ...

entsprechendor :: List -> Bool

• (Wdhlg.) welche Signatur beschreibt bin¨are B¨aume

(jeder Knoten hat 2 oder 0 Kinder, die B¨aume sind; es gibt keine Schl¨ussel)

• geben Sie die dazu ¨aquivalentedata-Deklaration an:data T = ...

• implementieren Sie daf¨ur die Funktionen size :: T -> Prelude.Int depth :: T -> Prelude.Int

benutzePrelude.+(das ist Operator),Prelude.min, Prelude.max

• f¨ur Peano-Zahlendata N = Z | S N implementieren Sieplus, mal, min, max

(18)

4 Polymorphie

Definition, Motivation

• Beispiel: bin¨are B¨aume mit Schl¨ussel vom Type data Tree e = Leaf

| Branch (Tree e) e (Tree e) Branch Leaf True Leaf :: Tree Bool

Branch Leaf 42 Leaf :: Tree Int

• Definition:

ein polymorpher Datentyp ist einTypkonstruktor (=eine Funktion, die Typen auf einen Typ abbildet)

• unterscheide:Treeist der Typkonstruktor,Branchist ein Datenkonstruktor Beispiele f. Typkonstruktoren (I)

• Kreuzprodukt:

data Pair a b = Pair a b

• disjunkte Vereinigung:

data Either a b = Left a | Right b

• data Maybe a = Nothing | Just a

• Haskell-Notation f¨ur Produkte:

(1,True)::(Int,Bool) f¨ur0,2,3, . . . Komponenten Beispiele f. Typkonstruktoren (II)

• bin¨are B¨aume

data Bin a = Leaf

| Branch (Bin a) a (Bin a)

• Listen

(19)

data List a = Nil

| Cons a (List a)

• B¨aume

data Tree a = Node a (List (Tree a)) Polymorphe Funktionen

Beispiele:

• Spiegeln einer Liste:

reverse :: forall e . List e -> List e

• Verketten von Listen mit gleichem Elementtyp:

append :: forall e . List e -> List e -> List e

Knotenreihenfolge eines Bin¨arbaumes:

preorder :: forall e . Bin e -> List e

Def: der Typ einer polymorphen Funktion beginnt mit All-Quantoren f¨ur Typvariablen.

Bsp: Datenkonstruktoren polymorpher Typen.

Bezeichnungen f. Polymorphie

data List e = Nil | Cons e (List e)

• Listist einTypkonstruktor

• List eist einpolymorpherTyp (ein Typ-Ausdruck mitTyp-Variablen)

• List Boolist einmonomorpherTyp

(entsteht durchInstantiierung: Substitution der Typ-Variablen durch Typen)

• polymorphe Funktion:reverse:: forall e . List e -> List e monomorphe Funktion:xor:: List Bool -> Bool

polymorphe Konstante:Nil::forall e. List e

(20)

Operationen auf Listen (I)

data List a = Nil | Cons a (List a)

• append xs ys = case xs of

Nil ->

Cons x xs’ ->

• Ubung: formuliere und beweise:¨ appendist assoziativ.

• reverse xs = case xs of

Nil ->

Cons x xs’ ->

• beweise:

forall xs . reverse (reverse xs) == xs Operationen auf Listen (II)

Die vorige Implementierung von reverse ist (f¨ur einfach verkettete Listen) nicht effizient.

Besser ist:

reverse xs = rev_app xs Nil mit Spezifikation

rev_app xs ys = append (reverse xs) ys Ubung: daraus die Implementierung von¨ rev_appableiten rev_app xs ys = case xs of ...

Operationen auf B¨aumen

data List e = Nil | Cons e (List e)

data Bin e = Leaf | Branch (Bin e) e (Bin e) Knotenreihenfolgen

• preorder :: forall e . Bin e -> List e preorder t = case t of ...

(21)

• entsprechendinorder,postorder

• und Rekonstruktionsaufgaben

Adressierug von Knoten (False=links,True=rechts)

• get :: Tree e -> List Bool -> Maybe e

• positions :: Tree e -> List (List Bool) Ubung Polymorphie¨

Geben Sie alle Elemente dieser Datentypen an:

• Maybe ()

• Maybe (Bool, Maybe ())

• Either (Bool,Bool) (Maybe (Maybe Bool)) Operationen auf Listen:

• append, reverse, rev app Operationen auf B¨aumen:

• preorder, inorder, postorder, (Rekonstruktion)

• get, (positions)

Kochrezept: Objektkonstruktion

Aufgabe (Bsp):x :: Either (Maybe ()) (Pair Bool ()) L¨osung (Bsp):

• der TypEither a bhat KonstruktorenLeft a | Right b. W¨ahleRight b.

Die Substitution f¨ur die Typvariablen ista = Maybe (), b = Pair Bool ().

x = Right ymity :: Pair Bool ()

• der TypPair a bhat KonstruktorPair a b.

die Substitution f¨ur diese Typvariablen ista = Bool, b = ().

y = Pair p qmitp :: Bool, q :: ()

(22)

• der Typ Boolhat Konstruktoren False | True, w¨ahle p = False. der Typ ()hat Konstruktor(), alsoq=()

Insgesamtx = Right y = Right (Pair False ()) Vorgehen (allgemein)

• bestimme den Typkonstruktor

• bestimme die Substitution f¨ur die Typvariablen

• w¨ahle einen Datenkonstruktor

• bestimme Anzahl und Typ seiner Argumente

• w¨ahle Werte f¨ur diese Argumente nach diesem Vorgehen.

Kochrezept: Typ-Bestimmung

Aufgabe (Bsp.) bestimme Typ vonx(erstes Arg. vonget):

at :: Position -> Tree a -> Maybe a at p t = case t of

Node f ts -> case p of Nil -> Just f

Cons x p’ -> case get x ts of Nothing -> Nothing

Just t’ -> at p’ t’

L¨osung:

• bestimme das Muster, durch welchesxdeklariert wird.

L¨osung:Cons x p’ ->

• bestimme den Typ diese Musters

L¨osung: ist gleich dem Typ der zugeh¨origenDiskriminantep

• bestimme das Muster, durch daspdeklariert wird L¨osung:at p t =

• bestimme den Typ vonp

L¨osung: durch Vergleich mit Typdeklaration von at (p ist das erste Argument) p :: Position, alsoCons x p’ :: Position = List N, alsox :: N.

Vorgehen zur Typbestimmung eines Namens:

(23)

• finde die Deklaration (Muster einer Fallunterscheidung oder einer Funktionsdefini- tion)

• bestimme den Typ des Musters (Fallunterscheidung: Typ der Diskriminante, Funk- tion: deklarierter Typ)

Statische Typisierung und Polymorphie Def: dynamische Typisierung:

• die Daten (zur Laufzeit des Programms, im Hauptspeicher) haben einen Typ Def: statische Typisierung:

• Bezeichner, Ausdr¨ucke (im Quelltext) haben einen Type (zur ¨Ubersetzungszeit be- stimmt).

• f¨ur jede Ausf¨uhrung des Programms gilt: der statische Typ eines Ausdrucks ist gleich dem dynamischen Typ seines Wertes

(24)

Bsp. f¨ur Programm ohne statischen Typ (Javascript) function f (x) {

if (x>0) { return function () { return 42; } } else { return "foobar"; } }

Dann: Auswertung von f(1)() ergibt 42, Auswertung von f(0)() ergibt Laufzeit- Typfehler.

entsprechendes Haskell-Programm ist statisch fehlerhaft f x = case x > 0 of

True -> \ () -> 42 False -> "foobar"

(25)

Nutzen der statischen Typisierung:

• beim Programmieren: Entwurfsfehler werden zu Typfehlern, diese werden zur Ent- wurfszeit automatisch erkannt⇒fr¨uher erkannte Fehler lassen sich leichter beheben

• beim Ausf¨uhren: es gibt keine Lauzeit-Typfehler⇒keine Typpr¨ufung zur Laufzeit n¨otig, effiziente Ausf¨uhrung

Nutzen der Polymorphie:

• Flexibilit¨at, nachnutzbarer Code, z.B. Anwender einer Collection-Bibliothek legt Element-Typ fest (Entwickler der Bibliothek kennt den Element-Typ nicht)

• gleichzeitig bleibt statische Typsicherheit erhalten Von der Spezifikation zur Implementierung (I)

Bsp: Addition von Peano-Zahlendata N = Z | S N plus :: N -> N -> N

aus der Typdeklaration wird abgeleitet:

plus x y = case x of Z ->

S x’ ->

erster Zweig:plus Z y = 0 + y = y

zweiter Zweig :plus (S x’) y = (1 + x’) + y=

mit Assoziativit¨at von+gilt... = 1 + (x’ + y) = S (plus x’ y) Bsp. ( ¨U): Multiplikation. Hinweis: benutze Distributivgesetz.

Von der Spezifikation zur Implementierung (II)

Bsp: homogene Listendata List a = Nil | Cons a (List a) Aufgabe: implementieremaximum :: List N -> N

Spezifikation:

maximum (Cons x1 Nil) = x1

maximum (append xs ys) = max (maximum xs) (maximum ys)

• substitutierexs = Nil, erhalte

(26)

maximum (append Nil ys) = maximum ys

= max (maximum Nil) (maximum ys)

d.h.maximum Nil sollte das neutrale Element f¨urmax (auf nat¨urlichen Zahlen) sein, also0(geschriebenZ).

• substitutierexs = Cons x1 Nil, erhalte

maximum (append (Cons x1 Nil) ys)

= maximum (Cons x1 ys)

= max (maximum (Cons x1 Nil)) (maximum ys)

= max x1 (maximum ys)

Damit kann der aus dem Typ abgeleitete Quelltext maximum :: List N -> N

maximum xs = case xs of

Nil ->

Cons x xs’ ->

erg¨anzt werden.

Vorsicht: f¨ur min, minimum funktioniert das nicht so, denn min hat f¨ur N kein neutrales Element.

5 Unver¨anderliche Objekte

Uberblick¨

• alle Attribute aller Objekte sind unver¨anderlich (final)

• anstatt Objekt zu ¨andern, konstruiert man ein neues Eigenschaften des Programmierstils:

• vereinfacht Formulierung und Beweis von Objekteigenschaften

• parallelisierbar (keine updates, keinedata races)

http://fpcomplete.com/the-downfall-of-imperative-programming/

• Persistenz (Verf¨ugbarkeit fr¨uherer Versionen)

• Belastung des Garbage Collectors (. . . daf¨ur ist er da)

(27)

Beispiel: Einf ¨ugen in Baum

• destruktiv:

interface Tree<K> { void insert (K key); } Tree<String> t = ... ;

t.insert ("foo");

• persistent (Java):

interface Tree<K> { Tree<K> insert (K key); } Tree<String> t = ... ;

Tree<String> u = t.insert ("foo");

• persistent (Haskell):

insert :: Tree k -> k -> Tree k Beispiel: (unbalancierter) Suchbaum

data Tree k = Leaf

| Branch (Tree k) k (Tree k) insert :: Ord k => k -> Tree k -> Tree k insert k t = case t of ...

Diskussion:

• Ord kentsprichtK implements Comparable<K>, genaueres sp¨ater (Haskell-Typklassen)

• wie teuer ist die Persistenz?

(wieviel M¨ull entsteht bei eineminsert?)

(28)

Beispiel: Sortieren mit Suchb¨aumen data Tree k = Leaf

| Branch (Tree k) k (Tree k) insert :: Ord k => k -> Tree k -> Tree k build :: Ord k => [k] -> Tree k

build = foldr ... ...

sort :: Ord k => [k] -> [k]

sort xs = ... ( ... xs ) Persistente Objekte in Git

http://git-scm.com/

• Distributeddevelopment.

• Strong support fornon-lineardevelopment.

(Branching and merging are fast and easy.)

• Efficient handling oflargeprojects.

(z. B. Linux-Kernel,http://kernel.org/)

• Toolkit design.

• Cryptographic authentication of history.

Objekt-Versionierung in Git

• Objekt-Typen:

– Datei (blob),

– Verzeichnis (tree), mit Verweisen auf blobs und trees – Commit, mit Verweisen auf tree u. commits (Vorg¨anger) git cat-file -p <hash>

• Objekte sind unver¨anderlich und durch SHA1-Hash (160 bit = 40 Hex-Zeichen) identifiziert

(29)

• statt ¨Uberschreiben: neue Objekte anlegen

• jeder Zustand ist durch Commit-Hash (weltweit) eindeutig beschrieben und kann wiederhergestellt werden

Quelltexte zur Vorlesung:https://gitlab.imn.htwk-leipzig.de/waldmann/

fop-ss16

6 Funktionen

Funktionen als Daten bisher:

f :: Int -> Int f x = 2 * x + 5

¨aquivalent: Lambda-Ausdruck f = \ x -> 2 * x + 5

Lambda-Kalk¨ul: Alonzo Church 1936, Henk Barendregt 198*, . . . Funktionsanwendung:

(\ x -> B) A = B [x := A]

ist nur erlaubt, falls keine inAfreie Variable durch ein Lambda inBgebunden wird.

Der Lambda-Kalk ¨ul

. . . als weiteres Berechnungsmodell,

(vgl. Termersetzungssysteme, Turingmaschine, Random-Access-Maschine) Syntax: die Menge der Lambda-TermeΛist

• jede Variable ist ein Term:v ∈V ⇒v ∈Λ

• Funktionsanwendung (Applikation):

F ∈Λ, A∈Λ ⇒(F A)∈Λ

• Funktionsdefinition (Abstraktion):

v ∈V, B ∈Λ⇒(λv.B)∈Λ Semantik: eine Relation→β aufΛ

(30)

Freie und gebundene Variablen(vorkommen)

• Das Vorkommen vonv ∈ V an Positionpin Termtheißtfrei, wenn

”dar¨uber kein λv. . . . steht“

• Def.fvar(t) =Menge der intfrei vorkommenden Variablen (definiere durch struk- turelle Induktion)

• Eine Variablexheißt inAgebunden, fallsAeinen Teilausdruckλx.B enth¨alt.

• Def.bvar(t) =Menge der intgebundenen Variablen Bsp:fvar(x(λx.λy.x)) = {x},bvar(x(λx.λy.x)) ={x, y}, Semantik des Lambda-Kalk ¨uls: Reduktion→β

Relation→β aufΛ(ein Reduktionsschritt) Es giltt→β t0, falls

• ∃p∈Pos(t), so daß

• t[p] = (λx.B)Amitbvar(B)∩fvar(A) = ∅

• t0 =t[p:=B[x:=A]]

dabei bezeichnetB[x:=A]ein Kopie vonB, bei der jedes freie Vorkommen vonx durchAersetzt ist

Ein (Teil-)Ausdruck der Form(λx.B)A heißtRedex. (Dort kann weitergerechnet wer- den.)

Ein Term ohne Redex heißtNormalform. (Normalformen sind Resultate von Rech- nungen.)

Semantik . . . : gebundene Umbenennung→α

• Relation→αaufΛ, beschreibtgebundene Umbenennungeiner lokalen Variablen.

• Beispielλx.f xz→α λy.f yz.

(f undzsind frei, k¨onnen nicht umbenannt werden)

• Definitiont→α t0:

– ∃p∈Pos(t), so daßt[p] = (λx.B) – y /∈bvar(B)∪fvar(B)

(31)

– t0 =t[p:=λy.B[x:=y]]

• wird angewendet, umbvar(B)∩fvar(A) =∅in Regel f¨ur→β zu erf¨ullen.

Bsp: betrachte den unterstrichenen Redex in (λx.((λf.(λx.(x+f8)))(λy.(x+y))))3

Umbenennung von lokalen Variablen int x = 3;

int f(int y) { return x + y; } int g(int x) { return (x + f(8)); } // g(5) => 16

Darff(8)ersetzt werden durchf[y:= 8]? - Nein:

int x = 3;

int g(int x) { return (x + (x+8)); } // g(5) => 18

Das freiexin(x+y)wird f¨alschlich gebunden.

L¨osung: lokal umbenennen

int g(int z) { return (z + f(8)); } dann ist Ersetzung erlaubt

int x = 3;

int g(int z) { return (z + (x+8)); } // g(5) => 16

Lambda-Terme: verk ¨urzte Notation

• Applikation ist links-assoziativ, Klammern weglassen:

(. . .((F A1)A2). . . An)∼F A1A2. . . An Beispiel:((xz)(yz))∼xz(yz)

Wirkt auch hinter dem Punkt: (λx.xx)bedeutet(λx.(xx))— und nicht((λx.x)x)

• geschachtelte Abstraktionen unter ein Lambda schreiben:

(λx1.(λx2. . . .(λxn.B). . .))∼λx1x2. . . xn.B Beispiel:λx.λy.λz.B ∼λxyz.B

(32)

Ein- und mehrstellige Funktionen

eine einstellige Funktion zweiter Ordnung:

f = \ x -> ( \ y -> ( x*x + y*y ) ) Anwendung dieser Funktion:

(f 3) 4 = ...

Kurzschreibweisen (Klammern weglassen):

f = \ x y -> x * x + y * y ; f 3 4 Ubung:¨

gegebent = \ f x -> f (f x)

bestimmet succ 0, t t succ 0, t t t succ 0, t t t t succ 0, ...

Typen

f¨ur nicht polymorphe Typen: tats¨achlicher Argumenttyp muß mit deklariertem Argu- menttyp ¨ubereinstimmen:

wennf ::A→Bundx::A, dann(f x) ::B.

bei polymorphen Typen k¨onnen der Typ von f :: A → B und der Typ von x :: A0 Typvariablen enthalten.

Beispiel:λx.x::∀t.t→t.

Dann m¨ussenAundA0 nicht ¨ubereinstimmen, sondern nurunifizierbarsein (eine ge- meinsame Instanz besitzen).

Beispiel:(λx.x)True

benutze Typ-Substitutionσ ={(t,Bool)}.

Bestimme allgemeinsten Typ vont =λf x.f(f x)), von(tt).

Beispiel f ¨ur Typ-Bestimmung

Aufgabe: bestimme den allgemeinsten Typ vonλf x.f(f x)

• Ansatz mit Typvariablenf ::t1, x::t2

• betrachte(f x): der Typ vonf muß ein Funktionstyp sein, alsot1 = (t11→t12)mit neuen Variablent11, t12. Dann giltt11 =t2 und(f x) ::t12.

• betrachtef(f x). Wir habenf ::t11→t12und(f x) ::t12, also folgtt11=t12. Dann f(f x) ::t12.

(33)

• betrachteλx.f(f x). Ausx::t12undf(f x) ::t12folgtλx.f(f x) ::t12→t12.

• betrachte λf.(λx.f(f x)). Aus f :: t12 → t12 und λx.f(f x) :: t12 → t12 folgt λf x.f(f x) :: (t12→t12)→(t12→t12)

Verk ¨urzte Notation f ¨ur Typen

• Der Typ-Pfeil istrechts-assoziativ:

T1 →T2 → · · · →Tn →T bedeutet(T1 →(T2 → · · · →(Tn →T)· · ·))

• das paßt zu den Abk¨urzungen f¨ur mehrstellige Funktionen:

λ(x::T1).λ(x::T2).(B ::T) hat den Typ(T1 →(T2 →B)), mit o.g. Abk¨urzungT1 →T2 →T. Lambda-Ausdr ¨ucke in C#

• Beispiel (Fkt. 1. Ordnung)

Func<int,int> f = (int x) => x*x;

f (7);

• Ubung (Fkt. 2. Ordnung) — erg¨anze alle Typen:¨

??? t = (??? g) => (??? x) => g (g (x));

t (f)(3);

• Anwendungen bei Streams, sp¨ater mehr

(new int[]{3,1,4,1,5,9}).Select(x => x * 2);

(new int[]{3,1,4,1,5,9}).Where(x => x > 3);

• Ubung: Diskutiere statische/dynamische Semantik von¨ (new int[]{3,1,4,1,5,9}).Select(x => x > 3);

(new int[]{3,1,4,1,5,9}).Where(x => x * 2);

(34)

Lambda-Ausdr ¨ucke in Java(8)

funktionalesInterface (FI): hat genau eine Methode Lambda-Ausdruck (

”burger arrow“) erzeugt Objekt einer anonymen Klasse, die FI im- plementiert.

interface I { int foo (int x); } I f = (x)-> x+1;

System.out.println (f.foo(8));

vordefinierte FIs:

import java.util.function.*;

Function<Integer,Integer> g = (x)-> x*2;

System.out.println (g.apply(8));

Predicate<Integer> p = (x)-> x > 3;

if (p.test(4)) { System.out.println ("foo"); } Lambda-Ausdr ¨ucke in Javascript

$ node

> var f = function (x){return x+3;}

undefined

> f(4) 7

Beispiele Fkt. h¨oherer Ord.

• Haskell-Notation f¨ur Listen:

data List a = Nil | Cons a (List a) data [a] = [] | a : [a]

• Verarbeitung von Listen:

filter :: (a -> Bool) -> [a] -> [a]

takeWhile :: (a -> Bool) -> [a] -> [a]

partition :: (a -> Bool) -> [a] -> ([a],[a])

• Vergleichen, Ordnen:

nubBy :: (a -> a -> Bool) -> [a] -> [a]

data Ordering = LT | EQ | GT minimumBy

:: (a -> a -> Ordering) -> [a] -> a

(35)

Ubung Lambda-Kalk ¨ul¨

• Wiederholung: konkrete Syntax, abstrakte Syntax, Semantik

• S=λxyz.xz(yz), K =λab.a, Normalform vonSKKc

• (mitdata N=Z|S N) bestimme Normalform vonttSZ f¨urt=λf x.f(f x),

• definiereΛals algebraischen Datentypdata L = ...(3 Konstruktoren) implementieresize :: L -> Int,depth :: L -> Int.

implementierebvar :: L -> S.Set String,fvar :: L -> S.Set String, siehe Folie mit Definitionen und dort angegebene Testf¨alle

benutzeimport qualified Data.Set as S, API-Dokumentation:https:

//hackage.haskell.org/package/containers/docs/Data-Set.html

• autotool-Aufgaben Lambda-Kalk¨ul Ubung Fkt. h¨oherer Ordnung¨

• Typisierung, Beispiele in Haskell, C#, Java, Javascript compose ::

compose = \ f g -> \ x -> f (g x)

• Implementierung von takeWhile, dropWhile

7 Rekursionsmuster

Rekursion ¨uber B¨aume (Beispiele) data Tree a = Leaf

| Branch (Tree a) a (Tree a) summe :: Tree Int -> Int

summe t = case t of Leaf -> 0

Branch l k r -> summe l + k + summe r preorder :: Tree a -> List a

preorder t = case t of Leaf -> Nil

Branch l k r ->

(36)

Rekursion ¨uber B¨aume (Schema) f :: Tree a -> b

f t = case t of Leaf -> ...

Branch l k r -> ... (f l) k (f r) dieses Schemaisteine Funktion h¨oherer Ordnung:

fold :: ( ... ) -> ( ... ) -> ( Tree a -> b ) fold leaf branch = \ t -> case t of

Leaf -> leaf Branch l k r ->

branch (fold leaf branch l) k (fold leaf branch r)

summe = fold 0 ( \ l k r -> l + k + r ) Rekursion ¨uber Listen

and :: List Bool -> Bool and xs = case xs of

Nil -> True ; Cons x xs’ -> x && and xs’

length :: List a -> N length xs = case xs of

Nil -> Z ; Cons x xs’ -> S (length xs’) fold :: b -> ( a -> b -> b ) -> List a -> b fold nil cons xs = case xs of

Nil -> nil

Cons x xs’ -> cons x ( fold nil cons xs’ ) and = fold True (&&)

length = fold Z ( \ x y -> S y) Rekursionsmuster (Prinzip)

ein Rekursionsmuster anwenden = jeden Konstruktor durch eine passende Funktion ersetzen.

data List a = Nil | Cons a (List a)

fold ( nil :: b ) ( cons :: a -> b -> b ) :: List a -> b

(37)

Rekursionsmuster instantiieren = (Konstruktor-)Symbole interpretieren (durch Funktio- nen)=eine Algebra angeben.

length = fold Z ( \ _ l -> S l ) reverse = fold Nil ( \ x ys -> ) Rekursionsmuster (Merks¨atze)

aus dem Prinzipein Rekursionsmuster anwenden=jeden Konstruktor durch eine pas- sende Funktion ersetzenfolgt:

• Anzahl der Muster-Argumente=Anzahl der Konstruktoren (plus eins f¨ur das Da- tenargument)

• Stelligkeit eines Muster-Argumentes=Stelligkeit des entsprechenden Konstruktors

• Rekursion im Typ⇒Rekursion im Muster (Bsp: zweites Argument vonCons)

• zu jedem rekursiven Datentyp gibt esgenau einpassendes Rekursionsmuster Rekursion ¨uber Listen ( ¨Ubung)

das vordefinierte Rekursionsschema ¨uber Listen ist:

foldr :: (a -> b -> b) -> b -> ([a] -> b) length = foldr ( \ x y -> 1 + y ) 0

Beachte:

• Argument-Reihenfolge (erst cons, dann nil)

• foldr nicht mit foldl verwechseln (foldr ist das

”richtige“) Aufgaben:

• append, reverse, concat, inits, tails mitfoldr(d. h., ohne Re- kursion)

(38)

Weitere Beispiele f ¨ur Folds

data Tree a = Leaf a | Branch (Tree a) (Tree a) fold :: ...

• Anzahl der Bl¨atter

• Anzahl der Verzweigungsknoten

• Summe der Schl¨ussel

• die Tiefe des Baumes

• der gr¨oßte Schl¨ussel

Rekursionsmuster (Peano-Zahlen)

data N = Z | S N fold :: ...

fold z s n = case n of Z ->

S n’ ->

plus = fold ...

times = fold ...

Ubung Rekursionsmuster¨

• Rekursionsmuster foldr f¨ur Listen benutzen (filter, takeWhile, append, reverse, concat, inits, tails)

• Rekursionmuster f¨ur Peano-Zahlen hinschreiben und benutzen (plus, mal, hoch, Nachfolger, Vorg¨anger, minus)

• Rekursionmuster f¨ur bin¨are B¨aume mit Schl¨usselnnur in den Bl¨atternhinschreiben und benutzen

• Rekursionmuster f¨ur bin¨are B¨aume mit Schl¨usselnnur in den Verzweigungsknoten benutzen f¨ur rekursionslose Programme f¨ur:

(39)

– Anzahl der Branch-Knoten ist ungerade (nicht z¨ahlen!) – Baum (Tree a) erf¨ullt die AVL-Bedingung

Hinweis: als Projektion auf die erste Komponente eines fold, das Paar von Bool(ist AVL-Baum) undInt(H¨ohe) berechnet.

– Baum (Tree Int) ist Suchbaum (ohneinorder) Hinweis: als Projektion. Bestimme geeignete Hilfsdaten.

• Wende die Vorschrift zur Konstruktion des Rekursionsmusters an auf den Typ – Bool

– Maybe a Jeweils:

– Typ und Implementierung – Testf¨alle

– gibt es diese Funktion bereits? Suche nach dem Typ mit https://www.

stackage.org/lts-5.17/hoogle

8 Objektorientierte Entwurfmuster

Definition, Geschichte

• Ziel: flexibel wiederverwendbarer sicherer Quelltext

• L¨osung:Funktionen h¨oherer Ordnung

• Simulation davon im OO-Paragidma:Entwurfsmuster

wir wollen: Funktion als Datum (z.B. Lambda-Ausdruck), wir konstruieren: Objekt, das zu einer (anonymen) Klasse geh¨ort, die diese Funktion als Methode enth¨alt.

• Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides:Entwurfsmuster (de- sign patterns)— Elemente wiederverwendbarer objektorientierter Software, Addison- Wesley 1996.

(40)

Beispiel Strategie-Muster

• Aufgabe: Sortieren einer Liste bzgl. w¨ahlbarer Ordnung auf Elementen.

• L¨osung (inData.List)

data Ordering = LT | EQ | GT

sortBy :: (a -> a -> Ordering) -> List a -> List a ( ¨U: implementiere durch unbalancierten Suchbaum)

• Simulation (injava.util.*)

interface Comparator<T> { int compare(T x, T y); } static <T> void sort(List<T> list,Comparator<T> c);

hier istceinStrategie-Objekt

9 Algebraische Datentypen in OOP

Kompositum: Motivation

• Bsp: Gestaltung von zusammengesetzten Layouts.

Modell als algebraischer Datentyp:

data Component = JButton { ... }

| Container (List Component)

• Simulation durch EntwurfsmusterKompositum:

– abstract class Component

– class JButton extends Component – class Container extends Component – { void add (Component c); }

(41)

Kompositum: Beispiel

public class Composite {

public static void main(String[] args) { JFrame f = new JFrame ("Composite");

f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

Container c = new JPanel (new BorderLayout());

c.add (new JButton ("foo"), BorderLayout.CENTER);

f.getContentPane().add(c);

f.pack(); f.setVisible(true);

} }

Ubung: geschachtelte Layouts bauen, vgl.¨ http://www.imn.htwk-leipzig.de/

˜waldmann/edu/ws06/informatik/manage/

Kompositum: Definition

• Definition:Kompositum=algebraischer Datentyp (ADT)

• ADTdata T = .. | C .. T ..

als Kompositum:

– TypT⇒gemeinsame Basisklasse (interface) – jeder KonstruktorC⇒implementierende Klasse

– jedes Argument des Konstruktors⇒Attribut der Klasse – diese Argumente k¨onnenTbenutzen (rekursiver Typ)

(Vorsicht: Begriff und Abk¨urzung nicht verwechseln mit abstrakter Datentyp = ein Typ, dessen Datenkonstruktoren wirnichtsehen)

Bin¨are B¨aume als Komposita

• Knoten sindinnere(Verzweigung) und ¨außere(Blatt).

• Die richtige Realisierung ist Kompositum interface Tree<K>;

class Branch<K> implements Tree<K>;

(42)

• Schl¨ussel: in allen Knoten, nur innen, nur außen.

der entsprechende algebraische Datentyp ist:

data Tree k = Leaf { ... }

| Branch { left :: Tree k , ...

, right :: Tree k }

Ubung: Anzahl aller Bl¨atter, Summe aller Schl¨ussel (Typ?), der gr¨oßte Schl¨ussel (Typ?)¨ Kompositum-Vermeidung

Wenn Bl¨atter keine Schl¨ussel haben, geht es musterfrei?

class Tree<K> {

Tree<K> left; K key; Tree<K> right;

}

Der entsprechende algebraische Datentyp ist data Tree k =

Tree { left :: Maybe (Tree k) , key :: k

, right :: Maybe (Tree k) }

erzeugt in Java das Problem, daß . . .

Ubung: betrachte Implementierung in¨ java.util.Map<K,V>

Maybe=Nullable

Algebraischer Datentyp (Haskell):

data Maybe a = Nothing | Just a

http://hackage.haskell.org/packages/archive/base/latest/doc/

html/Prelude.html#t:Maybe

In Sprachen mit Verweisen (auf Objekte vom TypO) gibt es h¨aufig auch

”Verweis auf kein Objekt“— auch vom TypO. Deswegennull pointer exceptions.

Ursache ist Verwechslung vonMaybe amita.

Trennung in C#:Nullable<T>(f¨ur primitive TypenT)

http://msdn.microsoft.com/en-us/library/2cf62fcy.aspx

(43)

Alg. DT und Pattern Matching in Scala http://scala-lang.org algebraische Datentypen:

abstract class Tree[A]

case class Leaf[A](key: A) extends Tree[A]

case class Branch[A]

(left: Tree[A], right: Tree[A]) extends Tree[A]

pattern matching:

def size[A](t: Tree[A]): Int = t match { case Leaf(k) => 1

case Branch(l, r) => size(l) + size(r) }

beachte: Typparameter in eckigen Klammern

10 Objektorientierte Rekursionsmuster

Plan

• algebraischer Datentyp=Kompositum (Typ⇒Interface, Konstruktor⇒Klasse)

• Rekursionsschema=Besucher (Visitor) (Realisierung der Fallunterscheidung)

(Zum Vergleich von Java- und Haskell-Programmierung)

sagte bereits Albert Einstein: Das Holzhacken ist deswegen so beliebt, weil man den Erfolg sofort sieht.

Wiederholung Rekursionsschema

foldanwenden: jeden Konstruktor d. Funktion ersetzen

• Konstruktor⇒Schema-Argument

• . . . mit gleicher Stelligkeit

• Rekursion im Typ⇒Anwendung auf Schema-Resultat

(44)

data Tree a = Leaf a | Branch (Tree a) (Tree a) Leaf :: a -> Tree a

Branch :: Tree a -> Tree a -> Tree a

fold :: (a -> b) -> (b -> b -> b) -> Tree a -> b fold leaf branch t = case t of

Leaf k -> leaf k

Branch l r -> branch (fold leaf branch l) (fold leaf branch r) depth :: Tree a -> Int

depth = fold ( \ k -> 0 ) ( \ x y -> 1 + max x y ) Wiederholung: Kompositum

Haskell: algebraischer Datentyp data Tree a = Leaf a

| Branch (Tree a) (Tree a) Leaf :: a -> Tree a

Branch :: Tree a -> Tree a -> Tree a Java: Kompositum

interface Tree<A> { }

class Leaf<A> implements Tree<A> { A key; } class Branch<A> implements Tree<A> {

Tree<A> left; Tree<A> right;

}

(Scala: case class) Ubung Kompositum¨ public class Main {

// vollst. Bin¨arbaum der Tiefe d

// mit Schl¨usseln 2ˆd * (c - 1) .. 2ˆd * c - 1 static Tree<Integer> build (int d, int c);

class Pair<A,B> { A first; B second; }

// (Schl¨ussel links außen, Schl. rechts außen) static <A> Pair<A,A> bounds (Tree<A> t);

public static void main(String[] args) {

(45)

Tree<Integer> t = Main.build(4,1);

System.out.println (Main.bounds(t));

} }

Kompositum und Visitor

Definition eines Besucher-Objektes (f¨ur Rekursionsmuster mit ResultattypR¨uberTree<A>) entspricht einem Tupel von Funktionen

interface Visitor<A,R> { R leaf(A k);

R branch(R x, R y); }

Empfangen eines Besuchers: durch jeden Teilnehmer des Kompositums interface Tree<A> { ..

<R> R receive (Visitor<A,R> v); }

• Implementierung

• Anwendung (Bl¨atter z¨ahlen, Tiefe, Spiegelbild) Aufgabe: Besucher f ¨ur Listen

Schreibe das Kompositum f¨ur

data List a = Nil | Cons a (List a) und den passenden Besucher. Benutze f¨ur

• Summe, Produkt f¨urList<Integer>

• Und, Oder f¨urList<Boolean>

• Wert als gespiegelte Bin¨arzahl (LSB ist links) Bsp:[1,1,0,1] ==> 11

Quelltexte aus Vorlesung:

https://gitlab.imn.htwk-leipzig.de/waldmann/fop-ss16

(46)

Eine Funktion, die kein Fold ist

Das geht:f xs = die L¨ange von xs ist gerade f = fold True ( \ x y -> not y )

Das geht nicht:g xs = die L¨ange von xs ist >= 2Beweis: falls dochg = fold nil cons, dann betrachte

l0 = Nil ; g l0 = False -- nach Spez.

l1 = Cons 4 Nil ; g l1 = False -- nach Spez.

g (Cons 2 l0) = False -- nach Spezifikation g (Cons 2 l0) = cons 2 (g l0) = cons 2 False g (Cons 2 l1) = True -- nach Spezifikation g (Cons 2 l1) = cons 2 (g l1) = cons 2 False es folgt WiderspruchFalse = cons 2 False = True

d.h. die Annahme (g = fold nil cons) ist falsch.

11 Polymorphie

Arten der Polymorphie

• generische Polymorphie: erkennbar an Typvariablen

zurUbersetzungszeit¨ werden Typvariablen durch konkrete Typen substituiert,

• dynamische Polymorphie (≈Objektorientierung)

erkennbar animplementszw. Klasse und Schnittstelle zurLaufzeitwird Methodenimplementierung ausgew¨ahlt

moderne OO-Sprachen (u.a. Java, C#) bietenbeideFormen der Polymorphie

mit statischer Sicherheit (d.h. statische Garantie, daß zur Laufzeit keine Methoden fehlen)

Java-Notation f. generische Polymorphie generischerTyp(Typkonstruktor):

• Deklaration der Typparameter:class C<S,T> {..}

• bei Benutzung Angabe der Typargumente (Pflicht):

(47)

{ C<Boolean,Integer> x = ... } statische generischeMethode:

• Deklaration:class C { static <T> int f(T x) }

• Benutzung:C.<Integer>f (3)

Typargumente k¨onnen auch inferiert werden.

( ¨Ubung: Angabe der Typargumente f¨ur polymorphe nicht statische Methode) Beispiel f. dynamische Polymorphie

interface I { int m (); } class A implements I

{ int m () { return 0; }}

class B implements I

{ int m () { return 1; }}

I x = // statischer Typ von x ist I new A(); // dynamischer Typ ist hier A System.out.println (x.m());

x = new B(); // dynamischer Typ ist jetzt B System.out.println (x.m());

• statischer Typ: eines Bezeichners im Programmtext

• dynamischer Typ: einer Stelle im Speicher Klassen, Schnittstellen und Entwurfsmuster

• FP-Sichtweise: Entwurfsmuster=Fkt. h¨oherer Ordnung

• OO-Sichtweise: E.M.=n¨utzliche Beziehung zw. Klassen . . . die durch Schnittstellen ausgedr¨uckt wird.

⇒Verwendung von konkreten Typen (Klassen)ist ein Code Smell, es sollen soweit m¨oglich abstrakte Typen (Schnittstellen) sein. ( ¨U: diskutiereIEnumerable)

• insbesondere: in Java (ab 8):

funkionales Interface=hat genau eine Methode

eine implementierende anonyme Klasse kann als Lambda-Ausdruck geschrieben

(48)

Erzwingen von Abstraktionen

• interface I { .. }

class C implements I { .. } ; Wie kannC x = new C()verhindert werden, undI x = new C()erzwungen?

• Ansatz:class C { private C() { } } aber dann ist auchI x = new C()verboten.

• L¨osung: Fabrik-Methode class C { ..

static I make () { return new C (); } } Das Fabrik-Muster

interface I { }

class A implements I { A (int x) { .. } } class B implements I { B (int x) { .. } }

die Gemeinsamkeit der Konstruktoren kann nicht inIausgedr¨uckt werden.

interface F // abstrakte Fabrik { I construct (int x); }

class FA implements F // konkrete Fabrik { I construct (int x) { return new A(x); } } class FB implements F { .. }

main () {

F f = Eingabe ? new FA() : new FB();

I o1=f.construct(3); I o2=f.construct(4);

Typklassen in Haskell: ¨Uberblick

• in einfachen Anwendungsf¨allen:

Typklasse in Haskell∼Schnittstelle in OO:

beschreibt Gemeinsamkeit von konkreten Typen

(49)

• – Bsp. der Typ hat eine totale Ordnung

Haskell:class Ord a, Java:interface Comparable<E>

– Bsp. der Typ besitzt Abbildung nachString Haskellclass Show a, Java?

• unterschiedliche Benutzung und Implementierung Haskell - statisch, OO - dynamisch

Beispiel

sortBy :: (a -> a -> Ordering) -> [a] -> [a]

sortBy ( \ x y -> ... ) [False, True, False]

Kann mit Typklassen so formuliert werden:

class Ord a where

compare :: a -> a -> Ordering sort :: Ord a => [a] -> [a]

instance Ord Bool where compare x y = ...

sort [False, True, False]

• sorthateingeschr¨ankt polymorphen Typ

• die Einschr¨ankung (das ConstraintOrd a) wird in ein zus¨atzliches Argument (eine Funktion) ¨ubersetzt. Entspricht OO-Methodentabelle, liegt aberstatischfest.

Unterschiede Typklasse/Interface (Bsp)

• Typklasse/Schnittstelleclass Show a where show :: a -> String interface Show { String show (); }

• Instanzen/Implementierungendata A = .. ; instance Show A where ..

class A implements Show { .. }entspr. f¨urB

• in Java istShowein Typ:static String showList(List<Show> xs) { .. } showList (Arrays.asList (new A(),new B()))

in Haskell istShowein Typconstraint und kein Typ:showList :: Show a => List a -> String showList [A,B]ist Typfehler

(50)

Typklassen k¨onnen mehr als Interfaces

in Java, C#, . . . kann Schnittstelle (interface) in Deklarationen wie Typ (class) benutzt werden, das ist

1. praktisch, aber nur 2. soweit es eben geht

• (?) Fkt. mit>1Argument, Bsp. compareTo,static <T extends Comparable<? super T>>

void sort(List<T> list)

• (–) Beziehungen zwischen mehreren Typen,class Autotool problem solution

• (–) Typkonstruktorklassen,class Foldable c where toList :: c a -> [a];

data Tree a = ..;instance Foldable Tree (wichtig f¨ur fortgeschrittene Haskell-Programmierung) Grundwissen Typklassen

• Typklasse schr¨ankt statische Polymorphie ein

(Typvariable darf nicht beliebig substitutiert werden)

• Einschr¨ankung realisiert durchW¨orterbuch-Argument (W.B.=Methodentabelle, Record von Funktionen)

• durch Instanz-Deklaration wird W¨orterbuch erzeugt

• bei Benutzung einer eingeschr¨ankt polymorphen Funktion: passendes W¨orterbuch wird statisch bestimmt

• n¨utzliche, h¨aufige Typklassen:Show,Read,Eq,Ord.

(Test.SmallCheck.Serial,Foldable,Monad,. . . )

• Instanzen automatisch erzeugen mitderiving Ubung Polymorphie¨

• Besucher f¨ur Listen-Kompositum hinzuf¨ugen (Quelltext aus VL), damit implemen- tieren:

class Main {

static Boolean and (List<Boolean> xs) { return xs.receive(new ... );

}

(51)

public static void main (String [] argv) { List<Boolean> xs = new Cons<Boolean>(...);

syso (Main.and(xs);

} }

• nach Java ¨ubersetzen und implementieren

data Pair a b = Pair { first :: a, second :: b } swap :: Pair a b -> Pair b a

p :: Pair Int Bool ; p = Pair 3 False ; q = swap p

• autotool: Aufgabe zu Typklassen (unbalancierte Suchb¨aume)

12 Verz¨ogerte Auswertung (lazy evaluation)

Motivation: Datenstr¨ome Folge von Daten:

• erzeugen (producer)

• transformieren

• verarbeiten (consumer)

aus softwaretechnischen Gr¨unden diese drei Aspekte im Programmtext trennen, aus Effizienzgr¨unden in der Ausf¨uhrung verschr¨anken (bedarfsgesteuerte Transforma- tion/Erzeugung)

Bedarfs-Auswertung, Beispiele

• Unix: Prozesskopplung durch Pipes

cat foo.text | tr ’ ’ ’\n’ | wc -l Betriebssystem (Scheduler) simuliert Nebenl¨aufigkeit

• OO: Iterator-Muster

Enumerable.Range(0,10).Select(n=>n*n).Sum()

(52)

• FP: lazy evaluation (verz¨ogerte Auswertung)

let nats = nf 0 where nf n = n : nf (n + 1) sum $ map ( \ n -> n * n ) $ take 10 nats Realisierung: Termersetzung⇒Graphersetzung,

Beispiel Bedarfsauswertung

data Stream a = Cons a (Stream a)

nats :: Stream Int ; nf :: Int -> Stream Int nats = nf 0 ; nf n = Cons n (nf (n+1))

head (Cons x xs) = x ; tail (Cons x xs) = xs

Obwohlnatsunendlich ist, kann Wert vonhead (tail (tail nats)) bestimmt werden:

= head (tail (tail (nf 0)))

= head (tail (tail (Cons 0 (nf 1))))

= head (tail (nf 1))

= head (tail (Cons 1 (nf 2)))

= head (nf 2) = head (Cons 2 (nf 3)) = 2 es wird immer ein ¨außererRedex reduziert

(Bsp:nf 3ist eininnererRedex) Strictness

zu jedem TypT betrachteT ={⊥} ∪T dabei ist⊥ein

”Nicht-Resultat vom TypT“

• Exceptionundefined :: T

• oder Nicht-Terminationlet { x = x } in x Def.: Funktionf heißtstrikt, wennf(⊥) =⊥.

Fkt.f mitnArg. heißtstrikt ini,

falls∀x1. . . xn: (xi =⊥)⇒f(x1, . . . , xn) = ⊥

verz¨ogerte Auswertung eines Arguments ⇒Funktion ist dort nicht strikt einfachste Beispiele in Haskell:

• Konstruktoren (Cons,. . . ) sind nicht strikt,

• Destruktoren (head, tail,. . . ) sind strikt.

(53)

Beispiele Striktheit

• length :: [a] -> Intist strikt:

length undefined ==> exception

• (:) :: a->[a]->[a]ist nicht strikt im 1. Argument:

length (undefined : [2,3]) ==> 3 d.h. (undefined : [2,3]) ist nicht⊥

• (&&)ist strikt im 1. Arg, nicht strikt im 2. Arg.

undefined && True ==> (exception) False && undefined ==> False

Aufgaben zu Striktheit

Beispiel 1: untersuche Striktheit der Funktion g :: Bool -> Bool -> Bool

g x y = case y of { False -> x ; True -> y } Antwort:

• f ist nicht strikt im 1. Argument, dennf undefined True = True

• f ist strikt im 2. Argument,

denn dieses Argument (y) ist die Diskriminante der obersten Fallunterscheidung.

Beispiel 2: untersuche Striktheit der Funktion g :: Bool -> Bool -> Bool -> Bool g x y z =

case (case y of False -> x ; True -> z) of False -> x

True -> False

(54)

• ist strikt im 2. Argument, denn die Diskriminante(case y of ..)der obersten Fallunterscheidung verlangt eine Auswertung der inneren Diskriminantey.

Aufgabe:

f x y z = case y && z of False -> case x || y of

False -> z True -> False True -> y

Implementierung der verz¨ogerten Auswertung Begriffe:

• nicht strikt: nicht zu fr¨uh auswerten

• verz¨ogert (lazy): h¨ochstens einmal auswerten (ist Spezialfall vonnicht strikt) bei jedem Konstruktor- und Funktionsaufruf:

• kehrtsofortzur¨uck

• Resultat istthunk(Paar von Funktion und Argument)

• thunk wird erst bei Bedarf ausgewertet

• Bedarf entsteht durch Pattern Matching

• nach Auswertung: thunk durch Resultat ¨uberschreiben (das ist der Graph-Ersetzungs-Schritt)

• bei weiterem Bedarf: wird Resultat nachgenutzt Bedarfsauswertung in Scala

def F (x : Int) : Int = { println ("F", x) ; x*x }

lazy val a = F(3);

println (a);

println (a);

http://www.scala-lang.org/

(55)

Diskussion

• John Hughes:Why Functional Programming Matters, 1984http://www.cse.

chalmers.se/˜rjmh/Papers/whyfp.html

• Bob Harper 2011http://existentialtype.wordpress.com/2011/04/

24/the-real-point-of-laziness/

• Lennart Augustsson 2011http://augustss.blogspot.de/2011/05/more-points-for-lazy-evaluation-in.

html

Anwendungen der verz¨ogerten Auswertg. (I) Abstraktionen ¨uber den Programm-Ablauf

• Nicht-Beispiel (warum funktioniert das nicht in Java?) (mitjshellausprobieren)

<R> R wenn (boolean b, R x, R y)

{ if (b) return x; else return y; } int f (int x)

{ return wenn(x<=0,1,x*f(x-1)); } f (3);

• in Haskell geht das (direkt inghci)

let wenn b x y = if b then x else y let f x = wenn (x<= 0) 1 (x * f (x-1)) f 3

Anwendungen der verz¨ogerten Auswertg. (II) unendliche Datenstrukturen

• Modell:

data Stream e = Cons e (Stream e)

• man benutzt meist den eingebauten Typdata [a] = [] | a : [a]

• alle anderen Anwendungen von[a]sindfalsch z.B. als Arrays, Strings, endliche Mengen

daf¨ur gibt esData.Seq, Data.Text, Data.Set http://hackage.haskell.

(56)

Primzahlen

primes :: [ Int ]

primes = sieve ( enumFrom 2 ) enumFrom :: Int -> [ Int ]

enumFrom n = n : enumFrom ( n+1 ) sieve :: [ Int ] -> [ Int ]

sieve (x : xs) = x : ys

wobeiys=die nicht durchxteilbaren Elemente vonxs

13 OO-Simulation v. Bedarfsauswertung

Motivation (Wdhlg.) Unix:

cat stream.tex | tr -c -d aeuio | wc -m Haskell:

sum $ take 10 $ map ( \ x -> xˆ3 ) $ naturals C#:

Enumerable.Range(0,10).Select(x=>x*x*x).Sum();

• logische Trennung: Produzent→Transformator(en)→Konsument

• wegen Speichereffizienz: verschr¨ankte Auswertung.

• gibt es beilazy Datenstrukturen geschenkt, wird ansonsten durch Iterator (Enume- rator) simuliert.

Iterator (Java)

interface Iterator<E> {

boolean hasNext(); // liefert Status E next(); // schaltet weiter

}

interface Iterable<E> { Iterator<E> iterator();

}

(57)

typische Verwendung:

Iterator<E> it = c.iterator();

while (it.hasNext()) { E x = it.next (); ...

}

Abk¨urzung:for (E x : c) { ... } Beispiele Iterator

• ein Iterator (bzw. Iterable), der/das die Folge der Quadrate nat¨urlicher Zahlen liefert

• Transformation eines Iterators (map)

• Zusammenf¨ugen zweier Iteratoren (merge)

• Anwendungen: Hamming-Folge, Mergesort Beispiel Iterator Java

Iterable<Integer> nats = new Iterable<Integer>() { public Iterator<Integer> iterator() {

return new Iterator<Integer>() { private int state = 0;

public Integer next() { int result = this.state;

this.state++; return res;

}

public boolean hasNext() { return true; } }; } };

for (int x : nats) { System.out.println(x); } Aufgabe: implementiere eine Methode

static Iterable<Integer> range(int start, int count) sollcountZahlen abstartliefern.

Testf¨alle daf¨ur:

(58)

• @Test

public void t1() {

assertTrue (3 == Main.range(3, 5).iterator().next());

}

@Test

public void t2() {

assertTrue (5 == StreamSupport.stream(Main.range(3, 5).spliterator(), false).count());

}

Enumerator (C#)

interface IEnumerator<E> { E Current; // Status

bool MoveNext (); // Nebenwirkung }

interface IEnumerable<E> {

IEnumerator<E> GetEnumerator();

}

U: typische Benutzung (schreibe die Schleife, vgl. mit Java-Programm)¨ Abk¨urzung:foreach (E x in c) { ... }

Zusammenfassung Iterator

• Absicht: bedarfsweise Erzeugung von Elementen eines Datenstroms

• Realisierung: Iterator hat Zustand und Schnittstelle mit Operationen:

– (1) Test (ob Erzeugung schon abgeschlossen) – (2) Ausliefern eines Elementes

– (3) Zustands¨anderung

• Java: 1 :hasNext(), 2und3:next() C#: 3und1:MoveNext(), 2:Current

(59)

Iteratoren mit yield

• der Zustand des Iterators ist die Position im Programm

• MoveNext():

– bis zum n¨achstenyieldweiterrechnen, – falls dasyield returnist: Resultattrue – fallsyield break: Resultatfalse

• benutzt das (uralte) KonzeptCo-Routine using System.Collections.Generic;

IEnumerable<int> Range (int lo, int hi) { for (int x = lo; x < hi ; x++) {

yield return x;

}

yield break; } Aufgaben Iterator C#

IEnumerable<int> Nats () {

for (int s = 0; true; s++) { yield return s;

} }

Implementiere

”das merge aus mergesort“(Spezifikation?) static IEnumerable<E> Merge<E>

(IEnumerable<E> xs, IEnumerable<E> ys) where E : IComparable<E>

zun¨achst f¨ur unendliche Str¨ome, Test:Merge(Nats().Select(x=>x*x),Nats().Select(x=>3*x+1)).Take(10) (ben¨otigtusing System.Linqund AssemblySystem.Core)

Dann auch f¨ur endliche Str¨ome, Test:Merge(new int [] {1,3,4}, new int [] {2,7,8}) Dann Mergesort

(60)

static IEnumerable<E> Sort<E> (IEnumerable<E> xs) where E : IComparable<E> {

if (xs.Count() <= 1) { return xs;

} else { // zwei Zeilen folgen ...

} }

Test:Sort(new int [] { 3,1,4,1,5,9}) Streams in C#: funktional, Linq

Funktional

IEnumerable.Range(0,10).Select(x => xˆ3).Sum();

Typ von Select? Implementierung?

Linq-Schreibweise:

(from x in new Range(0,10) select x*x*x).Sum();

Beachte: SQL-select

”vom Kopf auf die F¨uße gestellt“.

14 Fkt. h¨oherer Ord. f ¨ur Streams

Motivation

• Verarbeitung von Datenstr¨omen,

• durch modulare Programme,

zusammengesetzt aus elementaren Strom-Operationen

• angenehme Nebenwirkung (1):

(einige) elementare Operationen sind parallelisierbar

• angenehme Nebenwirkung (2):

externe Datenbank als Datenquelle, Verarbeitung mit Syntax und Semantik (Typsy- stem) der Gastsprache

Referenzen

ÄHNLICHE DOKUMENTE

Bin e -&gt; List e Def: der Typ einer polymorphen Funktion beginnt mit All-Quantoren f¨ur Typvariablen4. Bsp: Datenkonstruktoren

• bisher: Synchronisation durch Sperren (locks) wesentlicher Nachteil: nicht modular. • jetzt:

Johannes Waldmann, HTWK Leipzig Deklarative (= fortgeschrittene) Programmierung Vorlesung WS 09, WS 10, SS 12, SS 13, SS 14

Folgerung: jede Sinn-Einheit (z. Implementierung einer Methode, Schnittstelle ei- ner Klasse) muß auf eine Bildschirmseite passen.

} } Locks sind re-entrant, damit aus einer synchronisierten Methode eine andere aufgerufen werden kann (mit dem Lock, den der Thread schon besitzt).. ¨at

wenn mehrere Threads eine gemeinsame Variable ohne Synchronisation benutzen, ist das Programm nicht

Folgerung: jede Sinn-Einheit (z. Implementierung einer Methode, Schnittstelle ei- ner Klasse) muß auf eine Bildschirmseite passen.

} } Locks sind re-entrant, damit aus einer synchronisierten Methode eine andere aufgerufen werden kann (mit dem Lock, den der Thread schon besitzt).. Granularit ¨at