Introduktion til Elm

Funktionel programmering

2 Mere om funktioner

I dette kapitel skal vi lære meget mere om funktioner. I Elm er funktioner enormt vigtige, og derfor er det også vigtigt, at vi er gode til at bruge dem.

2.1 Funktioner med flere input

I matematik kan vi lave funktioner med flere input end ét. Det kan vi selvfølgelig også i Elm. Det kunne for for eksempel se sådan her ud.

Elm
> greeting : String -> String -> String | greeting firstName lastName = | "Hello " ++ firstName ++ " " ++ lastName | < function > : String -> String -> String > > greeting "Ellen" "Hansen" "Hello Ellen Hansen" : String > greeting ("Peter " ++ "Finn") "Jensen" "Hello Peter Finn Jensen" : String

Der er flere interessante ting i dette eksempel. Læg allerførst mærke til, at vi navngiver vores inputvariable firstName og lastName. Det gør vi, så er det let at huske, hvad der er hvad, og koden bliver lettere for andre at læse. Man kan simpelthen læse i koden, hvad det er meningen at inputtet skal repræsentere. Inputvariable navngives med lille begyndelsesbogstav - ligesom funktioner.

Derudover foregår der noget lidt kontraintuitivt i den måde vi definerer funktioner på, når de har flere inputs. Derfor gennemgår vi eksemplet bid for bid:

greeting : String -> String -> String
skal læses som: Vi tager en String og en String som input og så giver vi en String som output. Så alle typerne bortset fra den sidste er altså inputs. Man kunne måske have ønsket sig at notationen så sådan her ud:
greeting : String, String -> String
men det er altså ikke tilfældet. Der er en årsag til, at vi gør det på denne måde, men det vender vi først tilbage til senere.

En sidste ting, som er værd at bide mærke i er de sidste to linjer. Parenteserne angiver, at Elm først skal regne det ud, som står inde i parentesen (akkurat som i matematik) og derefter give det som det første input til funktionen greeting.

2.2 Konstanter

I Elm er konstanter faktisk også funktioner. Det er bare funktioner, der ikke tager noget input. For eksempel:

Elm
mitTal : Int mitTal = 2
Funktionen mitTal tager slet ikke noget input, den giver bare altid det samme output, og det er værdien 2 af typen Int.

Det virker måske lidt underligt - eller måske direkte dumt - for hvorfor skulle man lave sådan nogle funktioner? Det gør man blandt andet for at opbevare, eller huske en værdi af en bestemt type, så man kan kalde den igen på et senere tidspunkt.

Konstanter kan også bruges til at udbygge vores forståelse af pointen fra første kapitel med at 2 enten har typen Int eller typen Float. Lad os lave to konstanter i elm repl:

Elm
> mitTal : Int | mitTal = | 2 | 2 : Int > ditTal : Float | ditTal = | 2 | 2 : Float
Lad os nu definere en funktion, der ganger sit input med tre:
> multiplyByThree : Int -> Int
| multiplyByThree x =
|   3 * x
|
 : Int -> Int
Hvad mon der sker, hvis vi kalder multiplyByThree med henholdsvis mitTal og ditTal?
> multiplyByThree mitTal
6 : Int 
mitTal har typen Int, så her går alt som forventet.
> multiplyByThree ditTal
-- TYPE MISMATCH ------------------------------------------ REPL

The 1st argument to `multiplyByThree` is not what I expect:

15|   multiplyByThree ditTal
                      ^^^^^^
This `ditTal` value is a:

    Float

But `multiplyByThree` needs the 1st argument to be:

    Int
Men ditTal har typen Float, så det kan ikke bruges som input til multiplyByThree.

2.3 Indbyggede funktioner

Elm kommer med et hav af indbyggede funktioner, og en stor del af at programmere i Elm er at bruge disse. Vi har faktisk allerede set to af dem, nemlig + og ++.

2.3.1 Infix og prefix

Du tænker måske, at + da ikke kan være en funktion, fordi den ikke tager nogen input. Men det gør den faktisk! Når vi skriver

Elm
2 + 3
så er 2 det første input, mens 3 er det andet input. Funktioner, der står mellem sine input kaldes infix-funktioner.

Normalt skriver vi jo funktionen før sine input. Med et fint ord kaldes det en prefix-funktion, fordi "pre" betyder "før". Alle de funktioner vi selv laver er prefix-funktioner.

Hvis man vil insistere på det, kan godt skrive infix-funktioner op som prefix-funktioner. Vi kan fortælle Elm, at vi vil bruge en infix-funktion som en prefix-funktion ved at sætte parenteser omkring funktionsnavnet. Hvis vi virkelig vil, så kan vi altså lægge tal sammen på denne måde:

> (+) 2 3
5 : number
Hvis du synes, at det er meget funky, så pyt med det! I praksis bruger vi altid funktioner som + og ++ som infix-funktioner, for det er jo meget lettere at skrive:
> 2 + 3
5 : number

2.3.2 Moduler

Elm kommer med funktioner til mange forskellige formål. For at holde styr på dem, er de grupperet i moduler. Vi kan kalde sådanne funktioner ved først at angive navnet på et modul, og derefter navnet på den funktion, som vi gerne vi have fat i. For eksempel kan vi skrive:

> String.fromInt 2
"2" : String
for at kalde funktionen fromInt fra modulet String. Lad os lige bryde eksemplet ned, så vi forstår hvad der sker!

Allerførst undrer det dig måske, at der er et modul String, som hedder det præcis det samme som typen String. Det er der en grund til, og den er at modulet String indeholder alle de indbyggede funktioner, der kan bruges til at arbejde med værdier af typen String. Funktionen fromInt tager en værdi af typen Int og laver den om til en værdi af typen String.

String.fromInt : Int -> String

fromInt kan for eksempel bruges således:

favoriteInteger : String -> Int -> String
favoriteInteger name number = 
	name ++ "s yndlingstal er " ++ (String.fromInt number) ++ "!"
	

Øvelse

Implementer funktionen favoriteInteger i elm repl og prøv den, så du for eksempel kan skrive:

> favoriteInteger "Nicolai" 216
"Nicolais yndlingstal er 216!" : String

Modulet String indeholder mange andre funktioner, for eksempel repeat og length, der opfører sig således:

repeat: Int -> String -> String
repeat gentager en streng et antal gange:
> String.repeat 4 "hej"
"hejhejhejhej" : String
length : String -> Int
length tæller hvor lang en streng er:
> String.length "hej"
3 : Int

Øvelse

Definer en funktion copyCat

copyCat: String -> String
og returnerer en streng, hvor den gentager inputtet ligeså mange gange, som der er tegn i inputtet.
> copyCat "Hej"
"HejHejHej" : String
> copyCat "Hest"
"HestHestHestHest" : String