Funktionell programmering TDDD73 Funktionell och imperativ programmering i Python Föreläsning 8 Peter Dalenius Institutionen för datavetenskap 2013-10-09
Översikt Vad är ett programmeringsparadigm? Vad är funktionell programmering? Högre ordningens funktioner Lambda-funktioner Pythons egna iteratorfunktioner Listbyggare 2
Vad är ett programmeringsparadigm? från grekiskans paradeigma som betyder mönster, exempel eller föredöme fundamental programmeringsstil system av idéer som ligger till grund för designen av programspråk logiskt tillvägagångssätt för att beskriva hur ett programspråk implementeras modell för programmering baserad på särskilda koncept som formar hur en programmerare designar, organiserar och skriver program 3
Arkitekturstilar Antik romersk arkitektur (Colosseum, Rom) Bauhaus (Tyskland) Gotisk arkitektur (Katedralen i Reims, Frankrike) Art Nouveau (Portugal) Nationalromantisk stil (Finland) Indo-islamisk stil (Taj Mahal, Indien) 4
Koncept Paradigm Programspråk Koncept och idéer om hur program och data kan designas och organiseras Programmeringsparadigm kombinerar ett antal koncept Programspråk kan stödja ett eller flera paradigm Lexically scoped closure Concurrency Records Observable nondeterminism Named state Object oriented Imperative Event driven Declarative Functional Aspect oriented C++ Fortran COBOL Python Java Ada Ruby Visual Basic Peter van Roy (2009) Programming Paradigms for Dummies: What Every Programmer Should Know. In New Computational Paradigms for Computer Music, G. Assayag and A. Gerzso (eds.), IRCAM/Delatour France, 2009 http://www.info.ucl.ac.be/~pvr/paradigms.html 5
6
De vanligaste programmeringsparadigmen Imperativ programmering Ett program är en följd av satser som manipulerar globala datastrukturer så att deras tillstånd förändras över tid. Objektorienterad programmering Ett program är en uppsättning objekt som har vissa egenskaper och kan utföra vissa metoder. Programkörning innebär att meddelanden skickas mellan dessa objekt. Funktionell programmering Ett program är en uppsättning funktioner vars utdata enbart beror av indata, d.v.s. de har inga sidoeffekter och manipulerar inte globala datastrukturer. 7
Egenskaper hos funktionell programmering En programkörning betraktas som en beräkning av en serie matematiska funktioner. Funktionerna har inga sidoeffekter, d.v.s. förutom att returnera ett värde så undviker de in- och utmatning och ändrar inga globala variabler. Muterbara (förändringsbara) datastrukturer undviks gärna, liksom tilldelningar över huvud taget. Upprepning utförs gärna med hjälp av rekursion. Funktioner betraktas som första ordningens objekt, d.v.s. funktioner kan skapa och använda andra funktioner som värden. indata funktion utdata 8
Fördelar med funktionell programmering Den grundläggande fördelen är att varje funktion är en självständig enhet som inte är beroende av någon annan. Detta får flera olika konsekvenser: Varje funktion kan utvecklas och testas för sig. En funktion kan användas var som helst och när som helst i programflödet. Funktionerna kan lätt kombineras med varandra till större, mer komplexa funktioner. Färdiga program tenderar att bli kompakta, lätta att underhålla, skalbara och återanvändbara. 9
Nackdelar med funktionell programmering Funktionell programmering kan upplevas som krångligare och mer komplicerat än traditionell imperativ programmering. Det stora fokuset på rekursion och skapandet av många små funktioner gör programmen lite långsammare. Sidoeffekter (så som in- och utmatning) hanteras inte i strikt funktionell programmering, vilket kan göra det svårt att använda i verkliga projekt. Funktionella språk har inte haft särskilt stor kommersiell framång, med några undantag, utan har främst varit en akademisk angelägenhet. 10
Funktionell programmering i verkligheten Det är få som använder strikt funktionell programmering genomgående i ett projekt, utan man begränsar det till delar av systemet eller använder tankesättet lite mer pragmatiskt. Språk som i olika hög grad stöder funktionell programmering: Common Lisp, Scheme, Racket, Clojure, Erlang, OCaml, Haskell, Scala, F#. Funktionella språk och funktionell programmering har fått ett litet uppsving de senaste åren. Bl.a. har språket Erlang (som utvecklades av Ericsson i slutet av 1980-talet) kommit till heders igen och används av bl.a. Facebook och Klarna. 11
Funktioner som bearbetar sekvenser def increment5(seq): if not seq: return [] else: return [5 + seq[0]] + increment5(seq[1:]) Vi ska titta på fyra alternativa sätt att göra detta. def first_element(seq): if not seq: return [] else: return [seq[0][0]] + first_element(seq[1:]) >>> increment5([5,10,15,20]) [10, 15, 20, 25] >>> first_element([['q','w','e','r'],[2,3,5,7,11]]) ['q', 2] 12
Alternativ 1: Högre ordningens funktioner En funktion som använder andra funktioner som in- eller utdata. increment5 [10,20,30] [15,25,35] [[ x, y ],[47,11]] first_element [ x,47] s[0] n+5 [[ x, y ],[47,11]] [10,20,30] [ x,47] [15,25,35] 13
Alternativ 1: Högre ordningens funktioner def with_all(func,seq): if not seq: return [] else: return [func(seq[0])] + with_all(func,seq[1:]) def i5(n): return n + 5 def fe(s): return s[0] def increment5(seq): return with_all(i5,seq) def first_element(seq): return with_all(fe,seq) 14
Alternativ 2: Lambda-funktioner Små funktioner som enbart består av ett enda uttryck kan ersättas av lambda-funktioner. De är temporära funktioner utan namn som typiskt används som argument till högre ordningens funktioner. parameterlista (kan ha flera argument) def i5(n): return n + 5 (lambda n: n + 5) det som ska beräknas (endast ett enda uttryck) 15
Alternativ 2: Lambda-funktioner def with_all(func,seq): if not seq: return [] else: return [func(seq[0])] + with_all(func,seq[1:]) def increment5(seq): return with_all((lambda n: n + 5),seq) def first_element(seq): return with_all((lambda s: s[0]),seq) 16
Mer om högre ordningens funktioner Vi vill ha en funktion combine som kan gå igenom en lista av tal och kombinera dem parvis med hjälp av en funktion som vi skickar in. Några exempel på vad vi vill kunna göra: 0 + 1 + 2 + 3 + 4 = 10 0-1 - 2-3 - 4 = -10 0 * 1 * 2 * 3 * 4 = 0 17
Mer om högre ordningens funktioner def combine(func,seq): result = seq[0] for element in seq[1:]: result = func(result,element) return result >>> combine((lambda x,y: x-y),range(5)) -10 result = 0 result = (0-1) result = ((0-1)-2) result = (((0-1)-2)-3) result = ((((0-1)-2)-3)-4) = -10 Förväntat resultat 18
Mer om högre ordningens funktioner def combine(func,seq): result = seq[0] for element in seq[1:]: result = func(element,result) return result Omvänd ordning på argumenten till den inskickade funktionen! >>> combine((lambda x,y: x-y),range(5)) 2 result = 0 result = (1-0) result = (2-(1-0)) result = (3-(2-(1-0))) result = (4-(3-(2-(1-0)))) = 2 Fel resultat! 19
Mer om högre ordningens funktioner Elementen def combine(func,seq): bearbetas i if not seq[1:]: omvänd ordning. return seq[0] else: return func(combine(func,seq[:-1]),seq[-1]) >>> combine((lambda x,y: x-y),range(5)) -10 (?-4) seq = [0,1,2,3,4] ((?-3)-4) seq = [0,1,2,3] (((?-2)-3)-4) seq = [0,1,2] ((((?-1)-2)-3)-4) seq = [0,1] ((((0-1)-2)-3)-4) = -10 seq = [0] Förväntat resultat 20
Mer om högre ordningens funktioner Det gäller att ha koll på i vilken ordning de enskilda elementen kombineras. Om funktionen som vi skickar in är både kommutativ och associativ kan vi i princip kombinera hur som helst, men om den saknar någon av dessa egenskaper är ordningen viktig. Definition av egenskaper: Kommutativ: a + b = b + a Associativ: (a + b) + c = a + (b + c) Exempel: Addition är både kommutativ och associativ. Substraktion är varken kommutativ eller associativ. Konkatenering (sammanslagning av strängar eller listor) är associativ men inte kommutativ. 21
def create_circle(len): i = 0 def next(): nonlocal i i = i+1 if i>=len: i = 0 return i return next Funktioner som utdata >>> c = create_circle(3) >>> c <function next at 0x0000025067C8> >>> c() 1 >>> c() 2 >>> c() 0 Funktionen create_circle returnerar ett closure, d.v.s. ett funktionsobjekt som även innehåller information om lokala variabler. 22
Alternativ 3: Pythons egna iteratorfunktioner Den generella funktionen with_all har redan en motsvarighet i Python i funktionen map. Den returnerar dock inte listan direkt. >>> range(5) range(0, 5) >>> list(range(5)) [0, 1, 2, 3, 4] >>> map((lambda x: x*x),range(5)) <map object at 0x00000000024C83C8> >>> list(map((lambda x: x*x),range(5))) [0, 1, 4, 9, 16] Det som funktionerna range och map returnerar kallas iteratorer. De är objekt som innehåller sekvenser som ännu inte är beräknade. En sådan sekvens kan potentiellt vara oändlig, och vi får inte se elementen förrän vi tvingar fram dem, t.ex. med funktionen list. 23
Alternativ 3: Pythons egna iteratorfunktioner En iterator kan ses som en slags automat ur vilken man kan få ett element i taget. Den i princip enda operationen man kan göra med en iterator är next() som ger nästa element, om det finns. Iteratorer går utmärkt att använda i for-loopar: for element in iterator: do_something() Funktionen list ser till att spotta fram hela sekvensen på en gång, i de fall vi behöver detta. 24
Alternativ 3: Pythons egna iteratorfunktioner def increment5(seq): return list(map((lambda n: n + 5),seq)) def first_element(seq): return list(map((lambda s: s[0]),seq)) 25
Mer om iteratorfunktioner >>> s=[3,1,4,2] >>> min(s), max(s), sum(s) (1, 4, 10) >>> sorted(s) [1, 2, 3, 4] >>> list(reversed(s)) [2, 4, 1, 3] >>> list(filter((lambda x: x%2==0),s)) [4, 2] >>> list(enumerate(s)) [(0, 3), (1, 1), (2, 4), (3, 2)] >>> list(zip(s,['a','b','c','d'])) [(3, 'a'), (1, 'b'), (4, 'c'), (2, 'd')] 26
Alternativ 4: Listbyggare Listbyggare (eng. list comprehensions) är ett mer kompakt alternativ till funktionen map. Man får en lista direkt och behöver inte använda funktionen list. list(map((lambda variabel: uttryck),sekvens)) [uttryck for variabel in sekvens] 27
Alternativ 4: Listbyggare def increment5(seq): return [n + 5 for n in seq] def first_element(seq): return [s[0] for s in seq] 28
Mer exempel på listbyggare >>> [len(x) for x in ['spam','ham']] [4, 3] >>> [x*x for x in range(5) if x>0 and x%2==0] [4, 16] filter >>> [(x, y) for x in range(3) for y in range(4)] flera [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), listor (1, 3), (2, 0), (2, 1), (2, 2), (2, 3)] >>> open("poetry.txt").readlines() ['Roses are red\n', 'Violets are blue\n', 'Spam and eggs\n'] >>> [line.rstrip() for line in open("poetry.txt")] ['Roses are red', 'Violets are blue', 'Spam and eggs'] >>> people = [('Eve',37,'manager'), ('Dave',45,'technician'), ('Alice',49,'CEO')] >>> [age for (name,age,job) in people] projicering [37, 45, 49] textfiler 29
Ett lite klurigare exempel def sum1(f, n): if n < 0: return 0 else: return f(n) + sum1(f, n-1) Detta är en utmärkt funktionell lösning som är tydligt och enkelt skriven, men finns det andra sätt att lösa den med hjälp av vad vi har sett idag? 30
Principskiss lambda x: x*x 5 Räkna upp talen 0 till n Görs enklast med range() [0, 1, 2, 3, 4, 5] Beräkna f(x) för alla tal i listan T.ex. med listbyggare lambda x, y: x+y [0, 1, 4, 9, 16, 25] Kombinera ihop talen parvis Funktionen combine från tidgare, eller inbyggda funktionen reduce 55 31
Ett lite klurigare exempel import functools def sum2(f, n): return functools.reduce((lambda x, y: x+y), \ [f(x) for x in range(n+1)]) 32
Sammanfattning I funktionell programmering bygger man upp sitt program av ett antal matematiska funktioner utan sidoeffekter. Fördelen är att programkomponenterna blir testbara, skalbara och återanvänbara. Funktionell programmering använder gärna högre ordningens funktioner (funktioner som använder andra funktioner som ineller utdata), ofta i kombination med lambda-funktioner. Python har ett flertal iteratorer inbyggda, som kan hjälpa oss att t.ex. utföra en operation på alla element. Listbyggare är en ännu mer kompakt och flexibel metod att konstruera sekvenser. 33
Bildkällor: http://en.wikipedia.org/wiki/file:colosseum_in_rome,_italy_-_april_2007.jpg http://en.wikipedia.org/wiki/file:reims_kathedrale.jpg http://en.wikipedia.org/wiki/file:casa_arte_nova_aveiro_by_henrique_matos_02.jpg http://en.wikipedia.org/wiki/file:tajmahal_2012.jpg http://en.wikipedia.org/wiki/file:finnish_national_theatre.jpg http://en.wikipedia.org/wiki/file:bauhaus_chemnitz_hb.jpg http://www.info.ucl.ac.be/~pvr/paradigms.html www.liu.se