Ridgun
The language itself: a compiler that compiles itself, the stable project tool, the specification and the book
| 1 | # 06. kapitulua: Datu-Mota Aljebraikoak (ADTak), Polimorfismoa eta zergatik hemen ez dugun `null` edo `Any` nahi |
| 2 | |
| 3 | 1965ean, Tony Hoarek erreferentzia nulua (`null` / `nil`) asmatu zuen. Berrogei urte geroago barkamena eskatu zuen jendaurrean eta *"nire mila milioi dolarreko akatsa"* deitu zion. |
| 4 | |
| 5 | Zergatik? `null` duten lengoaietan, funtzio batek bere sinaduran hitzematen dizulako `Erabiltzailea` bat itzuliko dizula... eta gutxien espero duzunean `null` itzultzen dizulako eta goizeko hiruretan zerbitzaria lurrera botatzen dizulako. |
| 6 | |
| 7 | Eta beste horrenbeste gertatzen da objektuen herentziarekin eta `Any` (edo `Object`) motarekin: zenbaki bat eta testu bat `[1, "bi"]` zerrenda batean sartzen dituzu, lengoaiak sorbaldak jasotzen ditu eta esaten du *"ados, ba orain hau `List[Any]` bat da, zorte on barruan zer dagoen asmatzen zeharkatzen duzunean"*. |
| 8 | |
| 9 | Ridgunen bi arazo horiek errotik mozten ditugu **Datu-Mota Aljebraikoekin (ADTak)** eta **Polimorfismo Parametrikoarekin**. Ikasgai gogorreko izena duela dirudi, baina bost minutuan ikusiko duzu datuak modelatzeko munduko modurik naturalena dela. |
| 10 | |
| 11 | --- |
| 12 | |
| 13 | ## 6.1. Biderkadura-Motak (Erregistroak): Betiko zure `struct`-ak, baina superbotereekin |
| 14 | |
| 15 | Entitate berari dagozkion hainbat datu elkartu nahi dituzunean (adibidez, zerbitzari bat bere izenarekin, bere IParekin eta bere atakarekin), **`type`** bat definitzen duzu bere eremuekin: |
| 16 | |
| 17 | ```ridgun |
| 18 | type Zerbitzaria = ostalaria: String, ataka: Int, tls_aktibo: Bool |
| 19 | ``` |
| 20 | |
| 21 | Lerro hori idazte hutsarekin, Ridgunek **lau gauza** oparitzen dizkizu automatikoki: |
| 22 | |
| 23 | ### 1. `Zerbitzaria` eraikitzailea |
| 24 | ```ridgun |
| 25 | nire nodoa => Zerbitzaria |
| 26 | nire nodoa = Zerbitzaria "api.adibidea.eus" 443 True |
| 27 | ``` |
| 28 | |
| 29 | ### 2. *Getter* funtzio puru bat eremu bakoitzerako (`ostalaria`, `ataka`, `tls_aktibo`) |
| 30 | Ez duzu `s.getOstalaria()` edo antzekorik idatzi behar; eremu-izen bakoitza zuzenean datu hori ateratzen duen funtzio bat da: |
| 31 | |
| 32 | ```ridgun |
| 33 | konexio urla Zerbitzaria => String |
| 34 | konexio urla zrb = "Konektatu hona: " + (ostalaria zrb) + " {ataka zrb} atakan" |
| 35 | ``` |
| 36 | |
| 37 | ### 3. Erregistroen eguneratze funtzionala (`with eremua: balioa`) |
| 38 | Imajinatu 12 edo 15 eremuko konfigurazio-erregistro bat (`ls` komando baten edo web-zerbitzari baten aukerak bezala) eta eremu bat edo bi bakarrik aldatu nahi dituzula gainerakoak ukitu gabe. Erregistroa 15 eremuak eskuz pasatuz berreraiki beharrean, **`with`** eta bi puntu **`:`** erabiltzen dituzu (`type`-aren definizioan bezalaxe): |
| 39 | |
| 40 | ```ridgun |
| 41 | aktibatu garapen modua Zerbitzaria => Zerbitzaria |
| 42 | aktibatu garapen modua zrb = zrb with ataka: 8080, tls_aktibo: False |
| 43 | ``` |
| 44 | |
| 45 | Bi munduetako onena: |
| 46 | - Zure kodean **% 100ean aldaezina eta segurua** da (konpiladoreak egiaztatzen du `ataka` eta `tls_aktibo` `Zerbitzaria`-n existitzen direla eta mota zuzena dutela). |
| 47 | - Azpitik, `zrb` beste inorekin partekatuta ez badago, exekutagarri natiboak bere memoria *in-place* berrerabiltzen du (`Arc::try_unwrap`) gainerako eremuak kopiatu gabe. |
| 48 | |
| 49 | ### 4. *Pattern matching* osoa erregistroa erditik ireki nahi baduzu |
| 50 | ```ridgun |
| 51 | ataka segurua da Zerbitzaria => Bool |
| 52 | ataka segurua da (Zerbitzaria _ 443 True) = True |
| 53 | ataka segurua da _ = False |
| 54 | ``` |
| 55 | |
| 56 | --- |
| 57 | |
| 58 | ## 6.2. Batuketa-Motak (`|` bidezko aldaerak): Nola erretiratu `null`, salbuespenak eta herentzia |
| 59 | |
| 60 | Orain dator onena. |
| 61 | Erregistro arrunt batek `ostalaria` **ETA** `ataka` **ETA** `tls_aktibo` ditu. |
| 62 | Baina mundu errealean askotan hau esan behar duzu: *"Ordainketa-metodo bat **EDO** bere zenbakia duen txartel bat da, **EDO** bere IBANa duen transferentzia bat da, **EDO** eskudirua da (datu gehigarririk ez daramana)"*. |
| 63 | |
| 64 | Objektuetara bideratutako programazioan `OrdainketaMetodoa` interfaze bat, hiru azpiklase hiru fitxategi desberdinetan, eraikitzaileak, `instanceof`... sortu beharko zenituzke. Ridgunen **lerro bakarra** da: |
| 65 | |
| 66 | ```ridgun |
| 67 | type OrdainketaMetodoa = Txartela String Int | Transferentzia String | Eskudirua |
| 68 | ``` |
| 69 | |
| 70 | Eta begira zer gertatzen den funtzio batean `OrdainketaMetodoa` bat prozesatzen duzunean: |
| 71 | |
| 72 | ```ridgun |
| 73 | deskribatu ordainketa OrdainketaMetodoa => String |
| 74 | deskribatu ordainketa (Txartela zenb _) = "{zenb}-rekin amaitutako txartel bidezko ordainketa" |
| 75 | deskribatu ordainketa (Transferentzia ibana) = "Transferentzia {ibana} kontutik" |
| 76 | deskribatu ordainketa Eskudirua = "Eskudirutan ordaintzea leihatilan" |
| 77 | ``` |
| 78 | |
| 79 | Aldaera bakoitzak (`Txartela`, `Transferentzia`, `Eskudirua`) berarentzat zentzua duten datuak soilik daramatza itsatsita. **Ezinezkoa** da hutsez `ibana` bat zintzilik duen `Eskudirua` ordainketa bat eraikitzea. |
| 80 | |
| 81 | --- |
| 82 | |
| 83 | ## 6.3. "Eta nola sartzen dut `[1, "bi"]` zerrenda batean `Any` erabili gabe?" |
| 84 | |
| 85 | Itzul gaitezen milioiaren galderara. Imajinatu CSV edo JSON fitxategi bat irakurtzen ari zarela, non gelaxka bat zenbaki osoko bat, testu bat edo hutsa izan daitekeen. |
| 86 | |
| 87 | `Any` mota bat bazenu, `[1, "bi"]` sartuko zenuke eta konpiladorearen laguntza guztia galduko zenuke: une horretatik aurrera zerrendaren gaineko edozein eragiketa begiak estalita ibiltzea litzateke. |
| 88 | |
| 89 | Ridgunen (Haskell edo Rusten bezalaxe), bi segundotan batuketa-mota bat sortzen duzu, zerrenda horren barruan zein gauza bizi daitezkeen zehatz-mehatz azaltzen duena: |
| 90 | |
| 91 | ```ridgun |
| 92 | type GelaxkaBalioa = Osokoa Int | Testua String | Hutsa |
| 93 | |
| 94 | formateatu gelaxka GelaxkaBalioa => String |
| 95 | formateatu gelaxka (Osokoa n) = "Zenbakia: {n}" |
| 96 | formateatu gelaxka (Testua s) = "Testua: {s}" |
| 97 | formateatu gelaxka Hutsa = "(hutsik)" |
| 98 | |
| 99 | nire errenkada => [GelaxkaBalioa] |
| 100 | nire errenkada = [Osokoa 1, Testua "bi", Hutsa] |
| 101 | ``` |
| 102 | |
| 103 | Ikusten duzu aldea? |
| 104 | - `Any`-rekin, konpiladoreak ez daki zerrendan osokoak, kateak ala USB bidez konektatutako kafe-makina bat dauden. |
| 105 | - `[GelaxkaBalioa]`-rekin, zenbakiak eta testuak zerrenda berean nahas ditzakezu, **baina konpiladoreak % 100ean daki hiru aukera horiek baino ez direla existitzen** eta kasu bakoitza (`Osokoa n`, `Testua s`, `Hutsa`) erabili aurretik despaketatzera behartzen zaitu. Segurtasun osoa malgutasun izpi bat ere galdu gabe. |
| 106 | |
| 107 | --- |
| 108 | |
| 109 | ## 6.4. `Option` eta `Result`: Zerbait ez egon daitekeenean edo huts egin dezakeenean |
| 110 | |
| 111 | Ridgunen `null` existitzen ez denez eta airean lehertzen diren salbuespenik (`throw new Exception()`) existitzen ez denez, lengoaiak bi batuketa-mota unibertsal integratuta dakartza: |
| 112 | |
| 113 | 1. **`Option`**: edo **`Some balioa`** duzu, edo **`None`** (ezer ez) duzu. |
| 114 | 2. **`Result`**: edo eragiketa ondo atera da (**`Ok balioa`**), edo arrazoi zehatz bategatik huts egin du (**`Err errorea`**). |
| 115 | |
| 116 | ```ridgun |
| 117 | bilatu zatiketa Int Int => Result |
| 118 | bilatu zatiketa _ 0 = Err "Ei! Ezin da zeroz zatitu" |
| 119 | bilatu zatiketa a b = Ok (a / b) |
| 120 | |
| 121 | inprimatu em Result => () |
| 122 | inprimatu em (Ok n) = echo "Emaitza {n} da" |
| 123 | inprimatu em (Err msg) = echo "Hutsegite kontrolatua: {msg}" |
| 124 | ``` |
| 125 | |
| 126 | Inork ezin du errore bat deskuiduz alde batera utzi, `Result` motak berak barruan `Ok` bat ala `Err` bat dagoen begiratzera behartzen zaituelako. |
| 127 | |
| 128 | --- |
| 129 | |
| 130 | ## 6.5. Polimorfismo Parametrikoa: Funtzio generikoak `A`, `B`, `T`...-rekin |
| 131 | |
| 132 | Demagun `(x, y)` bikote bat alderantzikatzen duen edo zerrenda bateko lehen elementua ateratzen duen funtzio bat idazten duzula. Benetan axola du zerrenda `Int`, `String` ala `Zerbitzaria` motakoa den? Inola ere ez! Algoritmoa berdin-berdina da. |
| 133 | |
| 134 | Ridgunen, mota-sinadura bateko **edozein letra larri banakok (`A`-tik `Z`-ra)** automatikoki **komodin-mota polimorfiko (mota-aldagai)** gisa funtzionatzen du: |
| 135 | |
| 136 | ```ridgun |
| 137 | buru segurua [A] A => A |
| 138 | buru segurua [] lehenetsia = lehenetsia |
| 139 | buru segurua (x:_) _ = x |
| 140 | |
| 141 | biratu bikotea (A, B) => (B, A) |
| 142 | biratu bikotea (x, y) = (y, x) |
| 143 | ``` |
| 144 | |
| 145 | Erreparatu `buru segurua [A] A => A`-ren magiari: |
| 146 | - Ez da kontrolik gabeko `Any` bat: **`[A]` zerrendaren motak, `lehenetsia` balioaren motak (`A`) eta itzulitako motak (`A`) bera izan behar dutela** dio. |
| 147 | - `buru segurua [10, 20] 0` deitzen baduzu, konpiladoreak hariak lotzen ditu (`A = Int`) eta `Int` bat itzultzen dizu. |
| 148 | - `buru segurua ["a", "b"] "bat_ere_ez"` deitzen baduzu, hariak lotzen ditu (`A = String`) eta `String` bat itzultzen dizu. |
| 149 | - Baina tranpa egiten saiatzen bazara eta `buru segurua [10, 20] "bat_ere_ez"` deitzen baduzu, zas! Konpiladoreak lehorrean gelditzen zaitu, lehen argumentuak `A` `Int` dela dioelako eta bigarrenak `A` `String` dela. |
| 150 | |
| 151 | --- |
| 152 | |
| 153 | ## 6.6. Sortu zure eragile propioak (`op ::`): Bektoreetatik neurrira egindako esaldietara |
| 154 | |
| 155 | Zergatik zenbaki natiboek bakarrik izan behar zuten `+` edo `*` erabiltzeko eskubidea? Eta zergatik konformatu betiko eragileekin zure arazorako sintaxi ezin hobea asma dezakezunean? |
| 156 | |
| 157 | **`op ::`**-rekin (edo zuzenean aurrizkirik gabe eragile-ereduak erabiltzen badituzu) bi trebezia egin ditzakezu: |
| 158 | |
| 159 | ### 1. `+`, `-`, `*`, `/`, `%` eragileei zure mota propioekin eragiketak egiten irakastea |
| 160 | ```ridgun |
| 161 | type Konplexua = erreala: Int, irudikaria: Int |
| 162 | |
| 163 | op :: Konplexua + Konplexua => Konplexua |
| 164 | a + b = Konplexua ((erreala a) + (erreala b)) ((irudikaria a) + (irudikaria b)) |
| 165 | ``` |
| 166 | |
| 167 | ### 2. *Mixfix* eragileak sortzea (esaldi natural gisa irakurtzen direnak, `type` eta `with` barne) |
| 168 | Eragile-sinadura batean, **hasieran letra larria duten hitzak (`Int`, `String`, `A`..`Z`) edo kortxete/parentesi artekoak (`[Int]`, `(A, B)`) argumentuetarako hutsuneak dira**, eta **letra xehez idatzitako hitzak eragilearen esaldiaren parte dira** (baita `type` edo `with` bezalako hitz ohikoak ere, Ridgunen testuingurukoak direnak eta inoiz blokeatzen ez zaituztenak): |
| 169 | |
| 170 | ```ridgun |
| 171 | op :: baldin Bool orduan A bestela A => A |
| 172 | baldin True orduan v bestela _ = v |
| 173 | baldin False orduan _ bestela f = f |
| 174 | |
| 175 | op :: format String type String with Int => String |
| 176 | format label type kind with w = "{label} ({kind}, width={w})" |
| 177 | |
| 178 | main => () |
| 179 | main = |
| 180 | mezua = baldin (10 > 5) orduan "Hamarrak irabazten du!" bestela "Ezinezkoa" |
| 181 | echo mezua |
| 182 | echo (format "panela" type "ertzduna" with 80) |
| 183 | ``` |
| 184 | |
| 185 | Zure eragile hirutar polimorfiko propioa eta formateatzeko DSL bat fabrikatu berri dituzu Ridguneko **3 lerrotan**. |
| 186 | |
| 187 | --- |
| 188 | |
| 189 | ## 6.7. Eraikitzaile partekatuak, mota-aldaera zuzenak, `expand type` eta zuriune naturalak `fn ::`-n |
| 190 | |
| 191 | Ridgunek datu-mota aljebraikoak eta funtzioak are naturalago eta hedagarriago bihurtzen ditu: |
| 192 | |
| 193 | 1. **Eraikitzaile-izen partekatuak `type` ezberdinen artean**: Batura-mota ezberdinek eraikitzaile-izen bera (`Error`, `Var`, `Lit`) berrerabil dezakete aritate berarekin edo ezberdinarekin; mota-egiaztatzaileak sinadurako zutabearen arabera bereizten ditu. |
| 194 | 2. **Mota-izen zuzeneko aldaerak errepikapenik gabe (`type Something = String | Int | MyStruct Float Int`)**: Aldaera bat oinarrizko mota bat denean (`String`, `Int`, `Float`/`Real`, `Bool`, `Char`), ez duzu `Int Int` edo `String String` errepikatu behar. Balio zuzenak (`"kaixo"`, `42`) edo eraikitzaile esplizituak (`String "kaixo"`, `Int 42`) pasa ditzakezu, eta ereduak parekatu bai literalekin (`"admin"`, `0`) bai eredu tipatuekin (`(String s)`, `(Int n)`). |
| 195 | 3. **Mota-hedapen irekia (`expand type`) eta `fn ::` klausulen bateratze automatikoa**: `expand type` erabiliz aldaera berriak gehitu diezazkiokezu batura-mota bati edo eremu berriak erregistro bati. Funtzio bererako `fn ::` berriro deklaratzean, klausula berriak automatikoki bateratzen dira edozein `_` komodin-klausularen **aurretik** (`expand fn` idatzi beharrik gabe). |
| 196 | 4. **Zuriune naturalak `" "` `_`-ren ordez `fn ::`-n, funtzio natiboetan eta fitxategi-izenetan**: `op ::`-n bezala, hitz anitzeko `fn ::` izenak, funtzio natiboen deiak (`assert equals`, `str len`, `read file`) eta `.rg` fitxategi-izenak (`test core language.rg`) zuriune naturalekin idatz daitezke: |
| 197 | |
| 198 | ```ridgun |
| 199 | type Something = String | Int | MyStruct Float Int |
| 200 | |
| 201 | fn :: deskribatu zerbait Something => String |
| 202 | deskribatu zerbait "admin" = "admin_berezia" |
| 203 | deskribatu zerbait 0 = "zero" |
| 204 | deskribatu zerbait (String s) = "testua:{s}" |
| 205 | deskribatu zerbait (Int n) = "osoa:{n}" |
| 206 | deskribatu zerbait (MyStruct ratio cnt) = "egitura:{ratio}:{cnt}" |
| 207 | deskribatu zerbait _ = "bestea" |
| 208 | |
| 209 | expand type Something = Bool |
| 210 | |
| 211 | fn :: deskribatu zerbait Something => String |
| 212 | deskribatu zerbait (Bool b) = "boolearra:{b}" |
| 213 | ``` |
| 214 | |
| 215 | ### 5. Mota generiko propioak (`type Result T E`, `type Page T`) eta moduluen izen-espazioak (`import ... as`, `mod.Mota`) |
| 216 | Funtzio polimorfikoez gain, **zure batura-mota eta erregistro generiko propioak** deklara ditzakezu mota-parametroekin (`A`..`Z`), baita izen bereko motak, eraikitzaileak, eremuak eta funtzioak modulu ezberdinetan talkarik gabe antolatu ere (`module ::`, `import db as d`, `http.Config`, `d.Config`). Modulu bakoitzak **3 mailako inportazio-ikusgarritasun zorrotza** du: aurrizkirik gabeko izenak lehenik **(1) bere deklarazioetan** bilatzen ditu (`module :: izena` partekatzen duten fitxategi guztiak barne), ondoren **(2) bere `import` zuzenen artean** eta azkenik **(3) inportazio iragankorretan**, inportatu gabeko moduluek edo aplikazio nagusiak liburutegi bat inoiz kutsa ez dezaten: |
| 217 | |
| 218 | ```ridgun |
| 219 | type Emaitza T E = Arrakasta T | Hutsegitea E |
| 220 | type Orrialdea T = elementuak: [T], guztira: Int |
| 221 | |
| 222 | fn :: atera arrakasta (Emaitza A String) A => A |
| 223 | atera arrakasta (Arrakasta v) _ = v |
| 224 | atera arrakasta (Hutsegitea _) d = d |
| 225 | ``` |
| 226 | |
| 227 | **07. kapituluan** zure prozesadorearen nukleo guztiak aldi berean lanean jarriko ditugu izerdi tanta bat ere bota gabe: **goi-mailako funtzioak (`map`, `||map`, `filter`, `||filter`, `fold`), itxitura lexikoak (`(x => expr)`), `||` grafo paraleloak `->` geziekin eta kanal konkurrenteak**. |