Skip to content
Ridgun
English

Ridgun

The language itself: a compiler that compiles itself, the stable project tool, the specification and the book

04-monstermatchning-och-vakter.md208 lines · 10.8 KBRaw
1# Kapitel 04: Adjö till `if/else`, adjö till `while` (Mönstermatchning, vakter och TCO)
2
3Nä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
7Det 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:
81. **Mönstermatchning (*Pattern Matching*)** (när du väljer väg utifrån datans **form eller exakta värde**).
92. **Booleska vakter `|`** (när du väljer väg utifrån ett **villkor eller intervall**).
103. **Svansrekursion med TCO** (som kompilatorn under huven omvandlar till en blixtsnabb inbyggd `loop`-snurra).
11
12Låt oss se dem i aktion.
13
14---
15
16## 4.1. Mönstermatchning: När argumentet redan ger dig svaret
17
18I 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
21http kod Int => String
22http kod 200 = "OK: Allt gick som på räls"
23http kod 301 = "Permanent flyttad"
24http kod 404 = "Hittades inte (har du tittat under soffan?)"
25http kod 500 = "Det brinner i servern"
26http kod n = "Okänd kod: {n}"
27```
28
29Ridgun 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
40Naturligtvis inte! Det är precis vad **vakter (`|`)** är till för.
41
42Nä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
45bedom Int => String
46bedom 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
55Titta 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`)
61Har 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
63I 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
71Titta så rent det klassiska problemet med skottår blir:
72
73```ridgun
74ar skottar Int => Bool
75ar skottar a =
76 | (a % 400) == 0 = True
77 | (a % 4) == 0 and (a % 100) != 0 = True
78 | otherwise = False
79```
80
81Noll 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 =` + `|`)
84Eftersom 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
87biljettpris Int Bool => Int
88biljettpris 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
99Du 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
102dirigera anrop Int Int Bool Bool Int Int => Int
103dirigera 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
126Lä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)
131Precis 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
134justera fonster Int Int => Int
135justera 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
145Varje 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
151Låt oss prata om elefanten i rummet.
152
153Om 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
155Det 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?
158Titta 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)
162summera langsamt Int => Int
163summera langsamt 0 = 0
164summera langsamt n = n + (summera langsamt (n - 1))
165
166-- Version B: SVANSREKURSION (håller räkningen uppdaterad i `ackumulerat`)
167summera snabbt loop Int Int => Int
168summera snabbt loop 0 ackumulerat = ackumulerat
169summera 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*)
176Nä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
180Resultat:
1811. 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**.
1822. 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
188Varje 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
191sok tecken loop String Int Int Int => Int
192sok 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
197sok tecken String Int => Int
198sok tecken text sokt = sok tecken loop text 0 (str len text) sokt
199```
200
201Titta på de tre vakterna i `sok tecken loop`:
2021. Har jag nått slutet (`pos >= slut`)? Returnera `-1` (slut på loopen).
2032. Hittade jag tecknet på `pos`? Returnera `pos` (slut på loopen).
2043. Är det inte det här tecknet? Gå vidare till nästa (`pos + 1`) med ett svansanrop (som kompilatorn omvandlar till `continue;`).
205
206Rent, omöjligt att få buggar från överskrivna globala variabler och snabbt som blixten.
207
208I **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**.