Ridgun
The language itself: a compiler that compiles itself, the stable project tool, the specification and the book
| 1 | # 04. kapitulua: Agur `if/else`-ri, agur `while`-ri (Eredu Bat-etortzea, Zainak eta TCO) |
| 2 | |
| 3 | Norbaiti kontatzen diozunean Ridgunen **ez dela existitzen `if` hitz gakoa, ez `else`, ez `switch`, ez `for`, ez `while`**, etxe bat mailurik etailtzerik gabe eraikiko duzula esan bazenio bezala begiratu ohi dizu: |
| 4 | |
| 5 | > *"Ez dagoela `if` ezta `while` ere? Eta nola arraio hartzen dut erabaki bat edo zeharkatzen ditut milioi bat datu?"* |
| 6 | |
| 7 | Bada, kontua da `if/else` eta `while` 50eko hamarkadan jauziek (`GOTO`) funtzionatzen zuten modutik oinordetzan hartutako tresna nahiko zakarrak direla. Ridgunen eskularru bat bezala doitzen diren hiru piezarekin ordezkatzen ditugu: |
| 8 | 1. **Eredu Bat-etortzea (*Pattern Matching*)** (datuaren **forma edo balio zehatzaren** arabera erabakitzen duzunean). |
| 9 | 2. **`|` zain boolearrak** (**baldintza edo tarte baten** arabera erabakitzen duzunean). |
| 10 | 3. **Isats-errekurtsioa TCOrekin** (konpiladoreak azpitik `loop` begizta natibo ultra-azkar bihurtzen duena). |
| 11 | |
| 12 | Ikus ditzagun martxan. |
| 13 | |
| 14 | --- |
| 15 | |
| 16 | ## 4.1. *Pattern Matching*: Argumentuak berak erantzuna ematen dizunean |
| 17 | |
| 18 | Aurreko kapituluan aurrerapen bat ikusi genuen. `x` aldagai generiko bat jaso eta funtzioaren barruan bost `if (x == ...)` ireki beharrean, Ridgunen kasuak zuzenean funtzioaren sarrerako atean jartzen dituzu: |
| 19 | |
| 20 | ```ridgun |
| 21 | http kodea Int => String |
| 22 | http kodea 200 = "OK: Dena primeran joan da" |
| 23 | http kodea 301 = "Betiko lekuz aldatua" |
| 24 | http kodea 404 = "Ez da aurkitu (begiratu duzu sofaren azpian?)" |
| 25 | http kodea 500 = "Sua zerbitzarian" |
| 26 | http kodea n = "Kode ezezaguna: {n}" |
| 27 | ``` |
| 28 | |
| 29 | Ridgunek errenkadak **goitik behera** probatzen ditu: |
| 30 | - `404` sartzen bada, zuzenean hirugarren errenkadara egiten du jauzi. |
| 31 | - `418` sartzen bada, lehenengo lau errenkadetako bat ere ez dator bat, beraz azkenekoan (`n`) erortzen da, zeinak edozein osoko harrapatzen duen eta `n` izendatzen duen inprimatu ahal izan dezazun. |
| 32 | - Eta azken kasuan zein zenbaki zen ere axola ez bazaizu? `_` azpimarra bat (*komodina*) jartzen duzu eta ahaztu egiten zara: `http kodea _ = "Beste kode bat"`. |
| 33 | |
| 34 | --- |
| 35 | |
| 36 | ## 4.2. Zain boolearrak (`|`): Zure bisturia tarteetarako, baldintzazko aldagaietarako eta erabaki-zuhaitzetarako |
| 37 | |
| 38 | *"Ados, pattern matching-a bikaina da `200` edo `404` bilatzen dudanean. Baina zer egiten dut `nota >= 5` edo `adina > 18 and adina < 65` den egiaztatu nahi badut? Ez ditut 47 errenkada idatziko 18tik 65erako zenbaki guztiekin!"* |
| 39 | |
| 40 | Noski ezetz! Horretarako daude **zainak (`|`)**. |
| 41 | |
| 42 | Emaitza desberdintza baten edo konbinazio logiko baten menpe dagoenean, funtzioaren (edo edozein aldagai lokalen) `=` ikurraren ondoren `|` barra bertikalen eskailera bat irekitzen duzu: |
| 43 | |
| 44 | ```ridgun |
| 45 | kalifikatu Int => String |
| 46 | kalifikatu nota = |
| 47 | | nota < 0 = "Nota ezinezkoa (puntu negatiboak?)" |
| 48 | | nota < 5 = "Gutxiegi" |
| 49 | | nota < 7 = "Nahiko" |
| 50 | | nota < 9 = "Oso ongi" |
| 51 | | nota <= 10 = "Bikain" |
| 52 | | otherwise = "Eskalatik pasa zara" |
| 53 | ``` |
| 54 | |
| 55 | Begiratu ondo: |
| 56 | - Goiburuko `=` ikurra (`kalifikatu nota =`) `=>` azpian lerrokatzen da beste edozein funtziotan bezala. |
| 57 | - Azpian goitik behera irakurtzen da tarifa-taula bat bezalaxe: `| baldintza = emaitza` lerro bakoitza ordenan egiaztatzen da. Batek `True` ematen duen bezain laster, emaitza hori itzultzen du eta kitto. |
| 58 | - Amaieran **`| otherwise = ...`** jartzen duzu, hitzez hitz *"beste edozein kasutan"* esan nahi duena (zure segurtasun-sarea, inoiz itzulerarik gabeko biderik gera ez dadin). |
| 59 | |
| 60 | ### Logika boolearra letra xehe argietan (`and`, `or`, `xor`, `not`) |
| 61 | Konturatu zara lengoaia askok bitetarako `|`, boolearretarako `||`, ukatzeko `!`... erabiltzen dituztela eta baldintza konplexu bat irakurtzen duzunean katu bat teklatuaren gainetik ibili dela dirudiela (`if (!a && (b || c))`)? |
| 62 | |
| 63 | Ridgunen, `|` dagoeneko zainen eta datu-mota aljebraikoen ikur sakratua denez, eta `||` paralelismo-eragilea denez, **eragile boolearrak letra xeheko hitz argiekin idazten dira**: |
| 64 | |
| 65 | - **`and`**: bi gauzak betetzen dira zirkuitulaburreko ebaluazioarekin (`adina >= 18 and gidabaimena_du`). |
| 66 | - **`or`**: gutxienez bat betetzen da zirkuitulaburreko ebaluazioarekin (`eguna == "larunbata" or eguna == "igandea"`). |
| 67 | - **`xor`**: bata edo bestea betetzen da, baina ez biak batera. |
| 68 | - **`not`**: boolearra alderantzikatzen du (`not (hutsik dago testua)`). |
| 69 | - **`<`, `>`, `<=`, `>=`**: zenbakiak (`Int`, `Float`) zein **`String` kateak ordena lexikografikoan (UTF-8)** alderatzen dituzte (`"alfa" < "beta"`). |
| 70 | |
| 71 | Begira zein garbi geratzen den bisurte-urteen problema klasikoa: |
| 72 | |
| 73 | ```ridgun |
| 74 | bisurtea da Int => Bool |
| 75 | bisurtea da u = |
| 76 | | (u % 400) == 0 = True |
| 77 | | (u % 4) == 0 and (u % 100) != 0 = True |
| 78 | | otherwise = False |
| 79 | ``` |
| 80 | |
| 81 | Zero ikur kriptiko. Ridgun ez dakien norbaitek irakurtzen du eta lehenengoan ulertzen du. |
| 82 | |
| 83 | ### Baldintzazko aldagai lokalak (`x =` + `|`) |
| 84 | Ridgunen `=` orok arau bera jarraitzen duenez, **`|` aldagai lokal bat esleitzeko ere erabil dezakezu**, aparteko funtzio laguntzaile bat sortu beharrik izan gabe: |
| 85 | |
| 86 | ```ridgun |
| 87 | txartel prezioa Int Bool => Int |
| 88 | txartel prezioa oinarria familia_ugaria_da = |
| 89 | beherapena = |
| 90 | | familia_ugaria_da = 20 |
| 91 | | oinarria > 100 = 10 |
| 92 | | otherwise = 0 |
| 93 | oinarria - (oinarria * beherapena / 100) |
| 94 | ``` |
| 95 | |
| 96 | ### Habiaratutako erabaki-zuhaitzak eta `|.` mugatzaile trinkoa |
| 97 | *"Eta `| cond =` edo `| otherwise =` adar baten barruan 3 edo 4 sakonera-mailako beste `|` zain-azpizuhaitz bat ireki nahi badut eta gero goiko maila batera itzuli?"* |
| 98 | |
| 99 | `|` barruan `|` habiaratu dezakezu nahi adina mailatan. Eta habiaratutako `|` bloke bat amaitzen denean baina azpian guraso-maila bateko adar gehiago (edo blokeko sententzia gehiago) jarraitzen dutenean, **`|.`** jartzen duzu bere azken lerroaren amaieran (`| otherwise = balioa |.`) inolako anbiguotasunik gabe adierazteko azpizuhaitz hori ixten dela eta aurreko mailara itzultzen zarela: |
| 100 | |
| 101 | ```ridgun |
| 102 | bideratu eskaera Int Int Bool Bool Int Int => Int |
| 103 | bideratu eskaera metodoa rola sinatua admin_da tamaina_kb mugatua = |
| 104 | | metodoa == 0 = |
| 105 | | tamaina_kb == 0 = 204 |
| 106 | | otherwise = 200 |. |
| 107 | | metodoa == 1 = |
| 108 | | not sinatua = 401 |
| 109 | | otherwise = |
| 110 | -- 2. maila: POST eskaera sinatua |
| 111 | | mugatua > 0 = |
| 112 | -- 3. maila: rate-limit kontrola |
| 113 | | admin_da = |
| 114 | -- 4. maila: salbuespena administratzaileentzat |
| 115 | | tamaina_kb > 10000 = 413 |
| 116 | | otherwise = 202 |. |
| 117 | | otherwise = 429 |. |
| 118 | | tamaina_kb > 1000 = |
| 119 | -- 3. maila: 4. mailatik itzuli gara `|.`-ri esker |
| 120 | | rola >= 2 and admin_da = 201 |
| 121 | | otherwise = 413 |. |
| 122 | | otherwise = 201 |. |
| 123 | | otherwise = 405 |
| 124 | ``` |
| 125 | |
| 126 | Erreparatu zein garbia den: |
| 127 | - Maila 1eko zuhaitz sinple batean (edo funtzio osoaren amaieran) **ez da inoiz `|.` jarri behar** (beharrezkoa ez den zarata litzateke). |
| 128 | - Habiaratutako adar bat ixtean soilik idazten duzu `|.`, azpian goiko maila bateko adar batek jarraitzen duenean. |
| 129 | |
| 130 | ### Lerro anitzeko blokeak zain baten barruan (`| cond =` + koskadun blokea) |
| 131 | Funtzio arrunt baten `=` ikurraren ondoren lerro anitzeko koskadun bloke bat ireki dezakezun bezalaxe, **edozein `| cond =` edo `| otherwise =` zainen `=` ikurraren ondoren ere lerro-jauzi bat egin dezakezu eta bloke sekuentzial bat ireki**: |
| 132 | |
| 133 | ```ridgun |
| 134 | doitu leihoa Int Int => Int |
| 135 | doitu leihoa idx muga = |
| 136 | | idx >= muga = |
| 137 | goia = muga - 1 |
| 138 | eskalatua = goia * 2 |
| 139 | eskalatua + 10 |
| 140 | | otherwise = |
| 141 | hurrengoa = idx + 1 |
| 142 | hurrengoa * 2 |
| 143 | ``` |
| 144 | |
| 145 | Zainaren adar bakoitzak bere bloke lokal garbia du eta bere azken lerroa adar horrek itzultzen duen balioa da. |
| 146 | |
| 147 | --- |
| 148 | |
| 149 | ## 4.3. "`while` gabe? Eta ez al da pila lehertuko fitxategi handi bat zeharkatzen badut?" |
| 150 | |
| 151 | Hitz egin dezagun gelan dagoen elefanteaz. |
| 152 | |
| 153 | Python, Java edo JavaScriptetik bazatoz, txiki-txikitatik irakatsi dizute *"errekurtsioa dotorea da unibertsitateko arbelean, baina mundu errealean pila gainezkatzen du (`StackOverflowError`) 1.000 biratan; erabili `while` begizta bat"*. |
| 154 | |
| 155 | Hori egia da isats-deiak optimizatzen **ez** dituzten lengoaietan. Baina Ridgun bezalako sistemetako lengoaia funtzional batean, **isats-errekurtsio bat funtzio puruz mozorrotutako `while` begizta bat DA**. |
| 156 | |
| 157 | ### Zein alde dago errekurtsio arrunt baten eta isats-errekurtsio baten artean? |
| 158 | Erreparatu `n`-tik `0`-ra atzeraka zenbatuz zenbakiak batzen dituzten bi funtzio hauei: |
| 159 | |
| 160 | ```ridgun |
| 161 | -- A bertsioa: Errekurtsio ARRUNTA (itzulerarako lana zintzilik uzten du) |
| 162 | batu motel Int => Int |
| 163 | batu motel 0 = 0 |
| 164 | batu motel n = n + (batu motel (n - 1)) |
| 165 | |
| 166 | -- B bertsioa: ISATS-errekurtsioa (kontua egunean darama `metatua` aldagaian) |
| 167 | batu azkar loop Int Int => Int |
| 168 | batu azkar loop 0 metatua = metatua |
| 169 | batu azkar loop n metatua = batu azkar loop (n - 1) (metatua + n) |
| 170 | ``` |
| 171 | |
| 172 | - **A bertsioan**, `n + ...` batu ahal izan aurretik, funtzioak `batu motel (n - 1)` amaitu arte itxaron behar du. Horrek ordenagailua urrats bakoitzean `n` pilan gordetzera behartzen du. |
| 173 | - **B bertsioan**, `batu azkar loop`-erako deia **lerroan gertatzen den azken gauza da**. Ez da ezer zintzilik geratzen haren atzean. Batura eguneratua pasatzen diozu dagoeneko `metatua + n`-n. |
| 174 | |
| 175 | ### `ridgunc`-en magia-trikimailua (TCO: *Tail-Call Optimization*) |
| 176 | `ridgunc`-ek `batu azkar loop` konpilatzen duenean, funtzioak klausularen amaieran bere buruari deitzen diola ikusten du eta honela dio: |
| 177 | |
| 178 | > *"Zertarako sortuko dut pila-marko berri bat oraingoak dagoeneko ezertarako balio ez badu? `n` gainidatziko dut `n - 1`-ekin, `metatua` gainidatziko dut `metatua + n`-rekin eta hasierara jauzi egingo dut `loop { ... continue; }` natibo batekin."* |
| 179 | |
| 180 | Emaitza: |
| 181 | 1. `batu azkar loop 100_000_000 0` (ehun milioi bira) deitu dezakezu eta **zehazki 0 pila-byte gehigarri gastatuko ditu**. |
| 182 | 2. Rust edo C-n eskuz idatzitako `while` begizta bat bezain azkar doa, baina zure Ridgun kodean aldagai aldakor bakar bat ere deklaratu behar izan gabe. |
| 183 | |
| 184 | --- |
| 185 | |
| 186 | ## 4.4. Ridgun programa orotan erabiliko duzun eredu maisua |
| 187 | |
| 188 | Beste lengoaia batean *"`while` begizta bat egingo dut `i` indize batekin eta metatzaile batekin"* pentsatuko zenukeen bakoitzean, Ridgunen honelako funtzio-bikote bat idazten duzu (funtzio nagusi garbia eta bere isatseko `_loop` laguntzailea): |
| 189 | |
| 190 | ```ridgun |
| 191 | bilatu karakterea loop String Int Int Int => Int |
| 192 | bilatu karakterea loop testua pos ama bilatua = |
| 193 | | pos >= ama = -1 |
| 194 | | (str char at testua pos) == bilatua = pos |
| 195 | | otherwise = bilatu karakterea loop testua (pos + 1) ama bilatua |
| 196 | |
| 197 | bilatu karakterea String Int => Int |
| 198 | bilatu karakterea testua bilatua = bilatu karakterea loop testua 0 (str len testua) bilatua |
| 199 | ``` |
| 200 | |
| 201 | Begira `bilatu karakterea loop`-en hiru zainei: |
| 202 | 1. Amaierara iritsi naiz (`pos >= ama`)? `-1` itzultzen dut (begiztaren amaiera). |
| 203 | 2. Karaktere hori `pos`-en aurkitu dut? `pos` itzultzen dut (begiztaren amaiera). |
| 204 | 3. Ez da hau? Hurrengora pasatzen naiz (`pos + 1`) isats-dei batekin (konpiladoreak `continue;` bihurtzen duena). |
| 205 | |
| 206 | Garbia, zapaldutako aldagai globalen *bug*-ak edukitzea ezinezkoa eta tximista bezain azkarra. |
| 207 | |
| 208 | **05. kapituluan** datu-bildumei helduko diegu: **zerrendak, tuplak, argumentu infinituak (`...`) onartzen dituzten funtzioak eta J/APL estiloko sekuentzia-maskarak**. |