Flere typer
I det her kapitel skal vi se på tre typer, som er meget gængse i programmeringssprog.
Boolske værdier
Typen Bool har kun to værdier: True og False. Den er altså meget anderledes end de typer, som vi har set indtil nu, men den kan bruges til meget interesante ting, for eksempel at lave logik og sammenligne sandhedsværdien af forskellige udtryk.
Inden vi giver det første eksempel introducerer vi endnu en infix hjælpefunktion, der er indbygget i Elm. Funktionen > tager to tal som indput og returnerer True, hvis de første tal er større end det andet, og False, hvis det andet tal er større eller lig med det første. Med andre ord, så gør > præcis det vi forventer:
Elm
> 5 > 3
True : Bool
> 6 > 8
False : Bool
Nu kan vi lave en funktion, der tjekker om et givent input er større end 7:
largerThanSeven : Int -> Bool
largerThanSeven x =
x > 7
Som giver følgende output, når vi tester den:
> largerThanSeven 9
True : Bool
> largerThanSeven 5
False : Bool
Indrømmet, så fedt er det heller ikke. Men bare vent! At tjekke og sammenligne sandhedsværdien af forskellige værdier er ekstemt nyttigt i programmering. For at kunne gøre det har vi brug for flere funktioner, der sammenligner værdier. De kommer her:
-
Tilsvarende
>findes der naturligvis en funktion<, der returnererTruehvis det første tal er mindst, ogFalse, hvis det første tal er lig eller større end det andet tal.Elm
> 3 < 4 True : Bool > 10 < 6 False : Bool > -
Hvis man gerne vil tjekke om to værdier ens, så bruger man
==. Man bruger dobbelt lighedtegn, fordi et enkelt ligstegn bliver brugt til at angive funktionsforskrifter. Læg mærke til, at man kan sammeligne værdier af mange forskellige typer, men ikke to værdier af forskellig type.Elm
> "hej" == "hej" True : Bool > 3 == 4 False : Bool > "hej" == 4 -- TYPE MISMATCH ------------------------------------- REPL I need both sides of (==) to be the same type: 7| "hej" == 4 -
Infix funktionen
&&tager to værdier af typenBoolog tjekker, om de begge to er sande:Elm
> True && True True : Bool > True && False False : Bool > (5<7) && (7<11) True : Bool
Int ligger i et givent interval:
Elm
inInterval : Int -> Bool
inInterval number =
(2 < number) && (number < 7)
Vi kan foreksempel tjekke:
Elm
> inInterval 5
True : Bool
> inInterval 1
False : Bool
>
Lister
En liste er hvad man kunne forvente, nemlig en ordnet liste af objekter adskilt af kommaer. Her er nogle eksempler:
Elm
> names = ["Peter", "Ellen", "Hans"]
["Peter", "Ellen", "Hans"] : List String
> myNumbers = [3.1, 2.7]
[3.1, 2.7] : List Float
Vi har givet den første liste navnet names, og den indeholder tre Strings. Det stemmer selvfølgelig overens med at listen har typen List String. Eller sagt på en anden måde: names er en liste som består af elementer af typen String. Tilsvarende med det andet eksempel. Bemærk at en liste kun kan indeholde elementer af samme type og at navne på lister starter med småt.
Vi kan bruge en række funktioner på lister. Alle listefunktionerne kommer fra et modul der hedder List. Et modul er en samling af funktioner, som man kan hente ind i sit program. Modulet List er så normalt at det altid er hentet ind i Elm, så vi kan bruge det uden videre. Funktioner i List-modulet har altid navne på formen List.< beskrivelseAfFunktionen >. Se følgende eksempler:
Elm
> names = ["Peter", "Ellen", "Hans"]
["Peter", "Ellen", "Hans"] : List String
> List.length names
3 : Int
> List.take 2 [1.0, 2.5, 3.14]
[1.0, 2.5] : List Float
> List.drop 2 [1.0, 2.5, 3.14]
[3.14] : List Float
> List.repeat 3 "Banan"
["Banan", "Banan", "Banan"] : List String
De fleste af funktionsnavnene giver sig selv. List.length tager en liste og returnerer længden af listen. List.take tager en Int og en liste og returnerer en liste med et antal elementer fra listen. List.drop tager en Int og en liste og returnerer en liste hvor et antal elementer fra listen er fjernet. List.repeat tager en Int og et objekt og returnerer en liste med det objekt gentaget et antal gange.
Der er to andre listefunktioner, som vi skal se på. Den første er meget simpel, men det er faktisk en infix funktion.
Elm
> True :: [True, False]
[True, True, False] : List Bool
> "a" :: ["b"]
["a", "b"] : List String
Som I kan se ovenfor tager :: et element og en liste og returnerer en liste hvor vi har tilføjet elementet til listen. Så kort sagt til bruger vi :: når vi vil tilføje elementer til lister.
- Kan vi tilføje elementer til en tom liste?
- Kan vi tilføje flere elementer til en liste i et hug ved at bruge
::flere gange i samme linje?
Den sidste funktion er List.map. Den tager en funktion og en liste som input og returnerer en liste hvor funktionen er blevet brugt på alle elementerne i listen.
Elm
> doubleMe : Int -> Int
| doubleMe x =
| x + x
|
< function > : Int -> Int
> List.map doubleMe [1,2,3]
[2,4,6] : List Int
List.map er rigtig smart når vi gerne vil gøre det samme ved alle elementerne i en liste, men vi har ikke lyst til at bruge List.take en hel masse gange. Det ser vi anvendelser af senere.
Prøv at lave en liste som indeholder flere lister (lister inde i lister). Hvad for en type har den yderste liste?
Records
Lister er ordnede, men det er for eksempel ikke muligt kun at tilgå det tredje element i en lang liste. Og tit kunne vi godt tænke os at knytte en beskrivelse til elementerne i en liste udover bare deres placering i listen. Det er her records kommer ind i billedet. Lad os se et par eksempler.
Elm
> person = {age = 38, firstName = "Peter", lastName = "Hansen"}
{ age = 38, firstName = "Peter", lastName = "Hansen" }
: { age : number, firstName : String, lastName : String }
En record angives med Tuborg-parenteser og indeholder en række felter separeret med komma. Hvert felt er på formen navn = objekt. For eksempel har vi ovenfor defineret en record som har et felt med navnet age og objektet 38. Den har også to andre felter. Igen skal navnene inde i en record starte med småt. I eksemplet ovenfor kan vi også se at vores record har typen { age : number, firstName : String, lastName : String }. Sagt med ord så har vi altså at gøre med en record med tre felter: Feltet ved navn age har typen number (husk at number er synonym for Int eller Float), feltet ved navn firstName har typen String og feltet ved navn lastName har typen String.
Records er smarte fordi vi både kan tilgå og ændre objekterne i de enkelte felter uden at lave om på de andre felter. Det gør vi sådan her. Lad os først se hvordan vi tilgår informationen i en record. Vi bruger recorden person fra før.
Elm
> person.firstName
"Peter" : String
Så for at hive informationen i et felt ud af en record, så sætter vi et punktum efter recordens navn og skriver navnet på det felt, vi gerne vil vide noget om.
Vi kan også ændre et enkelt felt i en record uden at ændre de andre felter. Det gør vi sådan her. Bemærk hvor meget det ligner mængdenotation.
Elm
> { person | age = 87}
{ age = 87, firstName = "Peter", secondName = "Hansen" }
: { age : number, firstName : String, secondName : String }
Byg en record der angiver et SET-kort der er rødt, fyldt, diamantformet og med to symboler. Overvej: Hvor mange felter skal der være i din record? Hvad er rimelige navne på felterne? Giv din record et navn.
Type alias
Som vi så ovenover, så får records hurtigt nogle meget komplicerede typer. Eksempelvis så vi at person = {age = 38, firstName = "Peter", lastName = "Hansen"} har typen { age : number, firstName : String, lastName : String }. Det ser altså ud som om at typen også er en record. Det er den ikke helt, for i stedet for 38 står der number, og i stedet for "Peter" og "Hansen" står der String. Vi kan i hvert fald hurtigt blive enige om at det er en bøvlet type at skulle skrive igen og igen. Prøv at se den her funktion, som afgør om en person er gammel nok til at stemme:
Elm
> isOldEnoughToVote : { age : Int, firstName : String, lastName : String } -> Bool
| isOldEnoughToVote user =
| user.age >= 18
|
< function > : { age : Int, firstName : String, lastName : String } -> Bool
> isOldEnoughToVote person
True
Funktionen virker fint, men inputtet til funktionen har en meget uoverskuelig type. Løsningen er at give typen en forkortelse. Det gør vi med kommandoen type alias.
Elm
> type alias User =
| { age : Int, firstName : String, lastName : String }
> isOldEnoughToVote : User -> Bool
| isOldEnoughToVote user =
| user.age >= 18
|
< function > : User -> Bool
> isOldEnoughToVote person
True