Ridgun
The language itself: a compiler that compiles itself, the stable project tool, the specification and the book
| 1 | # Kapitel 05: Listor, tupler och ninjatricket med sekvenser |
| 2 | |
| 3 | Ett program som bara flyttar ett tal eller en text i taget gör inte mycket nytta. I verkliga livet hanterar du fillistor, rader från en databas, nätverkspaket eller koordinater. |
| 4 | |
| 5 | För det ger Ridgun dig tre olika behållare, var och en utformad för en mycket specifik uppgift: |
| 6 | 1. **Listor (`[T]`)**: *"Jag har många element av samma typ (eller inga alls) och vill gå igenom dem"*. |
| 7 | 2. **Tupler (`(A, B)`)**: *"Jag vill returnera ett par eller en trio av olika saker tillsammans utan att skapa en ny typ"*. |
| 8 | 3. **Sekvenser och masker (`s(_ + _)`, `(+ _ _ +)`)**: *"Jag vill filtrera eller stuva om argument i farten som om jag hade ett matematiskt durkslag"*. |
| 9 | |
| 10 | Vi tar dem en i taget. |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | ## 5.1. Listor (`[T]`): Vårt dagliga bröd |
| 15 | |
| 16 | Om du i Ridgun sätter hakparenteser runt en typ har du en lista av den typen: |
| 17 | - `[Int]` är en lista med heltal: `[10, 20, 30]`. |
| 18 | - `[String]` är en lista med texter: `["apple", "paron"]`. |
| 19 | - `[]` är den tomma listan (jokertecknet som passar till varje `[T]`). |
| 20 | |
| 21 | > *"Hallå, kan jag stoppa in ett tal och en text tillsammans i samma lista, typ `[1, "tva"]`?"* |
| 22 | > Som den står, **nej** (och tacka kompilatorn för att den hindrar dig, för på så sätt kommer en summering i produktion aldrig att explodera när den stöter på en oväntad text). I Kapitel 06 ska vi se hur man löser fallet `[1, "tva"]` med fullständig elegans med hjälp av en algebraisk datatyp (precis som i Haskell). |
| 23 | |
| 24 | ### Hur man delar upp en lista utan index `i`, `j`, `k`: `[]` och `(x:xs)` |
| 25 | I imperativa språk innebär det oftast `for (int i = 0; i < lista.size(); i++)` att gå igenom en lista, med den klassiska risken att räkna ett steg fel (*off-by-one*). |
| 26 | |
| 27 | I Ridgun räknar du inte index; **du plockar isär listan från huvudet**: |
| 28 | - `[]`: matchar om listan är tom. |
| 29 | - `(x:xs)`: matchar om listan har minst ett element. Den lägger det första elementet i `x` (**huvudet**) och resten av listan i `xs` (**svansen**). |
| 30 | |
| 31 | Titta så smidigt det är att summera en lista eller räkna hur många element den har: |
| 32 | |
| 33 | ```ridgun |
| 34 | summera lista [Int] => Int |
| 35 | summera lista [] = 0 |
| 36 | summera lista (x:xs) = x + (summera lista xs) |
| 37 | |
| 38 | rakna element [A] => Int |
| 39 | rakna element [] = 0 |
| 40 | rakna element (_:xs) = 1 + (rakna element xs) |
| 41 | ``` |
| 42 | |
| 43 | Och en teknisk detalj som kommer att göra dig glad: i Ridgun **kopierar inte `(x:xs)` resten av listan i minnet** när svansen `xs` plockas ut. Internt är det en vy över samma vektor som bara flyttar fram startpekaren med `1` (omedelbart i $O(1)$). |
| 44 | |
| 45 | ### Du kan plocka fram flera element i ett svep |
| 46 | Mönstret `:` är otroligt användbart när du tolkar kommandon eller protokoll: |
| 47 | |
| 48 | ```ridgun |
| 49 | tolka kommando [String] => String |
| 50 | tolka kommando [] = "Du har inte skrivit något" |
| 51 | tolka kommando ("hjalp":_) = "Visar hjälpen..." |
| 52 | tolka kommando ("kopiera":kalla:mal:[]) = "Kopierar från {kalla} till {mal}" |
| 53 | tolka kommando (cmd:_) = "Okänt kommando: {cmd}" |
| 54 | ``` |
| 55 | |
| 56 | Titta på tredje raden: `("kopiera":kalla:mal:[])` kontrollerar i ett enda slag att listan har **exakt 3 element**, att det första är `"kopiera"`, och serverar det andra och tredje på silverfat i `kalla` och `mal`. Försök skriva det i C eller Java så får du se hur många rader med `args.length == 3 && args[0].equals("kopiera")` det blir. |
| 57 | |
| 58 | ### Slå ihop listor med dubbla kolon (`::`) |
| 59 | När du befinner dig till höger om `=` (i ett uttryck) och vill foga ihop två listor använder du **`::`**: |
| 60 | |
| 61 | ```ridgun |
| 62 | foga listor [Int] [Int] => [Int] |
| 63 | foga listor a b = a :: b |
| 64 | |
| 65 | lagg till sist [Int] Int => [Int] |
| 66 | lagg till sist xs x = xs :: [x] |
| 67 | ``` |
| 68 | |
| 69 | *(Snabb minnesregel: ett enkelt `:` i mönster plockar isär listan; dubbla `::` i uttryck fogar ihop två listor).* |
| 70 | |
| 71 | Och om du verkligen vill hämta element nummer 5 direkt utan att stega igenom något? Använd hakparenteser direkt efter namnet: `min_lista[5]`. |
| 72 | |
| 73 | --- |
| 74 | |
| 75 | ## 5.2. Variadiska funktioner (`T...`): "Skicka in hur många du vill!" |
| 76 | |
| 77 | Ibland vill du att den som anropar din funktion bekvämt ska kunna skriva `max av 4 19 2 88 7`, utan att tvingas linda in talen i hakparenteser `[4, 19, 2, 88, 7]`. |
| 78 | |
| 79 | Sätt tre punkter **`...`** efter den sista typen i din signatur, så får du inuti funktionen automatiskt in argumenten paketerade som en lista: |
| 80 | |
| 81 | ```ridgun |
| 82 | summera flera Int... => Int |
| 83 | summera flera tal = summera lista tal |
| 84 | |
| 85 | main => () |
| 86 | main = |
| 87 | echo (summera flera 10 20 30) -- Skriver ut 60 |
| 88 | echo (summera flera 1 2 3 4 5 6 7) -- Skriver ut 28 |
| 89 | ``` |
| 90 | |
| 91 | --- |
| 92 | |
| 93 | ## 5.3. Tupler: När en funktion vill returnera mer än en sak |
| 94 | |
| 95 | I många språk är det en plåga att returnera två saker från en funktion: antingen skapar du en fånig hjälpklass `ResultatMedPosition`, eller så skickar du med utdatapekare via referens. |
| 96 | |
| 97 | I Ridgun returnerar du en **tupel** `(A, B)`: |
| 98 | |
| 99 | ```ridgun |
| 100 | kvot och rest Int Int => (Int, Int) |
| 101 | kvot och rest a b = (a / b, a % b) |
| 102 | ``` |
| 103 | |
| 104 | Du kan till och med spara in parenteserna på signaturraden om du tycker att det ser renare ut: |
| 105 | `kvot och rest Int Int => Int Int`. |
| 106 | |
| 107 | ### Hur man öppnar en tupel |
| 108 | Precis som du skapar den med `(a, b)` öppnar du den i argumenten till vilken funktion som helst med `(kvot, rest)`: |
| 109 | |
| 110 | ```ridgun |
| 111 | forklara division (Int, Int) => String |
| 112 | forklara division (kvot, rest) = "Det går {kvot} gånger och blir {rest} över" |
| 113 | ``` |
| 114 | |
| 115 | ### Stjärndetaljen: Automatisk uppackning mellan funktioner |
| 116 | Anta att du har `kvot och rest` (som returnerar en tupel med 2 heltal) och en annan funktion `summera par Int Int => Int` (som förväntar sig 2 fristående heltal). I Ridgun behöver du inte packa upp tupeln för hand i en mellanliggande variabel; du kan koppla in den ena funktionen rakt i den andra: |
| 117 | |
| 118 | ```ridgun |
| 119 | summera par Int Int => Int |
| 120 | summera par x y = x + y |
| 121 | |
| 122 | main => () |
| 123 | main = |
| 124 | -- kvot och rest 17 5 returnerar (3, 2), och summera par tar emot 3 och 2 direkt: |
| 125 | echo (summera par (kvot och rest 17 5)) -- Skriver ut 5 |
| 126 | ``` |
| 127 | |
| 128 | --- |
| 129 | |
| 130 | ## 5.4. Ninjatricket: Sekvenser och positionsmasker (`s(_ + _)`, `(+ _ _ +)`) |
| 131 | |
| 132 | Här kommer en av Ridguns mest originella juveler, ärvd rakt från vektorspråk som J och APL. |
| 133 | |
| 134 | Föreställ dig att du tar emot en rad argument med sekvensmönstret **`(s)`** och bara vill behålla några och kasta andra utan att deklarera fem variabler med påhittade namn (`_a`, `b`, `_c`, `d`...). |
| 135 | |
| 136 | Då lägger du på en **tupelmask med `+` (behåll) och `_` (kasta)** på `s`: |
| 137 | |
| 138 | - **`s(_ + _)`**: av 3 element, kasta det första (`_`), behåll det andra (`+`) och kasta det tredje (`_`). |
| 139 | - **`s(+ _ +)`**: behåll det första och det tredje; returnera sekvensen med dessa två. |
| 140 | - **`s(_ _ +...)`**: kasta de två första (`_ _`) och tillämpa `+...` **på alla efterföljande element**, oavsett hur många de är. |
| 141 | |
| 142 | Se hur det fungerar i praktiken: |
| 143 | |
| 144 | ```ridgun |
| 145 | den i mitten Int Int Int => Int |
| 146 | den i mitten (s) = s(_ + _) |
| 147 | |
| 148 | kasta tva forsta Int... => [Int] |
| 149 | kasta tva forsta (s) = s(_ _ +...) |
| 150 | |
| 151 | main => () |
| 152 | main = |
| 153 | echo (den i mitten 100 42 999) -- Skriver ut 42 |
| 154 | echo (kasta tva forsta 1 2 10 20 30) -- Skriver ut [10, 20, 30] |
| 155 | ``` |
| 156 | |
| 157 | Och det bästa av allt: du kan skjuta in en positionsmask **`(+ _ _ +)`** **rakt in i en idiomatisk `->`-pipeline**! Om en funktion returnerar en tupel med flera värden och nästa funktion bara behöver två av dem, filtrerar du tupeln i farten och Ridgun packar upp de överlevande elementen direkt som separata argument till nästa steg: |
| 158 | |
| 159 | ```ridgun |
| 160 | -- tolka paket returnerar 4 värden: (Int, Int, [String String], String) |
| 161 | -- (+ _ _ +) behåller det 1:a (code) och det 4:e (msg), och skickar dem till kombinera kod: |
| 162 | tolka paket "v0.9.9" -> (+ _ _ +) -> kombinera kod -> sammanfattning |
| 163 | ``` |
| 164 | |
| 165 | Och om du dessutom använder kommaoperatorn `,` utanför parenteser (`lista1 , lista2`), plattar Ridgun ut båda samlingarna till en enda sammanhängande sekvens. |
| 166 | |
| 167 | I **Kapitel 06** ska vi ta steget som förvandlar en funktionell programmerare till en förstklassig arkitekt: **att skapa dina egna algebraiska datatyper (ADT), skriva polymorfa funktioner med `A`..`Z` och skräddarsy dina egna operatorer**. |