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-e-gardas.md208 lines · 10.3 KBRaw
1# Capítulo 04: Adeus ao `if/else`, adeus ao `while` (Pattern Matching, Gardas e TCO)
2
3Cando lle contas a alguén que en Ridgun **non existe a palabra chave `if`, nin `else`, nin `switch`, nin `for`, nin `while`**, adoita mirarte coma se lle dixeses que vas construír unha casa sen martelo nin cravos:
4
5> *"Que non hai `if` nin `while`? E como demo tomo unha decisión ou percorro un millón de datos?"*
6
7Pois resulta que `if/else` e `while` son ferramentas bastante toscas herdadas de como funcionaban os saltos (`GOTO`) nos anos 50. En Ridgun substituímolos por tres pezas que encaixan coma unha luva:
81. ***Pattern Matching*** (cando decides segundo a **forma ou valor exacto** do dato).
92. **Gardas booleanas `|`** (cando decides segundo unha **condición ou rango**).
103. **Recursión de cola con TCO** (que o compilador converte por baixo nun bucle `loop` nativo ultrarrápido).
11
12Imos velas en acción.
13
14---
15
16## 4.1. *Pattern Matching*: Cando o argumento xa che dá a resposta
17
18No capítulo anterior xa vimos un adianto. En lugar de recibir unha variable xenérica `x` e abrir dentro da función cinco `if (x == ...)`, en Ridgun pos os casos directamente na porta de entrada da función:
19
20```ridgun
21codigo http Int => String
22codigo http 200 = "OK: Todo foi sobre rodas"
23codigo http 301 = "Movido permanentemente"
24codigo http 404 = "Non atopado (miraches debaixo do sofá?)"
25codigo http 500 = "Lume no servidor"
26codigo http n = "Código descoñecido: {n}"
27```
28
29Ridgun proba as filas **de arriba abaixo**:
30- Se entra `404`, salta directo á terceira fila.
31- Se entra `418`, ningunha das catro primeiras filas coincide, así que cae na última (`n`), que atrapa calquera enteiro e o nomea `n` para que poidas imprimilo.
32- E se no último caso nin sequera che importa que número era? Pos un guión baixo `_` (*comodín*) e esquéceste: `codigo http _ = "Outro código"`.
33
34---
35
36## 4.2. Gardas booleanas (`|`): O teu bisturí para rangos, variables condicionais e árbores de decisión
37
38*"Vale, o pattern matching mola cando busco o `200` ou o `404`. Pero que fago se quero comprobar se `nota >= 5` ou se `idade > 18 and idade < 65`? Non vou escribir 47 filas con todos os números do 18 ao 65!"*
39
40Claro que non! Para iso están as **gardas (`|`)**.
41
42Cando o resultado depende dunha desigualdade ou dunha combinación lóxica, despois do `=` da función (ou de calquera variable local) abres unha escaleira de barras verticais `|`:
43
44```ridgun
45cualificar Int => String
46cualificar nota =
47 | nota < 0 = "Nota imposible (puntos negativos?)"
48 | nota < 5 = "Suspenso"
49 | nota < 7 = "Aprobado"
50 | nota < 9 = "Notable"
51 | nota <= 10 = "Sobresaliente"
52 | otherwise = "Pasácheste da escala"
53```
54
55Mírao ben:
56- O signo `=` da cabeceira (`cualificar nota =`) alíñase debaixo de `=>` igual ca en calquera outra función.
57- Debaixo lese de arriba abaixo igual ca unha táboa de tarifas: cada liña `| condicion = resultado` compróbase en orde. En canto unha dá `True`, devolve ese resultado e rematou.
58- Ao final pos **`| otherwise = ...`**, que significa literalmente *"en calquera outro caso"* (a túa rede de seguridade para que nunca quede un camiño sen retorno).
59
60### Lóxica booleana en minúsculas claras (`and`, `or`, `xor`, `not`)
61Fixácheste en que moitas linguaxes usan `|` para bits, `||` para booleanos, `!` para negar... e cando les unha condición complexa parece que un gato camiñou sobre o teclado (`if (!a && (b || c))`)?
62
63En Ridgun, como `|` xa é o símbolo sagrado das gardas e dos tipos alxébricos, e `||` é o operador de paralelismo, **os operadores booleanos escríbense con palabras claras en minúsculas**:
64
65- **`and`**: cúmprense as dúas cousas con avaliación en curtocircuíto (`idade >= 18 and ten_carne`).
66- **`or`**: cúmprese polo menos unha con avaliación en curtocircuíto (`dia == "sabado" or dia == "domingo"`).
67- **`xor`**: cúmprese unha ou outra, pero non as dúas á vez.
68- **`not`**: inverte o booleano (`not (esta baleiro texto)`).
69- **`<`, `>`, `<=`, `>=`**: comparan tanto números (`Int`, `Float`) como cadeas **`String` en orde lexicográfica UTF-8** (`"alfa" < "beta"`).
70
71Mira que limpo queda o clásico problema dos anos bisestos:
72
73```ridgun
74e bisesto Int => Bool
75e bisesto a =
76 | (a % 400) == 0 = True
77 | (a % 4) == 0 and (a % 100) != 0 = True
78 | otherwise = False
79```
80
81Cero símbolos crípticos. Leo alguén que non sabe Ridgun e enténdeo á primeira.
82
83### Variables locais condicionais (`x =` + `|`)
84Como en Ridgun todo `=` segue a mesma regra, **tamén podes usar `|` para asignar unha variable local** sen ter que crear unha función auxiliar á parte:
85
86```ridgun
87prezo billete Int Bool => Int
88prezo billete base e_familia_numerosa =
89 desconto =
90 | e_familia_numerosa = 20
91 | base > 100 = 10
92 | otherwise = 0
93 base - (base * desconto / 100)
94```
95
96### Árbores de decisión aniñadas e o limitador compacto `|.`
97*"E se dentro dunha rama `| cond =` ou `| otherwise =` quero abrir outra sub-árbore de gardas `|` de 3 ou 4 niveis de profundidade e logo volver a un nivel superior?"*
98
99Podes aniñar `|` dentro de `|` tantos niveis como queiras. E cando un bloque `|` aniñado remata pero debaixo seguen máis ramas dun nivel pai (ou máis sentenzas do bloque), colocas **`|.`** ao final da súa última liña (`| otherwise = valor |.`) para indicar sen ningunha ambigüidade que esa sub-árbore se pecha e volves ao nivel anterior:
100
101```ridgun
102encaminar peticion Int Int Bool Bool Int Int => Int
103encaminar peticion metodo rol asinada e_admin tamano_kb limitado =
104 | metodo == 0 =
105 | tamano_kb == 0 = 204
106 | otherwise = 200 |.
107 | metodo == 1 =
108 | not asinada = 401
109 | otherwise =
110 -- Nivel 2: petición POST asinada
111 | limitado > 0 =
112 -- Nivel 3: control de rate-limit
113 | e_admin =
114 -- Nivel 4: excepción para administradores
115 | tamano_kb > 10000 = 413
116 | otherwise = 202 |.
117 | otherwise = 429 |.
118 | tamano_kb > 1000 =
119 -- Nivel 3: volvemos desde o Nivel 4 grazas a `|.`
120 | rol >= 2 and e_admin = 201
121 | otherwise = 413 |.
122 | otherwise = 201 |.
123 | otherwise = 405
124```
125
126Fíxate no limpo que é:
127- Nunha árbore simple de 1 nivel (ou ao final de toda a función) **nunca fai falta poñer `|.`** (sería ruído innecesario).
128- Só escribes `|.` ao pechar unha rama aniñada cando debaixo continúa unha rama dun nivel superior.
129
130### Bloques de varias liñas dentro dunha garda (`| cond =` + bloque sangrado)
131Igual que despois do `=` dunha función normal podes abrir un bloque sangrado de varias liñas, **despois do `=` de calquera garda `| cond =` ou `| otherwise =` tamén podes facer un salto de liña e abrir un bloque secuencial**:
132
133```ridgun
134axustar fiestra Int Int => Int
135axustar fiestra idx limite =
136 | idx >= limite =
137 tope = limite - 1
138 escalado = tope * 2
139 escalado + 10
140 | otherwise =
141 seguinte = idx + 1
142 seguinte * 2
143```
144
145Cada rama da garda ten o seu propio bloque local limpo e a súa última liña é o valor que devolve esa rama.
146
147---
148
149## 4.3. "Sen `while`? E non vai rebentar a pila se percorro un ficheiro grande?"
150
151Falemos do elefante na sala.
152
153Se vés de Python, Java ou JavaScript, ensináronche desde pequeniño que *"a recursión é elegante no encerado da universidade, pero no mundo real desborda a pila (`StackOverflowError`) ás 1.000 voltas; usa un bucle `while`"*.
154
155Iso é verdade en linguaxes que **non** optimizan as chamadas de cola. Pero nunha linguaxe funcional de sistemas como Ridgun, **unha recursión de cola É un bucle `while` disfrazado de función pura**.
156
157### Que diferenza hai entre unha recursión normal e unha recursión de cola?
158Fíxate nestas dúas funcións que contan cara a atrás desde `n` ata `0` sumando os números:
159
160```ridgun
161-- Versión A: Recursión NORMAL (deixa traballo pendente para a volta)
162sumar lento Int => Int
163sumar lento 0 = 0
164sumar lento n = n + (sumar lento (n - 1))
165
166-- Versión B: Recursión DE COLA (leva a conta ao día en `acumulado`)
167sumar rapido loop Int Int => Int
168sumar rapido loop 0 acumulado = acumulado
169sumar rapido loop n acumulado = sumar rapido loop (n - 1) (acumulado + n)
170```
171
172- Na **Versión A**, antes de poder sumar `n + ...`, a función ten que agardar a que remate `sumar lento (n - 1)`. Iso obriga ao ordenador a gardar `n` na pila en cada paso.
173- Na **Versión B**, a chamada a `sumar rapido loop` **é o último que ocorre na liña**. Non queda nada pendente detrás dela. Xa lle pasas a suma actualizada en `acumulado + n`.
174
175### O truco de maxia de `ridgunc` (TCO: *Tail-Call Optimization*)
176Cando `ridgunc` compila `sumar rapido loop`, ve que a función se chama a si mesma ao final da cláusula e di:
177
178> *"Para que vou crear un marco de pila novo se o actual xa non serve para nada? Vou sobrescribir `n` con `n - 1`, sobrescribir `acumulado` con `acumulado + n` e saltar ao principio cun `loop { ... continue; }` nativo."*
179
180Resultado:
1811. Podes chamar a `sumar rapido loop 100_000_000 0` (cen millóns de voltas) e **gastará exactamente 0 bytes extra de pila**.
1822. Vai igual de rápido ca un bucle `while` escrito á man en Rust ou C, pero sen ter que declarar nin unha soa variable mutable no teu código Ridgun.
183
184---
185
186## 4.4. O patrón mestre que usarás en todo programa Ridgun
187
188Cada vez que noutra linguaxe pensarías *"vou facer un bucle `while` cun índice `i` e un acumulador"*, en Ridgun escribes unha parella de funcións coma esta (a función principal limpa e a súa axudante `_loop` de cola):
189
190```ridgun
191buscar caracter loop String Int Int Int => Int
192buscar caracter loop texto pos fin buscado =
193 | pos >= fin = -1
194 | (str char at texto pos) == buscado = pos
195 | otherwise = buscar caracter loop texto (pos + 1) fin buscado
196
197buscar caracter String Int => Int
198buscar caracter texto buscado = buscar caracter loop texto 0 (str len texto) buscado
199```
200
201Mira as tres gardas de `buscar caracter loop`:
2021. Cheguei ao final (`pos >= fin`)? Devolvo `-1` (fin do bucle).
2032. Atopei o carácter en `pos`? Devolvo `pos` (fin do bucle).
2043. Non é este? Paso ao seguinte (`pos + 1`) cunha chamada de cola (que o compilador converte en `continue;`).
205
206Limpo, imposible de ter *bugs* de variables globais pisadas e rápido coma un raio.
207
208No **Capítulo 05** ímoslle meter man ás coleccións de datos: **listas, tuplas, funcións que aceptan infinitos argumentos (`...`) e as máscaras de secuencias estilo J/APL**.