Skip to content
Ridgun
English

Ridgun

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

04-pattern-matching-i-guardes.md208 lines · 10.4 KBRaw
1# Capítol 04: Adéu a l'`if/else`, adéu al `while` (Pattern Matching, Guardes i TCO)
2
3Quan li expliques a algú que en Ridgun **no existeix la paraula clau `if`, ni `else`, ni `switch`, ni `for`, ni `while`**, sol mirar-te com si li haguessis dit que construiràs una casa sense martell ni claus:
4
5> *"Que no hi ha `if` ni `while`? I com nassos prenc una decisió o recorro un milió de dades?"*
6
7Doncs resulta que `if/else` i `while` són eines força tosques heretades de com funcionaven els salts (`GOTO`) als anys 50. En Ridgun els substituïm per tres peces que encaixen com un guant:
81. ***Pattern Matching*** (quan decideixes segons la **forma o valor exacte** de la dada).
92. **Guardes booleanes `|`** (quan decideixes segons una **condició o rang**).
103. **Recursió de cua amb TCO** (que el compilador converteix per sota en un bucle `loop` natiu ultraràpid).
11
12Vegem-les en acció.
13
14---
15
16## 4.1. *Pattern Matching*: Quan l'argument ja et dóna la resposta
17
18Al capítol anterior ja en vam veure un avançament. En lloc de rebre una variable genèrica `x` i obrir dins de la funció cinc `if (x == ...)`, en Ridgun poses els casos directament a la porta d'entrada de la funció:
19
20```ridgun
21codi http Int => String
22codi http 200 = "OK: Tot ha anat sobre rodes"
23codi http 301 = "Mogut permanentment"
24codi http 404 = "No trobat (has mirat sota el sofà?)"
25codi http 500 = "Foc al servidor"
26codi http n = "Codi desconegut: {n}"
27```
28
29Ridgun prova les files **de dalt a baix**:
30- Si entra `404`, salta directe a la tercera fila.
31- Si entra `418`, cap de les quatre primeres files coincideix, així que cau a l'última (`n`), que atrapa qualsevol enter i l'anomena `n` perquè puguis imprimir-lo.
32- I si en l'últim cas ni tan sols t'importa quin número era? Poses un guió baix `_` (*comodí*) i te n'oblides: `codi http _ = "Un altre codi"`.
33
34---
35
36## 4.2. Guardes booleanes (`|`): El teu bisturí per a rangs, variables condicionals i arbres de decisió
37
38*"D'acord, el pattern matching mola quan busco el `200` o el `404`. Però què faig si vull comprovar si `nota >= 5` o si `edat > 18 and edat < 65`? No escriuré pas 47 files amb tots els números del 18 al 65!"*
39
40És clar que no! Per a això hi ha les **guardes (`|`)**.
41
42Quan el resultat depèn d'una desigualtat o d'una combinació lògica, després de l'`=` de la funció (o de qualsevol variable local) obres una escala de barres verticals `|`:
43
44```ridgun
45qualificar Int => String
46qualificar nota =
47 | nota < 0 = "Nota impossible (punts negatius?)"
48 | nota < 5 = "Suspès"
49 | nota < 7 = "Aprovat"
50 | nota < 9 = "Notable"
51 | nota <= 10 = "Excel·lent"
52 | otherwise = "T'has passat de l'escala"
53```
54
55Mira-t'ho bé:
56- El signe `=` de la capçalera (`qualificar nota =`) s'alinea sota `=>` igual que en qualsevol altra funció.
57- A sota es llegeix de dalt a baix igual que una taula de tarifes: cada línia `| condicio = resultat` es comprova en ordre. Tan bon punt una dóna `True`, retorna aquell resultat i s'ha acabat.
58- Al final poses **`| otherwise = ...`**, que significa literalment *"en qualsevol altre cas"* (la teva xarxa de seguretat perquè mai no quedi un camí sense retorn).
59
60### Lògica booleana en minúscules clares (`and`, `or`, `xor`, `not`)
61T'has fixat que molts llenguatges fan servir `|` per a bits, `||` per a booleans, `!` per negar... i quan llegeixes una condició complexa sembla que un gat hagi caminat sobre el teclat (`if (!a && (b || c))`)?
62
63En Ridgun, com que `|` ja és el símbol sagrat de les guardes i dels tipus algebraics, i `||` és l'operador de paral·lelisme, **els operadors booleans s'escriuen amb paraules clares en minúscules**:
64
65- **`and`**: es compleixen totes dues coses amb avaluació en curtcircuit (`edat >= 18 and te_carnet`).
66- **`or`**: se'n compleix almenys una amb avaluació en curtcircuit (`dia == "dissabte" or dia == "diumenge"`).
67- **`xor`**: es compleix l'una o l'altra, però no totes dues alhora.
68- **`not`**: inverteix el booleà (`not (es buit text)`).
69- **`<`, `>`, `<=`, `>=`**: comparen tant nombres (`Int`, `Float`) com cadenes **`String` en ordre lexicogràfic UTF-8** (`"alfa" < "beta"`).
70
71Mira que net queda el clàssic problema dels anys de traspàs:
72
73```ridgun
74es de traspas Int => Bool
75es de traspas a =
76 | (a % 400) == 0 = True
77 | (a % 4) == 0 and (a % 100) != 0 = True
78 | otherwise = False
79```
80
81Zero símbols críptics. Ho llegeix algú que no sap Ridgun i ho entén a la primera.
82
83### Variables locals condicionals (`x =` + `|`)
84Com que en Ridgun tot `=` segueix la mateixa regla, **també pots fer servir `|` per assignar una variable local** sense haver de crear una funció auxiliar a part:
85
86```ridgun
87preu bitllet Int Bool => Int
88preu bitllet base es_familia_nombrosa =
89 descompte =
90 | es_familia_nombrosa = 20
91 | base > 100 = 10
92 | otherwise = 0
93 base - (base * descompte / 100)
94```
95
96### Arbres de decisió imbricats i el limitador compacte `|.`
97*"I si dins d'una branca `| cond =` o `| otherwise =` vull obrir un altre subarbre de guardes `|` de 3 o 4 nivells de profunditat i després tornar a un nivell superior?"*
98
99Pots imbricar `|` dins de `|` tants nivells com vulguis. I quan un bloc `|` imbricat acaba però a sota continuen més branques d'un nivell pare (o més sentències del bloc), col·loques **`|.`** al final de la seva última línia (`| otherwise = valor |.`) per indicar sense cap ambigüitat que aquell subarbre es tanca i tornes al nivell anterior:
100
101```ridgun
102encaminar peticio Int Int Bool Bool Int Int => Int
103encaminar peticio metode rol signada es_admin mida_kb limitat =
104 | metode == 0 =
105 | mida_kb == 0 = 204
106 | otherwise = 200 |.
107 | metode == 1 =
108 | not signada = 401
109 | otherwise =
110 -- Nivell 2: petició POST signada
111 | limitat > 0 =
112 -- Nivell 3: control de rate-limit
113 | es_admin =
114 -- Nivell 4: excepció per a administradors
115 | mida_kb > 10000 = 413
116 | otherwise = 202 |.
117 | otherwise = 429 |.
118 | mida_kb > 1000 =
119 -- Nivell 3: hem tornat des del Nivell 4 gràcies a `|.`
120 | rol >= 2 and es_admin = 201
121 | otherwise = 413 |.
122 | otherwise = 201 |.
123 | otherwise = 405
124```
125
126Fixa't en com n'és, de net:
127- En un arbre simple d'1 nivell (o al final de tota la funció) **mai no cal posar `|.`** (seria soroll innecessari).
128- Només escrius `|.` en tancar una branca imbricada quan a sota continua una branca d'un nivell superior.
129
130### Blocs de diverses línies dins d'una guarda (`| cond =` + bloc sagnat)
131Igual que després de l'`=` d'una funció normal pots obrir un bloc sagnat de diverses línies, **després de l'`=` de qualsevol guarda `| cond =` o `| otherwise =` també pots fer un salt de línia i obrir un bloc seqüencial**:
132
133```ridgun
134ajustar finestra Int Int => Int
135ajustar finestra idx limit =
136 | idx >= limit =
137 topall = limit - 1
138 escalat = topall * 2
139 escalat + 10
140 | otherwise =
141 seguent = idx + 1
142 seguent * 2
143```
144
145Cada branca de la guarda té el seu propi bloc local net i la seva última línia és el valor que retorna aquella branca.
146
147---
148
149## 4.3. "Sense `while`? I no rebentarà la pila si recorro un fitxer gran?"
150
151Parlem de l'elefant a l'habitació.
152
153Si véns de Python, Java o JavaScript, t'han ensenyat des de ben petit que *"la recursió és elegant a la pissarra de la universitat, però al món real desborda la pila (`StackOverflowError`) a les 1.000 voltes; fes servir un bucle `while`"*.
154
155Això és veritat en llenguatges que **no** optimitzen les crides de cua. Però en un llenguatge funcional de sistemes com Ridgun, **una recursió de cua ÉS un bucle `while` disfressat de funció pura**.
156
157### Quina diferència hi ha entre una recursió normal i una recursió de cua?
158Fixa't en aquestes dues funcions que compten cap enrere des de `n` fins a `0` sumant els números:
159
160```ridgun
161-- Versió A: Recursió NORMAL (deixa feina pendent per a la tornada)
162sumar lent Int => Int
163sumar lent 0 = 0
164sumar lent n = n + (sumar lent (n - 1))
165
166-- Versió B: Recursió DE CUA (porta el compte al dia a `acumulat`)
167sumar rapid loop Int Int => Int
168sumar rapid loop 0 acumulat = acumulat
169sumar rapid loop n acumulat = sumar rapid loop (n - 1) (acumulat + n)
170```
171
172- A la **Versió A**, abans de poder sumar `n + ...`, la funció ha d'esperar que acabi `sumar lent (n - 1)`. Això obliga l'ordinador a desar `n` a la pila en cada pas.
173- A la **Versió B**, la crida a `sumar rapid loop` **és l'última cosa que passa a la línia**. No queda res pendent darrere seu. Ja li passes la suma actualitzada a `acumulat + n`.
174
175### El truc de màgia de `ridgunc` (TCO: *Tail-Call Optimization*)
176Quan `ridgunc` compila `sumar rapid loop`, veu que la funció es crida a si mateixa al final de la clàusula i diu:
177
178> *"Per a què crearé un marc de pila nou si l'actual ja no serveix per a res? Sobreescriuré `n` amb `n - 1`, sobreescriuré `acumulat` amb `acumulat + n` i saltaré al principi amb un `loop { ... continue; }` natiu."*
179
180Resultat:
1811. Pots cridar `sumar rapid loop 100_000_000 0` (cent milions de voltes) i **gastarà exactament 0 bytes extra de pila**.
1822. Va igual de ràpid que un bucle `while` escrit a mà en Rust o C, però sense haver hagut de declarar ni una sola variable mutable al teu codi Ridgun.
183
184---
185
186## 4.4. El patró mestre que faràs servir en tot programa Ridgun
187
188Cada vegada que en un altre llenguatge pensaries *"faré un bucle `while` amb un índex `i` i un acumulador"*, en Ridgun escrius una parella de funcions com aquesta (la funció principal neta i el seu ajudant `_loop` de cua):
189
190```ridgun
191cercar caracter loop String Int Int Int => Int
192cercar caracter loop text pos fi cercat =
193 | pos >= fi = -1
194 | (str char at text pos) == cercat = pos
195 | otherwise = cercar caracter loop text (pos + 1) fi cercat
196
197cercar caracter String Int => Int
198cercar caracter text cercat = cercar caracter loop text 0 (str len text) cercat
199```
200
201Mira les tres guardes de `cercar caracter loop`:
2021. He arribat al final (`pos >= fi`)? Retorno `-1` (fi del bucle).
2032. He trobat el caràcter a `pos`? Retorno `pos` (fi del bucle).
2043. No és aquest? Passo al següent (`pos + 1`) amb una crida de cua (que el compilador converteix en `continue;`).
205
206Net, impossible de tenir *bugs* de variables globals trepitjades i ràpid com un llamp.
207
208Al **Capítol 05** posarem fil a l'agulla amb les col·leccions de dades: **llistes, tuples, funcions que accepten infinits arguments (`...`) i les màscares de seqüències estil J/APL**.