class: center, middle # Programmering med funktioner Peter Ljung --- # Vad är en funktion? Inom matematiken används [funktioner](https://sv.wikipedia.org/wiki/Funktion) som ett samband mellan två eller flera in och ut värden. Exempelvis `+` (addition) är en funktion Skickar man in två värden `3` och `5` så skickar funktionen `+` ut ett resultat `8` ~~~ 3 [ + ] 8 5 ~~~ Andra vanliga funktioner inom matematiken är `-` (subtraktion), `*` (multiplikation) och `/` (division) ~~~ 3 [ - ] -2 5 9 [ * ] 27 3 ~~~ --- # Vad är värden och variabler? Värden är en konstant tex. `3`. Inom matematiken använder man också [variabler](https://sv.wikipedia.org/wiki/Variabel) för sätta ett namn på ett föränderligt värde. En variabel kan representera ett godtyckligt värde `x`, men kan också vara begränsat till ett värde tex. `3`. ~~~ x = 3 ~~~ Variabler relaterar till varandra (begränsas) av de funktioner som sätter upp mellan dom tex. Volymen `V` av ett klot med radien `r` beräknas genom [funktionen](https://www.matteboken.se/lektioner/skolar-9/geometri/klot-och-sfarer): ~~~ V = (4/3) * pi * r * r * r ~~~ Värdet på `V` är bestämt om man vet värdet på `r` enligt ekvationen. Tex. om `r = 1` då är `V = (4/3) * pi` --- # Typer av värden Man delar in värden i olika kategorier (eller grupper) av värden som talar om vilka alla möjliga värden är. Exempel på värden är: * Hela tal: ... -2, -1, 0, 1, 2, ... * Positiva hela tal: 1, 2, ... * Jämna tal: 2, 4, 6, 102 * Decimala tal: 3.14, 2.4, -12.4 --- # Hur många värden kan funktioner hantera? Vi såg att `+` är relationen mellan två (in) värden och ett (ut) värde Men det finns funktioner som tar in och ger tillbaka fler eller färre värden Vissa funktioner tar bara ett värde. Exempelvis `+ 1` som lägger till `1` till in värdet ~~~ 5 [ + 1 ] 6 ~~~ Andra tar hur många värden man vill. Exempelvis en funktion `length` som anger hur många värden man matar in. ~~~ 1, 4, 5, 3 "length" 4 3.14, 11.23 "length" 2 ~~~ --- # Hur många värden kan funktioner mata ut? Funktioner kan även ge tillbaka flera värden. Exempelvis en funktion `reverse` ordnar värdena i omvänd ordning ~~~ 4, 76 "reverse" 76, 4 434, 33 "reverse" 33, 434 ~~~ Konstanter kan man också se som funktioner utan invärde men med ett konstant utvärde ~~~ pi = 3.14159 ... - "pi" 3.14159 ... ~~~ --- # Hur skriver man funktioner? Matematiska funktioner skriver man normalt med de ingående värdena på vardera sida om funktionen (sk. [infix notation](https://sv.wikipedia.org/wiki/Infix_notation)) ~~~ 3 + 5 = 8 9 * 3 = 27 ~~~ Man kan också skriva funktionen först, med de ingående värdena efter funktionen (sk. [prefix notation](https://sv.wikipedia.org/wiki/Prefixnotation)) ~~~ add 3 5 (+) 3 5 multiply 9 3 (*) 9 3 ~~~ Ofta skriver man en funktion med värden som obestämda variabler ~~~ add a b divide c d ~~~ Detta betyder att funktionen `add` tar två okända godtyckliga värden (variabler) `a` och `b` --- # Funktioner i programmering Det visar sig att funktioner är mycket vanliga även inom programmering. Det som kan skilja sig, är exakt hur man skriver funktioner i olika programmeringsspråk Testa genom att öppna följande sida i en browser [Online Haskell REPL](https://repl.it/languages/haskell) Här kan man testa att använda funktioner i programmeringsspråket, Haskell --- # Funktioner i Haskell Testa exempelvis dessa kommandon i svarta rutan till höger på sidan. Tryck ENTER mellan varje kommando ~~~ 3 + 5 3 - 5 9 / 3 pi / 2 (+1) 1 add = (+) (+) 3 5 add 3 5 3 `add` 5 length [1,4,5,3] reverse [1,4,5,3] ~~~ Det är inte stor skillnad mellan hur man skriver matematiska funktioner och hur man skriver funktioner i Haskell. Haskell stödjer prefix notation och infix notation med några specialregler ... --- # Funktioner i Haskell (2) Vanliga tecken funktioner som `+`, `-`, `*` och `/` skrivs vanligtvis med infix notation och måste skrivas med parenteser för prefix notation ~~~ 3 + 5 3 - 5 (+) 3 5 (-) 3 5 ~~~ Namngivna funktioner som `pi`, `add`, `length` och `reverse` skrivs vanligtvis med prefix notation och måste skrivas med sk. bak-tick för in-fix notation ~~~ add 3 5 3 `add` 5 ~~~ --- # Hur skapar man egna funktioner? Man kan skapa egna funktioner inom programmering på ungefär samma sätt som man skriver matematiska ekvationer Tex. kan man skriva att volymen `V` på en kub bestäms av sidan `s` upphöjt till 3 som en ekvation ~~~ V = s*s*s ~~~ Man man kan också skriva detta med ett namn på funktionen `vol` som tar ett värde på sidan `s` och ger tillbaka en volym ~~~ vol(s) = s*s*s ~~~ Vi har alltså namnet på funktionen `vol` och den eller de parametrar som går in till funktionen (här bara `s`) till vänster och uträkningen av svaret till höger om likhetstecknet På nästan exakt samma sätt skapar man en funktion i Haskell ~~~ vol s = s*s*s ~~~ --- # Hur skapar man egna funktioner? (2) När man beskriver en funktion använder man variabler för att representera de godtyckliga värden som matas in i funktionen. ~~~ add a b = a + b ~~~ Man kan själv välja vilka variabelnamn som man vill använda för de inmatade parametrarna och även namnet på funktionen. ~~~ summera first second = first + second ~~~ När man använder funktionen `add` får variablerna värdena som man har matat in. Om `3` och `4` används som parametrar i `add` så får variablerna `a` och `b` värdena `3` och `4` när funktionen utförs ~~~ add 3 4 = [ a = 3, b = 4 ] = 3 + 4 = 7 ~~~ --- # Hur skapar man egna funktioner? (3) Man kan själv skapa funktioner som kan räkna ut väldigt komplicerade saker Exempelvis kan vi skriva funktionen for volymen av en kub med sidan `s` såhär: ~~~ cube s = s*s*s ~~~ Skriv in funktionen och testa sedan den genom att använda funktionen med olika värden på `s` ~~~ cube 1 cube 3 ~~~ I funktionen `cube` återanvänder vi funktionen `*` (multiplikation) för att räkna ut volymen av en kub. Haskell har redan många fördefinierade funktioner tex. `*`. Vår `cube` är en ny funktion som vi utökade språket med. --- # Funktioner som anropar funktioner Man kan skapa hur komplicerade funktioner som helst Ofta använder en funktion andra funktioner. Det såg vi i det förra exemplet där `*` (multiplikation) funktionen användes för att skapa funktionen `cube` I följande exempel skapar vi två nya funktioner `cube` och `sphere` som vi sedan använder i en tredje funktion `sphere_in_cube` ~~~ cube s = s*s*s sphere r = (4/3) * pi * r*r*r sphere_in_cube s = cube s - sphere (r/2) ~~~ `sphere_in_cube` ger tillbaka volymen av en kub med sidan `s` minus volymen av en sfär som är inbäddad i kuben ~~~ sphere_in_cube 3 ~~~ --- # Andra typer av värden Funktioner behöver inte bara hantera numeriska värden Man kan representera vad som helst med en typ av data (datatyp). Exempelvis kan man tänka sig att man har en datatyp som representerar färger. Sedan kan man definiera funktioner som jobbar med färger som in och ut data. ~~~ add yellow blue = green ~~~ Funktioner kan också jobba med bokstäver Exempelvis en funktion (`reverse`) som ger tillbaka texten baklänges ~~~ reverse "Naturrutan är ett palindrom" ~~~ Man kan se grafiska former som värden Exempelvis funktionen `circle r` ge tillbaka en bild (`Picture`) av en cirkel med radien `r` ~~~ circle r = Picture ~~~ --- # Typer av värden Olika värden kan delas upp i olika typer (eller klasser) av värden. Några exempel på typer är * Heltal * Decimaltal * Färger * Bilder Vissa värden ingår i flera typer Exempelvis heltal finns också bland decimaltal ~~~ 13 är samma tal som 13.00000... ~~~ Funktioner kan ta och ge tillbaka olika typer av värden Exempelvis funktionen `round` tar ett decimaltal och ger tillbaka ett heltal ~~~ round pi = 3 ~~~ --- # Testa olika typer Testa några funktioner som tar olika typer in in och ut värde. Vilka typer är det? ~~~ round pi pi - round pi length "Hej" ~~~ Vad händer om du skickar in fel typ? ~~~ 3 - "hej" 3 - length "hej" pi - (round pi) pi - fromIntegral (round pi) ~~~ Parenteser används förresten på samma sätt som inom matematiken dvs. när man vill se till att en beräkning utförs före en annan ~~~ 3 + 4 * 5 = 23 (3 + 4) * 5 = 35 ~~~ --- # Testa olika typer (2) I det sista exemplet kan man ifrågasätta varför det inte går att subtrahera ett heltal `3` från `pi`. Programmeringsspråk kan ofta vara mer strikta vad gäller typer av värden än vad matematiken tillåter. Av den anledningen går det inte att subtrahera två tal av olika typ (decimaltal och heltal). För att komma runt detta måste man först konvertera heltalet till ett decimaltal. ~~~ pi - (fromIntegral (round pi)) ~~~ Funktionen `fromIntegral` konverterar ett heltal till en lämplig numerisk typ i detta fall ett decimaltal. Man kan fråga vilken typ det är genom att skriva `:t` framför uttrycket tex. ~~~ :t pi pi :: Floating a => a :t (round pi) (round pi) :: Integral b => b ~~~ `Floating` visar här att det är ett decimaltal och `Integral` att det är ett heltal --- # Programmera bilder Man kan även skapa bilder genom programmering Vi ska göra detta på en annan hemsida som heter
Ett mycket enkelt program ser ut så här: ~~~ program = drawingOf(sol) sol = circle(1) ~~~ * `circle` är en funktion som tar ett tal och ger tillbaka en bild av en cirkel * `sol` är en (konstant) funktion som ger tillbaka en bild av en cirkel med radien `1` * `drawingOf` är en funktion som tar en bild och ger tillbaka ett program * `program` är en speciell huvud funktion som alltid måste finnas och ge tillbaka ett program --- # Programmera bilder (2) En skillnad från tidigare är att man skriver värden till funktionen inom parenteser med komma (`,`) emellan Värdet som kommer ut från `program` är resultatet när man kör programmet ~~~ program = drawingOf(sol) sol = circle(1) ~~~ 1. Testa genom att skriva in programmet 2. Testa att köra programmet genom att trycka på `Run` (kör) 3. Vad är resultatet från programmet när du kör det? --- # Mer avancerade program - Solsystemet Nu är vi redo för mer avancerade program. Vi ska skriva ett program som simulerar solsystemet! Till vår hjälp har vi lite dokumentation * code.world Guide - hittar du bland knapparna längst ned * [code.world API](https://hackage.haskell.org/package/codeworld-api-0.2.1.0/docs/CodeWorld.html) --- # Färger Först ska vi byta ut cirkeln mot en fylld cirkel. Funktionen `solidCircle` (fylld cirkel) gör det. ~~~ sol = solidCircle(1) ~~~ Det finns en funktion `colored` som tar en bild och en färg och skickar ut bilden med en ny färg. Skicka cirkeln och färgen `yellow` (gul) så får vi en färgad cirkel. ~~~ sol = colored( solidCircle(1) , yellow) ~~~ Testa detta! --- # Olika typer Vad är händer egentligen när man anropar funktionen `colored()`? `colored()` tar två värden (värden till funktioner kallas `parametrar`): 1. En bild (`Picture`) som i det här fallet är `solidCircle(1)` 2. En färg (`Color`) som i det här fallet är `yellow` Det värde som kommer ut från funktionen är en ny bild (`Picture`) dvs. en gul fylld cirkel Det är viktigt att alltid skicka in parametrar (värden) av rätt typ i funktioner annars kommer programmet inte att fungera --- # Olika typer Testa exempelvis att byta plats på parametrarna bild och färg i funktionen `colored()` och kör programmet (`Run`). ~~~ sol = colored(yellow, solidCircle(1)) Line 3, Column 15: error: • Couldn't match expected type Picture with actual type Color • In the expression: yellow In the parameter(s) of colored, namely (yellow, solidCircle (1)) In the expression: colored (yellow, solidCircle (1)) Line 3, Column 23: error: • Couldn't match expected type Color with actual type Picture • In the expression: solidCircle (1) In the parameter(s) of colored, namely (yellow, solidCircle (1)) In the expression: colored (yellow, solidCircle (1)) ~~~ Detta är ett felmeddelande som talar om att funktionens första värde `yellow` inte passar (match) med den förväntade typen, `Picture`. Det samma gäller andra värdet som inte matchar typen `Color`. Det står också på vilken rad felet är. --- # Olika typer (2) Det är inte speciellt konstigt att ordningen har stor betydelse inom programmering. Man kan tex inte byta ordning på `a - b` till `b - a` inom matematiken och förvänta sig samma resultat. Det gäller alla matematiska funktioner om dom inte är [kommutativa](https://sv.wikipedia.org/wiki/Kommutativitet) som tex. multiplikation är. --- # Fler funktioner och typer Liksom funktionen `colored` finns många andra funktioner som förändrar bilden som skickas in. Funktionen `translated` tar en bild som första parameter, sedan ett tal (`Number`), sedan ytterligare ett tal. Resultatet är en ny bild där bilden är flyttad (`translated`) i x och y led. Så här skriver man vilka typer av parametrar som funktionen tar. Sista typen anger typen av resultatet. ~~~ translated :: Picture -> Number -> Number -> Picture ~~~ Man kan få hjälp med vilka typer en funktion tar i code.world. Skriv tex. `tr` och sedan `Ctrl`+`Space` Titta också på funktionen `colored` genom att skriva `co` och `Ctrl`+`Space` --- # Planeter Nu är det dags för solen att få sällskap av nya planeter. Vi skapar fler planeter vid sidan om solen i [solsystemet](https://sv.wikipedia.org/wiki/Solsystemet) På wikipedia kan man se att planeternas storlek anges om andel av jorden storlek. Vi bestämmer att jorden storlek ska vara `20%` av solens. ~~~ size_sun = 1.0 size_earth = 0.2 * size_sun ~~~ Sedan skapar vi planeter ... ~~~ merkurius = solidCircle(size_earth*0.4) venus = solidCircle(size_earth*0.4) jorden = solidCircle(size_earth) mars = solidCircle(size_earth*1.5) ~~~ Testa att rita ut en av planeterna. ~~~ program = drawingOf(venus) ~~~ --- # Flera planeter Hur kan vi rita ut flera planeter samtidigt? Det finns en funktion `&` som tar två bilder och skickar ut summan (kombinationen) av dessa bilder som en ny bild som resultat ~~~ & :: Picture -> Picture -> Picture ~~~ Testa genom att rita ut både solen och jorden ~~~ program = drawingOf( sol & jorden ) ~~~ Vad hände? Jorden syns inte. Solen och jorden skrivs ut på samma plats och skriver över varandra --- # Flera planeter (2) Det här kan vi fixa genom att flytta ut jorden från centrum av ritytan Funktionen `translated` flyttar på bilder ~~~ translated :: Picture -> Number -> Number -> Picture ~~~ `translated` tar alltså en bild och två tal som flyttar bilden i `x` (horisontell) och `y` (vertikal) led Så vi kan fixa överritningen genom att flytta jorden ut 4 solradier i x-led från solens centrum ~~~ jorden = translated(solidCircle(size_earth), 4 * size_sun, 0) ~~~ --- # Ett solsystem Så här ser inre delen av solsystemet ut ... ~~~ program = drawingOf(solsystem) size_sun = 1.0 size_earth = 0.2 * size_sun sol = colored(solidCircle(1), yellow) merkurius = translated(solidCircle(size_earth), 2, 0) venus = translated(solidCircle(size_earth), 3, 0) jorden = translated(solidCircle(size_earth), 4, 0) mars = translated(solidCircle(size_earth), 5, 0) solsystem = sol & merkurius & venus & jorden & mars ~~~ --- # Mer realistiskt solsystem Vi vill skapa ett helt solsystem med olika planeter med olika storlek, färg och avstånd från solen. För att underlätta programmering skapar vi en funktion som skapar en ny planet ~~~ planet(size, distance, color) = colored( translated( solidCircle(earth_size*size), earth_dist*distance, 0), color) ~~~ Storlek (`size`), avstånd (`distance`) och färg (`color`) är in parametrar till funktionen. Resultatet är en bild på planeten Funktionen använder inget nytt utan kombinerar bara dom funktioner som vi har använt tidigare. --- # Mer realistiskt solsystem Men denna planet skapar funktion kan vi visa ett mer realistiskt solsystem ~~~ program = drawingOf(solsystem) sol = colored (solidCircle(1), yellow) earth_size = 0.2 -- sun has radius 1 earth_dist = 5.0 -- distance from center of sun merkurius = planet(0.382, 0.387, yellow) venus = planet(0.949, 0.72, yellow) jorden = planet(1, 1, blue) mars = planet(0.53, 1.52, red) planet(size, distance, color) = colored( translated( solidCircle(earth_size*size), earth_dist*distance, 0), color) solsystem = sol & merkurius & venus & jorden & mars ~~~ --- # Planetrörelser Skulle det inte vara coolt om man kunde få planeterna att röra sig kring solen också? Det första vi måste göra är att byta ut programmet mot en animering. En animering är en bild som ändrar sig med avseende på tiden. Om tiden (betecknat `t`) skulle kunna vara en av parametrarna till `solsystem` så skulle vi kunna rita ut hur solsystemet se ut vid olika tidpunkter. Vi använder en funktion `rotated` som tar en bild och en ytterligare parameter som anger hur mycket bilden ska rotera (i grader kring centrum av bilden) --- # Tiden som parameter Så vi byter ut funktionen `solsystem` med en ny funktion som tar en parameter `t`: ~~~ solsystem(t) = sol & rotated(merkurius,(10/0.241)*t) & rotated(venus,(10/0.615)*t) & rotated(jorden, 10*t) & rotated(mars, (10/1.88)*t) ~~~ Vi kan köra programmet med olika värden på `t` för att se hur det ser ut vid olika tidpunkter. Testa `0, 1, 2, ...` ~~~ program = drawingOf(solsystem(1)) ~~~ --- # Animera med tiden `t` Men vi kan faktiskt få programmet att visa bilden för många olika värden på `t` automatiskt dvs. animera bilden. För det behöver vi en ny funktion `animationOf` i stället för `drawingOf`. Skillnaden är att `animationOf` förväntar sig en parameter som är just den typ av funktion som vi precis har skapat. ~~~ animationOf :: (Number -> Picture) -> Program ~~~ Jämför med `drawingOf` som bara tar en parameter av typen `Picture` ~~~ drawingOf :: Picture -> Program ~~~ --- # Funktioner som parametrar Funktioner är alltså också datatyper som kan användas som parametrar Funktionen `solsystem(t)` har redan rätt datatyp ~~~ solsystem :: Number -> Picture ~~~ `animationOf` förväntar sig alltså en funktion som genererar en bild från ett tal som representerar tid. `animationOf` kommer att anropa denna funktion från `t = 0` och framåt och skapa en animation. Testa att byta ut `program` mot den nya funktionen. ~~~ program = animationOf(solsystem) ~~~ --- # The end Nu kan du grunderna i programmering med funktioner. Nu är det bara fantasin som sätter gränserna för vad du kan göra! Denna presentation är skapad mha. [remarkjs](https://remarkjs.com)