Go to: Solsystemet

title: Programmera med funktioner
author: Peter Ljung

Programmering med funktioner

Vad är en funktion?

Inom matematiken används funktioner 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å variabeler 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:

V = (4/3) * pi * r * r * r

V bestäms entydigt av r enligt ekvationen ovan. Dvs. om r = 1 då är V = (3/4) * pi

Man kan också ha andra relationer än likhet tex. större än >, lägre än < eller skilt från =/=

Tex. om r < 1 då är också volymen av klotet V < (3/4) * pi eller r =/= 1 då är också volymen av klotet V =/= (3/4) * pi

Typer av värden

Man delar in värden i olika kategorier av värden som definerar mängden av möjliga värden.

Exempel på värden är

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 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å var sida om funktionen (sk. 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)

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 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öljade sida i en browser.

Online Haskell REPL

Här kan man testa att skriva funktioner i programmeringsprå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
let 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.

Hur skapar man egna funktioner?

Om vi bestämmer värdena på in variablerna så har funktionen ett ut värde

add a b = 8 om a = 3 och b = 5
add a b = 10 om a = 5 och b = 5

Man kan själv skapa funktioner som kan räkna ut mer 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ördefinerade funktioner tex. *. Vår cube utökade vi språket med.

Funktioner som anropar funktioner

Man kan skapa hur komplicerade funktioner som helst

Ofta använder en funktion andra funktioner som vi redan såg i det förra exemplet

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
cube 3

sphere r = (4/3) * pi * r * r * r
sphere 3

sphere_in_cube r = cube r - sphere r

sphere_in_cube 3

Andra typer av värden

Funktioner behöver inte bara hantera siffror

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 definera 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 "Programmering är funktioner!"

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

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

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

Programmera bilder!

Man kan även skapa bilder direkt genom programmering

Vi ska göra detta på en annan hemsida som heter http://code.world

Ett mycket enkelt program ser ut så här:

main = drawingOf(sol)

sol = circle(1)

En annan skillnad från tidigare är att man skriver värden till funktionen inom paranteser med comma (,) emellan

Värdet som kommer ut från main är resultatet när man kör programmet.

Programmera bilder!

main = drawingOf(sol)
 
sol = circle(1)
  1. Testa att skiva in programmet
  2. Testa att köra programmet genom att trycka på Run (kör)
  3. Vad är resutatet 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

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 in till en funktion kallas ofta 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å parametrar 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ällar 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 och förvänta sig samma resultat.

Det gäller alla matematiska funktioner om dom inte är kommunativa 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

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.

main = drawingOf(venus)

Flera planeter

Hur kan vi rita ut flera planeter samtidigt?

Det finns en funktion & som tar två bilder och skickar ut summan (båda) dessa bilder som en ny bild som resultat

& :: Picture -> Picture -> Picture

Testa genom att rita ut både solen och jorden

main = 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 och y 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 = animationOf(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 main mot den nya funktionen.

program = animationOf(solsystem)

References