Ridgun
The language itself: a compiler that compiles itself, the stable project tool, the specification and the book
| 1 | # Kapitel 04: Adjö till `if/else`, adjö till `while` (Mönstermatchning, vakter och TCO) |
| 2 | |
| 3 | När du berättar för någon att det i Ridgun **varken finns nyckelorden `if`, `else`, `switch`, `for` eller `while`**, brukar de titta på dig som om du just sagt att du tänker bygga ett hus utan hammare och spik: |
| 4 | |
| 5 | > *"Finns det inget `if` och inget `while`? Hur i hela friden fattar jag då ett beslut eller går igenom en miljon datapunkter?"* |
| 6 | |
| 7 | Det visar sig att `if/else` och `while` är ganska grovhuggna verktyg som vi har ärvt från hur hoppinstruktioner (`GOTO`) fungerade på 50-talet. I Ridgun ersätter vi dem med tre pusselbitar som passar som handen i handsken: |
| 8 | 1. **Mönstermatchning (*Pattern Matching*)** (när du väljer väg utifrån datans **form eller exakta värde**). |
| 9 | 2. **Booleska vakter `|`** (när du väljer väg utifrån ett **villkor eller intervall**). |
| 10 | 3. **Svansrekursion med TCO** (som kompilatorn under huven omvandlar till en blixtsnabb inbyggd `loop`-snurra). |
| 11 | |
| 12 | Låt oss se dem i aktion. |
| 13 | |
| 14 | --- |
| 15 | |
| 16 | ## 4.1. Mönstermatchning: När argumentet redan ger dig svaret |
| 17 | |
| 18 | I föregående kapitel fick vi redan en förhandstitt. I stället för att ta emot en generisk variabel `x` och öppna fem `if (x == ...)` inuti funktionen lägger du i Ridgun fallen direkt vid funktionens ingång: |
| 19 | |
| 20 | ```ridgun |
| 21 | http kod Int => String |
| 22 | http kod 200 = "OK: Allt gick som på räls" |
| 23 | http kod 301 = "Permanent flyttad" |
| 24 | http kod 404 = "Hittades inte (har du tittat under soffan?)" |
| 25 | http kod 500 = "Det brinner i servern" |
| 26 | http kod n = "Okänd kod: {n}" |
| 27 | ``` |
| 28 | |
| 29 | Ridgun provar raderna **uppifrån och ner**: |
| 30 | - Om `404` kommer in hoppar den direkt till tredje raden. |
| 31 | - Om `418` kommer in stämmer ingen av de fyra första raderna, så den faller ner till den sista (`n`), som fångar upp vilket heltal som helst och ger det namnet `n` så att du kan skriva ut det. |
| 32 | - Och om du i det sista fallet inte ens bryr dig om vilket tal det var? Då sätter du ett understreck `_` (*jokertecken*) och glömmer det: `http kod _ = "Annan kod"`. |
| 33 | |
| 34 | --- |
| 35 | |
| 36 | ## 4.2. Booleska vakter (`|`): Din skalpell för intervall, villkorsstyrda variabler och beslutsträd |
| 37 | |
| 38 | *"Okej, mönstermatchning är häftigt när jag letar efter `200` eller `404`. Men vad gör jag om jag vill kontrollera om `betyg >= 5` eller om `alder > 18 and alder < 65`? Jag tänker ju inte skriva 47 rader med alla tal från 18 till 65!"* |
| 39 | |
| 40 | Naturligtvis inte! Det är precis vad **vakter (`|`)** är till för. |
| 41 | |
| 42 | När resultatet beror på en olikhet eller en logisk kombination öppnar du efter funktionens `=` (eller efter vilken lokal variabel som helst) en trappa av lodräta streck `|`: |
| 43 | |
| 44 | ```ridgun |
| 45 | bedom Int => String |
| 46 | bedom betyg = |
| 47 | | betyg < 0 = "Omöjligt betyg (minuspoäng?)" |
| 48 | | betyg < 5 = "Underkänt" |
| 49 | | betyg < 7 = "Godkänt" |
| 50 | | betyg < 9 = "Väl godkänt" |
| 51 | | betyg <= 10 = "Mycket väl godkänt" |
| 52 | | otherwise = "Du har sprängt skalan" |
| 53 | ``` |
| 54 | |
| 55 | Titta noga: |
| 56 | - Likhetstecknet `=` i rubrikraden (`bedom betyg =`) justeras rakt under `=>` precis som i vilken annan funktion som helst. |
| 57 | - Därunder läses det uppifrån och ner precis som en pristabell: varje rad `| villkor = resultat` kontrolleras i tur och ordning. Så fort en ger `True` returneras det resultatet och allt är klart. |
| 58 | - På slutet sätter du **`| otherwise = ...`**, som bokstavligen betyder *"i alla andra fall"* (ditt skyddsnät så att ingen väg någonsin blir utan returvärde). |
| 59 | |
| 60 | ### Boolesk logik med tydliga gemener (`and`, `or`, `xor`, `not`) |
| 61 | Har du lagt märke till att många språk använder `|` för bitar, `||` för booleska värden, `!` för negation... och när du läser ett komplext villkor ser det ut som om en katt har promenerat över tangentbordet (`if (!a && (b || c))`)? |
| 62 | |
| 63 | I Ridgun, där `|` redan är den heliga symbolen för vakter och algebraiska datatyper och `||` är parallellitetsoperatorn, **skrivs de booleska operatorerna med tydliga ord i gemener**: |
| 64 | |
| 65 | - **`and`**: båda sakerna är uppfyllda med kortslutningsutvärdering (`alder >= 18 and har_korkort`). |
| 66 | - **`or`**: minst en är uppfylld med kortslutningsutvärdering (`dag == "lordag" or dag == "sondag"`). |
| 67 | - **`xor`**: antingen det ena eller det andra är uppfyllt, men inte båda samtidigt. |
| 68 | - **`not`**: vänder på det booleska värdet (`not (ar tom text)`). |
| 69 | - **`<`, `>`, `<=`, `>=`**: jämför både tal (`Int`, `Float`) och **`String`-strängar i lexikografisk UTF-8-ordning** (`"alfa" < "beta"`). |
| 70 | |
| 71 | Titta så rent det klassiska problemet med skottår blir: |
| 72 | |
| 73 | ```ridgun |
| 74 | ar skottar Int => Bool |
| 75 | ar skottar a = |
| 76 | | (a % 400) == 0 = True |
| 77 | | (a % 4) == 0 and (a % 100) != 0 = True |
| 78 | | otherwise = False |
| 79 | ``` |
| 80 | |
| 81 | Noll kryptiska symboler. Någon som inte kan Ridgun kan läsa det och förstå det vid första anblicken. |
| 82 | |
| 83 | ### Villkorsstyrda lokala variabler (`x =` + `|`) |
| 84 | Eftersom varje `=` i Ridgun följer samma regel **kan du också använda `|` för att tilldela en lokal variabel** utan att behöva skapa en separat hjälpfunktion: |
| 85 | |
| 86 | ```ridgun |
| 87 | biljettpris Int Bool => Int |
| 88 | biljettpris bas ar_storfamilj = |
| 89 | rabatt = |
| 90 | | ar_storfamilj = 20 |
| 91 | | bas > 100 = 10 |
| 92 | | otherwise = 0 |
| 93 | bas - (bas * rabatt / 100) |
| 94 | ``` |
| 95 | |
| 96 | ### Nästlade beslutsträd och den kompakta avgränsaren `|.` |
| 97 | *"Men tänk om jag inuti en gren `| villkor =` eller `| otherwise =` vill öppna ett nytt underträd av vakter `|` på 3 eller 4 nivåers djup och sedan återvända till en högre nivå?"* |
| 98 | |
| 99 | Du kan nästla `|` inuti `|` hur många nivåer du vill. Och när ett nästlat `|`-block avslutas men fler grenar på en föräldranivå (eller fler satser i blocket) följer nedanför, sätter du **`|.`** i slutet av dess sista rad (`| otherwise = varde |.`) för att utan minsta tvetydighet markera att underträdet stängs och att du återvänder till föregående nivå: |
| 100 | |
| 101 | ```ridgun |
| 102 | dirigera anrop Int Int Bool Bool Int Int => Int |
| 103 | dirigera anrop metod roll signerad ar_admin storlek_kb begransad = |
| 104 | | metod == 0 = |
| 105 | | storlek_kb == 0 = 204 |
| 106 | | otherwise = 200 |. |
| 107 | | metod == 1 = |
| 108 | | not signerad = 401 |
| 109 | | otherwise = |
| 110 | -- Nivå 2: signerat POST-anrop |
| 111 | | begransad > 0 = |
| 112 | -- Nivå 3: kontroll av rate-limit |
| 113 | | ar_admin = |
| 114 | -- Nivå 4: undantag för administratörer |
| 115 | | storlek_kb > 10000 = 413 |
| 116 | | otherwise = 202 |. |
| 117 | | otherwise = 429 |. |
| 118 | | storlek_kb > 1000 = |
| 119 | -- Nivå 3: vi har återvänt från Nivå 4 tack vare `|.` |
| 120 | | roll >= 2 and ar_admin = 201 |
| 121 | | otherwise = 413 |. |
| 122 | | otherwise = 201 |. |
| 123 | | otherwise = 405 |
| 124 | ``` |
| 125 | |
| 126 | Lägg märke till hur rent det är: |
| 127 | - I ett enkelt träd med 1 nivå (eller i slutet av hela funktionen) **behöver du aldrig sätta ut `|.`** (det vore bara onödigt brus). |
| 128 | - Du skriver bara `|.` när du stänger en nästlad gren och en gren på en högre nivå fortsätter nedanför. |
| 129 | |
| 130 | ### Flerradiga block inuti en vakt (`| villkor =` + indraget block) |
| 131 | Precis som du efter `=` i en vanlig funktion kan öppna ett indraget block på flera rader, **kan du efter `=` i vilken vakt som helst (`| villkor =` eller `| otherwise =`) också göra en radbrytning och öppna ett sekventiellt block**: |
| 132 | |
| 133 | ```ridgun |
| 134 | justera fonster Int Int => Int |
| 135 | justera fonster idx grans = |
| 136 | | idx >= grans = |
| 137 | tak = grans - 1 |
| 138 | skalad = tak * 2 |
| 139 | skalad + 10 |
| 140 | | otherwise = |
| 141 | nasta = idx + 1 |
| 142 | nasta * 2 |
| 143 | ``` |
| 144 | |
| 145 | Varje gren i vakten har sitt eget rena lokala block, och dess sista rad är värdet som den grenen returnerar. |
| 146 | |
| 147 | --- |
| 148 | |
| 149 | ## 4.3. "Utan `while`? Kommer inte stacken att sprängas om jag går igenom en stor fil?" |
| 150 | |
| 151 | Låt oss prata om elefanten i rummet. |
| 152 | |
| 153 | Om du kommer från Python, Java eller JavaScript har du fått lära dig sedan barnsben att *"rekursion är elegant på svarta tavlan på universitetet, men i verkligheten svämmar stacken över (`StackOverflowError`) efter 1 000 varv; använd en `while`-loop"*. |
| 154 | |
| 155 | Det stämmer i språk som **inte** optimerar svansanrop. Men i ett funktionellt systemspråk som Ridgun **ÄR svansrekursion en `while`-loop förklädd till en ren funktion**. |
| 156 | |
| 157 | ### Vad är skillnaden mellan vanlig rekursion och svansrekursion? |
| 158 | Titta på dessa två funktioner som räknar baklänges från `n` till `0` och summerar talen: |
| 159 | |
| 160 | ```ridgun |
| 161 | -- Version A: VANLIG rekursion (lämnar kvar väntande arbete till tillbakavägen) |
| 162 | summera langsamt Int => Int |
| 163 | summera langsamt 0 = 0 |
| 164 | summera langsamt n = n + (summera langsamt (n - 1)) |
| 165 | |
| 166 | -- Version B: SVANSREKURSION (håller räkningen uppdaterad i `ackumulerat`) |
| 167 | summera snabbt loop Int Int => Int |
| 168 | summera snabbt loop 0 ackumulerat = ackumulerat |
| 169 | summera snabbt loop n ackumulerat = summera snabbt loop (n - 1) (ackumulerat + n) |
| 170 | ``` |
| 171 | |
| 172 | - I **Version A** måste funktionen vänta på att `summera langsamt (n - 1)` blir klar innan den kan beräkna `n + ...`. Det tvingar datorn att spara `n` på stacken i varje steg. |
| 173 | - I **Version B** är anropet till `summera snabbt loop` **det absolut sista som händer på raden**. Inget väntande arbete finns kvar efter det. Du skickar redan med den uppdaterade summan i `ackumulerat + n`. |
| 174 | |
| 175 | ### Trolltricket i `ridgunc` (TCO: *Tail-Call Optimization*) |
| 176 | När `ridgunc` kompilerar `summera snabbt loop` ser den att funktionen anropar sig själv i slutet av klausulen och tänker: |
| 177 | |
| 178 | > *"Varför skulle jag skapa en ny stackram när den nuvarande inte längre behövs till någonting? Jag skriver över `n` med `n - 1`, skriver över `ackumulerat` med `ackumulerat + n` och hoppar tillbaka till början med en inbyggd `loop { ... continue; }`."* |
| 179 | |
| 180 | Resultat: |
| 181 | 1. Du kan anropa `summera snabbt loop 100_000_000 0` (hundra miljoner varv) och **det kommer att förbruka exakt 0 extra byte på stacken**. |
| 182 | 2. Det går lika snabbt som en handskriven `while`-loop i Rust eller C, men utan att du har behövt deklarera en enda muterbar variabel i din Ridgun-kod. |
| 183 | |
| 184 | --- |
| 185 | |
| 186 | ## 4.4. Mästarmönstret som du kommer att använda i varje Ridgun-program |
| 187 | |
| 188 | Varje gång du i ett annat språk skulle tänka *"jag gör en `while`-loop med ett index `i` och en ackumulator"*, skriver du i Ridgun ett funktionspar som detta (den rena huvudfunktionen och dess svansrekursiva `_loop`-hjälpare): |
| 189 | |
| 190 | ```ridgun |
| 191 | sok tecken loop String Int Int Int => Int |
| 192 | sok tecken loop text pos slut sokt = |
| 193 | | pos >= slut = -1 |
| 194 | | (str char at text pos) == sokt = pos |
| 195 | | otherwise = sok tecken loop text (pos + 1) slut sokt |
| 196 | |
| 197 | sok tecken String Int => Int |
| 198 | sok tecken text sokt = sok tecken loop text 0 (str len text) sokt |
| 199 | ``` |
| 200 | |
| 201 | Titta på de tre vakterna i `sok tecken loop`: |
| 202 | 1. Har jag nått slutet (`pos >= slut`)? Returnera `-1` (slut på loopen). |
| 203 | 2. Hittade jag tecknet på `pos`? Returnera `pos` (slut på loopen). |
| 204 | 3. Är det inte det här tecknet? Gå vidare till nästa (`pos + 1`) med ett svansanrop (som kompilatorn omvandlar till `continue;`). |
| 205 | |
| 206 | Rent, omöjligt att få buggar från överskrivna globala variabler och snabbt som blixten. |
| 207 | |
| 208 | I **Kapitel 05** ska vi ta oss an datasamlingar: **listor, tupler, funktioner som tar emot oändligt många argument (`...`) och sekvensmasker i J/APL-stil**. |