Ridgun
The language itself: a compiler that compiles itself, the stable project tool, the specification and the book
| 1 | # 07. kapitulua: Goi-Mailako Funtzioak eta Izerdirik Gabeko Paralelismoa (Ez `Mutex`-ik, ez `Deadlock`-ik) |
| 2 | |
| 3 | Ia programazio-lengoaia guztietan une dramatiko bat egoten da proiektu baten bizitzan: bilera batean norbaitek honako hau esaten duen eguna: |
| 4 | |
| 5 | > *"Hau motel doa. Hariekin paralelizatu behar da."* |
| 6 | |
| 7 | Isiltasuna nagusitzen da aretoan. Mundu guztiak daki zer datorren ondoren: asteak ilargi beteko astearteetan bakarrik gertatzen diren *race condition* mamuak, elkar-blokeatzeak (*deadlock*-ak) eta erreproduzitu ezinezko erroreak ehizatzen. |
| 8 | |
| 9 | Ridgunen, norbaitek *"paralelizatu egin behar da"* esaten duenean, nahikoa da zerrenda-eragiketen aurrean `||` jartzea (**`||map`** eta **`||filter`**) edo zure kalkuluak `->` geziak dituen `||` bloke paralelo batean jartzea eta kafe bat hartzera joatea. |
| 10 | |
| 11 | Zergatik? **Zure funtzioak puruak direnean eta zure datuak aldaezinak direnean lehenespenez, aldi berean lanean ari diren bi PUZ nukleok ezin diotelako inoiz elkarri mahuka zapaldu**. |
| 12 | |
| 13 | Ikus dezagun urratsez urrats. |
| 14 | |
| 15 | --- |
| 16 | |
| 17 | ## 7.1. Funtzioak jasotzen dituzten funtzioak: `map`, `||map`, `filter`, `||filter` eta `fold` |
| 18 | |
| 19 | Ridgunen funtzio bat beste balio bat gehiago da. Parametro bidez pasa dezakezu `7` zenbakia pasatzen duzun bezalaxe. |
| 20 | |
| 21 | Lengoaiak bilduma-konbinatzaileak dakartza seriean, zerrendak eskuzko errekurtsio bakar bat ere idatzi gabe eraldatzeko, bai modu sekuentzialean (`map`, `filter`, `fold`) bai nukleo anitzeko modu paraleloan `||` aurrizkiarekin (**`||map`**, **`||filter`**): |
| 22 | |
| 23 | ### 1. `map` (sekuentziala) eta `||map` (nukleo anitzeko paraleloa) |
| 24 | Demagun `garestia Int => Int` funtzio bat eta zenbakiak dituen zerrenda bat dituzula: |
| 25 | |
| 26 | ```ridgun |
| 27 | garestia Int => Int |
| 28 | garestia x = (x * x) + (x * 3) |
| 29 | |
| 30 | main => () |
| 31 | main = |
| 32 | datuak = [1, 2, 3, 4, 5, 6, 7, 8] |
| 33 | -- Sekuentzial zorrotza ezkerretik eskuinera: |
| 34 | r1 = map garestia datuak |
| 35 | -- Nukleo anitzeko paraleloa `||` aurrizkiarekin: |
| 36 | r2 = ||map garestia datuak |
| 37 | echo r1 |
| 38 | echo r2 |
| 39 | ``` |
| 40 | |
| 41 | - **`map`**-ekin, Ridgunek funtzioa elementuz elementu aplikatzen du ezkerretik eskuinera uneko harian (zerrenda txikietarako edo ordenatutako sarrera/irteera efektuak dituzten funtzioetarako bikaina). |
| 42 | - **`||map`**-ekin, Ridgunen runtime-ak `datuak` zerrendako elementuak zure prozesadoreko hari langileen artean banatzen ditu eta azken zerrenda **zehazki ordena berean** berreraikitzen du, `||map f xs == map f xs` bermatuz. |
| 43 | |
| 44 | ### 2. `filter` (sekuentziala) eta `||filter` (paraleloa): Baldintza bat betetzen dutenekin bakarrik geratzea |
| 45 | `Bool` itzultzen duen funtzio bat (*predikatu* bat) eta zerrenda bat pasatzen dizkiozu: |
| 46 | |
| 47 | ```ridgun |
| 48 | positiboa da Int => Bool |
| 49 | positiboa da n = n > 0 |
| 50 | |
| 51 | main => () |
| 52 | main = |
| 53 | garbiak = filter positiboa da [-5, 10, 0, -2, 42] |
| 54 | garbiak_par = ||filter positiboa da [-5, 10, 0, -2, 42] |
| 55 | echo garbiak -- [10, 42] inprimatzen du |
| 56 | echo garbiak_par -- [10, 42] inprimatzen du |
| 57 | ``` |
| 58 | |
| 59 | ### 3. `fold`: Zerrenda oso bat emaitza bakar batera murriztea |
| 60 | Bi argumentuko funtzio konbinatzaile bat `(Metatua Elementua => Metatua)`, hasierako balioa eta zerrenda pasatzen dizkiozu: |
| 61 | |
| 62 | ```ridgun |
| 63 | metatu biderkadura Int Int => Int |
| 64 | metatu biderkadura acc x = acc * x |
| 65 | |
| 66 | main => () |
| 67 | main = |
| 68 | echo (fold metatu biderkadura 1 [2, 3, 4, 5]) -- 1 * 2 * 3 * 4 * 5 = 120 |
| 69 | ``` |
| 70 | |
| 71 | ### Nola idazten dut nik beste funtzio bat jasotzen duen nire funtzio propioa? |
| 72 | Oso erraz: mota-sinaduran funtzio gonbidatuaren sinadura parentesi artean jartzen duzu `(Sarrera => Irteera)`: |
| 73 | |
| 74 | ```ridgun |
| 75 | exekutatu bi aldiz (A => A) A => A |
| 76 | exekutatu bi aldiz f x = f (f x) |
| 77 | ``` |
| 78 | |
| 79 | ### 4. Ezkerretik eskuinerako hodi arinak (`->`) — % 100 Ridgun estiloa! |
| 80 | Hainbat eraldaketa kateatzen dituzunean (`map`, `filter`, `fold` edo zure funtzio eta `op ::` eragile propioak), parentesiak eskuinetik ezkerrera habiaratzeak (`compact l (filter g (map f xs))`) barrutik kanpora irakurtzera behartzen zaitu. |
| 81 | |
| 82 | Ridgunen, **gomendatutako estilo idiomatikoa (`2b`)** ezkerretik eskuinera irakurtzen da **`->`** hodi-eragilearekin, non **ezkerretik datorrena beti hurrengo funtzioaren edo `op ::`-aren lehen argumentu gisa sartzen den**: |
| 83 | |
| 84 | ```ridgun |
| 85 | -- Ridgunen gomendatutako estilo idiomatikoa (2b): |
| 86 | xs map (x => x * 2) -> filter (x => x > 10) -> fold (acc x => acc + x) 0 -> emaitza |
| 87 | |
| 88 | -- Eta funtzio batek tupla bat itzultzen badu, maskara batekin iragazi eta berez desegituratzen da: |
| 89 | parseatu paketea "v0.9.9" -> (+ _ _ +) -> konbinatu kodea -> laburpena |
| 90 | ``` |
| 91 | |
| 92 | Gainera, albo-ondorioak dituen edozein adierazpen (fitxategi bat idatzi, sistemari deitu) bere lerroan bakarrik jar daiteke `echo` edo `_ =` aldagai muturik gabe, edo `-> ()`-n amaitu bere itzulera esplizituki baztertu nahi baduzu. |
| 93 | |
| 94 | ### 5. Itxitura lexikoak eta funtzio anonimoak (`(x => expr)`, `(acc x => expr)`) |
| 95 | Unean bertan funtzio labur bat pasa nahi duzunean edo uneko esparruko aldagai lokalak (desplazamendu bat, aurrizki bat edo saio aktibo bat) atzeman nahi dituzunean funtzio global laguntzaile bat deklaratu gabe, **itxitura lexiko** bat erabiltzen duzu parentesi artean **`=>`**-rekin: |
| 96 | |
| 97 | ```ridgun |
| 98 | fn :: prozesatu saioarekin => [Int] |
| 99 | prozesatu saioarekin = |
| 100 | oinarria = 10 |
| 101 | hobaria = 3 |
| 102 | batuak = map (x => x + oinarria) [1, 2, 3] |
| 103 | iragaziak = filter (x => x >= hobaria) [1, 2, 3, 4] |
| 104 | guztira = fold (acc x => acc + x + hobaria) 0 [1, 2, 3] |
| 105 | bikoteak = map ((a b) => a + b + oinarria) [(1, 2), (3, 4)] |
| 106 | batuak :: iragaziak :: [guztira] :: bikoteak |
| 107 | ``` |
| 108 | |
| 109 | Bost arauk mantentzen dituzte lambdak garbi eta anbiguotasunik gabe: |
| 110 | - **Parentesiak derrigorrezkoak**: lambda oro beti doa parentesi artean `(... => ...)`. |
| 111 | - **Parametrorik gabeko lambda (`(=> expr)`)**: `fn :: main => ()`-k `=>` aurretik `()` ez daraman bezala, parametrorik gabeko lambda `(=> expr)` idazten da (eta bere mota `(=> Mota)` da), inoiz ez `(() => expr)`. |
| 112 | - **Parametro anitz vs. tupla (`(a b => ...)` vs. `((a b) => ...)`)**: `(acc x => ...)`-k bi parametro bereiz jasotzen ditu; `((a b) => ...)`-k tupla bakar bat jasotzen du eta desegituratu egiten du. |
| 113 | - **Lerro anitzeko gorputza**: `=>` ondoren lerro-jauzi bat jartzen baduzu, koskatutako urrats anitzeko bloke bat idatz dezakezu ixteko `)` aurretik: |
| 114 | ```ridgun |
| 115 | bikoitzak = map (x => |
| 116 | urratsa = x * 2 |
| 117 | urratsa + oinarria |
| 118 | ) [1, 2, 3] |
| 119 | ``` |
| 120 | - **Maiuskulak vs. minuskulak**: `(Conn => Conn)` funtzio-mota bat da (hasiera maiuskulaz) eta `(conn => conn)` lambda balio bat da (hasiera minuskulaz). |
| 121 | |
| 122 | Atzemandako aldagaiak (`oinarria`, `hobaria`) automatikoki paketatzen dira memoria-kudeaketa deterministarekin (`RMM1`/`RMM2`), ihesik gabe eta zabor-biltzailearen etenik gabe. |
| 123 | |
| 124 | --- |
| 125 | |
| 126 | ## 7.2. Koroaren bitxia: `||` blokeak `->` geziekin Grafo Paralelo (DAG) gisa |
| 127 | |
| 128 | Hemen aldatzen du Ridgunek zure pentsamoldea. |
| 129 | |
| 130 | Lengoaia inperatibo arrunt batean, hiru lerro jarraian idazten dituzunean: |
| 131 | 1. Lehenengo 1. lerroa exekutatzen da. |
| 132 | 2. Gero 2. lerroa (nahiz eta 1. lerroarekin inolako zerikusirik ez izan). |
| 133 | 3. Gero 3. lerroa. |
| 134 | |
| 135 | Hori ordenagailua betaurreko ilunekin lan egitera behartzea da. Ridgunen, **`||`** barra paraleloak **`->`** geziekin erabiltzen dituzunean (`where` bezalako inongo hitz gakorik idatzi beharrik gabe), ez zara agindu sekuentzialak ematen ari: zein kalkuluk zein aldagai elikatzen duen dioen **Grafo Azikliko Zuzendu (DAG)** bat marrazten ari zara: |
| 136 | |
| 137 | ```ridgun |
| 138 | fib Int => Int |
| 139 | fib 0 = 0 |
| 140 | fib 1 = 1 |
| 141 | fib n = (fib (n - 1)) + (fib (n - 2)) |
| 142 | |
| 143 | fact Int => Int |
| 144 | fact 0 = 1 |
| 145 | fact n = n * (fact (n - 1)) |
| 146 | |
| 147 | super kalkulua => Int |
| 148 | super kalkulua = ezker_adarra + eskuin_adarra |
| 149 | || fib 10 -> ezker_adarra |
| 150 | || fact 6 -> eskuin_adarra |
| 151 | ``` |
| 152 | |
| 153 | Irakurri ozenki: |
| 154 | - *"`ezker_adarra + eskuin_adarra` kalkulatu nahi dut, bitartean paraleloan (`||`) `fib 10` `ezker_adarra`-ra doalarik (`->`) eta `fact 6` `eskuin_adarra`-ra doalarik (`->`)."* |
| 155 | |
| 156 | ### Zer egiten duen `ridgunc`-ek oihalaren atzean zure `||` lerroekin: |
| 157 | 1. `->` geziei begiratzen die eta mendekotasun-zuhaitza (DAG) osatzen du. |
| 158 | 2. **Zikloak estatikoki ehizatzen ditu**: hutsez `|| a + 1 -> b` eta `|| b + 1 -> a` idazten badituzu, konpiladoreak berehala esaten dizu *"Geldi hor! Mendekotasun zirkular bat dago"* eta ez dizu blokeo infinito bat konpilatzen uzten. |
| 159 | 3. Ikusten du `fib 10` eta `fact 6` ez daudela elkarren menpe, beraz argi berdea dute paraleloan exekutatzeko `ezker_adarra + eskuin_adarra`-n batu aurretik. |
| 160 | |
| 161 | Are gehiago, hainbat solairuko hodi bat munta dezakezu `||` bloke berean —adierazpenaren oinean zein `=` ondoren bertan— eta konpiladoreak bere kabuz ondorioztatuko du zein solairu joan daitezkeen batera eta zeintzuek itxaron behar duten: |
| 162 | |
| 163 | ```ridgun |
| 164 | hiruhileko balantzea Int => Int |
| 165 | hiruhileko balantzea oinarria = |
| 166 | || oinarria * 100 -> salmenta_gordinak |
| 167 | || oinarria * 40 -> kostu_finkoak |
| 168 | || salmenta_gordinak - kostu_finkoak -> irabazi_gordina ||. |
| 169 | irabazi_gordina * 80 / 100 |
| 170 | ``` |
| 171 | |
| 172 | Erreparatu azken `||` lerroaren amaierako **`||.`** xehetasunari: `|.` ikurrak habiaratutako zain-zuhaitz bat ixten duen bezalaxe, **`||.`** ikurrak `||` bloke paraleloa ixten du azpian sententzia sekuentzial arruntek jarraitzen dutenean. Hemen `salmenta_gordinak` eta `kostu_finkoak` paraleloan abiarazten dira; biak prest daudenean `irabazi_gordina` kalkulatzen da, eta azkenik `irabazi_gordina * 80 / 100` itzultzen da. Dena `async`, `Future`, `Promise` edo `Mutex` bakar bat ere gabe. |
| 173 | |
| 174 | --- |
| 175 | |
| 176 | ## 7.3. Izenik gabeko `||` lasterbidea: Tupla bat itzultzen duten hodi paraleloak |
| 177 | |
| 178 | Eta bi edo hiru kalkulu paraleloan abiarazi eta tupla batean elkarrekin jaso nahi badituzu `->` bidez aldagai-izenak asmatu ere egin gabe? |
| 179 | |
| 180 | `||` zuzenean erabiltzen duzu: |
| 181 | |
| 182 | ```ridgun |
| 183 | bikote paraleloa Int => (Int, Int) |
| 184 | bikote paraleloa n = |
| 185 | || fib n |
| 186 | || fact n |
| 187 | ``` |
| 188 | |
| 189 | Zuzena, bisuala eta `(fib n, fact n)` tupla itzultzen du. |
| 190 | |
| 191 | --- |
| 192 | |
| 193 | ## 7.4. Eskuzko bolantea nahi duzunean: Agenteen Eredu Natiboa (`agent`, `send ... to`, `receive from`) eta CSP Kanalak |
| 194 | |
| 195 | `||` eta `||map`-en paralelismo automatikoak kasuen % 90a estaltzen du. Baina zerbitzari iraunkor bat, egoera isolatuko erabiltzaile-saioak, lan-ilara bat edo ekoizle-kontsumitzaile hodi bat programatzen ari zarenean, **memoria isolatuko agente konkurrenteak eta motadun mezu-igarotzea** nahi dituzu. |
| 196 | |
| 197 | Ridgunen ez dago `actor ::` bezalako sintaxi arrotz bat asmatu beharrik: **edozein funtzio arrunt (`fn ::` edo `op ::`) agente konkurrente bihurtzen da lerro bakar batean `agent` erabiliz**: |
| 198 | |
| 199 | ### 1. Egoerarik gabeko eta egoera isolatuko agenteak (`agent`, `send ... to`, `receive from`, `stop`) |
| 200 | - `ag = agent (nire funtzioa)`: `M => R` funtzio bat `Agent M R` motako egoerarik gabeko agente bihurtzen du. |
| 201 | - `ag = agent (egoeradun funtzioa) hasierako_egoera`: `S M => S` (edo `S M => S R`) funtzio bat egoera isolatuko agente bihurtzen du, mezuz mezu bere egoera gogoratuz `Mutex` bakar bat ere gabe. |
| 202 | - `send msg to ag` (edo argumentu askorekin: `send a b to ag`): motadun mezu bat bidaltzen du agentearen postontzira. |
| 203 | - `em = receive from ag` (edo `try receive from ag` blokeatu gabe): agentearen irteerako postontziko hurrengo erantzuna jasotzen du. |
| 204 | - `stop ag`: agentea gelditzen du eta bere egoera automatikoki askatzen du `RMM1`/`RMM2` bidez. |
| 205 | |
| 206 | ```ridgun |
| 207 | fn :: gehitu bisita Int Int => Int |
| 208 | gehitu bisita met n = met + n |
| 209 | |
| 210 | fn :: prozesatu sorta Int Int => Int |
| 211 | prozesatu sorta id t = (id * 100) + t |
| 212 | |
| 213 | fn :: main => () |
| 214 | main = |
| 215 | kontagailua = agent (gehitu bisita) 10 |
| 216 | send 5 to kontagailua |
| 217 | send 7 to kontagailua |
| 218 | b1 = receive from kontagailua |
| 219 | b2 = receive from kontagailua |
| 220 | stop kontagailua |
| 221 | |
| 222 | langilea = agent (prozesatu sorta) |
| 223 | send 3 25 to langilea |
| 224 | sorta = receive from langilea |
| 225 | stop langilea |
| 226 | echo "Bisitak:" b1 "->" b2 "| Sorta:" sorta |
| 227 | ``` |
| 228 | |
| 229 | ### 2. Aukeraketa adimenduna konpilazio-garaian (*Green Threads* vs. Kerneleko Benetako Hariak) |
| 230 | Ohiko galdera bat hau da: *"`||` blokeek, `||map`-ek eta `agent` agenteek beti sortzen al dituzte sistema eragilearen hariak, ala ataza arin gisa exekutatzen dira?"* |
| 231 | |
| 232 | Ridgunen, **konpiladoreak zure ordez hartzen du erabaki hori funtzio bakoitza estatikoki aztertuz**: |
| 233 | - **Funtzio arinak (`TierTrivial` / `StratInlineSequential`)**: `||`, `||map`, `||filter` edo `agent`-era pasatzen duzun funtzioak sakonera txikia badu (aritmetika, kateen formateatzea, HTTP saio baten egoera-trantsizio azkarra), sistema eragilearen hariak berotzeak kalkuluak berak baino gehiago balioko luke. Konpiladoreak *green-thread* urrats ultraarin gisa exekutatzen du, `SYS_clone` gainkargarik gabe. |
| 234 | - **Kalkulu astunak edo errekurtsibo sakonak (`TierDynamicRecursive` / `TierHeavyCPU`)**: Funtzioak errekurtsio sakona (`fib` edo `fact` bezala) edo tentsore-aljebra (`einsum`, `hadamard`, `@`) badu, konpiladoreak automatikoki sortzen du nukleo anitzeko bidalketa **kerneleko benetako harien gainean (`SYS_clone`)**, `||` adarrak edo `agent`-aren mezu-ilara zure PUZ nukleo guztietan benetako paraleloan prozesatuz. |
| 235 | |
| 236 | ### 3. Mezuak kanal mugatuetatik pasatzea (`new bounded channel`) |
| 237 | Go edo Erlangen bezalaxe, agenteez gain hodi esplizituak ere sor ditzakezu etapak komunikatzeko: |
| 238 | |
| 239 | - `new bounded channel edukiera`: `edukiera` mezurentzako lekua duen kanal bat sortzen du. |
| 240 | - `send through balioa kanala` (edo `chan send balioa kanala`): mezu bat hodian sartzen du. |
| 241 | - `receive from kanala` (edo `chan recv kanala`): hurrengo mezua hoditik ateratzen du. |
| 242 | - `try receive from kanala`: mezurik dagoen begiratzen du zain geratu gabe; `Some balioa` edo `None` itzultzen du. |
| 243 | - `receive from timeout kanala ticks`: denbora-muga bat itxaroten du eta `Ok balioa` edo `Err Timeout` itzultzen du. |
| 244 | |
| 245 | ```ridgun |
| 246 | fn :: main => () |
| 247 | main = |
| 248 | hodia = new bounded channel 4 |
| 249 | _ = send through "A ataza osatuta" hodia |
| 250 | _ = send through "B ataza osatuta" hodia |
| 251 | lehena = receive from hodia |
| 252 | bigarrena = receive from hodia |
| 253 | echo lehena "|" bigarrena |
| 254 | ``` |
| 255 | |
| 256 | ### 4. Hustubide-agenteak (*fire-and-forget* `agent sink`), postontzi globalak (`agent ::`) eta proba deterministak (`with agent`) |
| 257 | Ikuskaritza asinkronoa, metrikak edo trazabilitate zeharkakoa behar dituzunean sinadura guztiak zikindu gabe eta irakurri gabeko erantzunak irteera-ilaran pilatu gabe, Ridgunek **hustubide-agenteak (`agent sink`)** eta **goi-mailako postontzi globalak (`agent ::`)** eskaintzen ditu (`agent dropped`, `agent flush`, `agent state` eta `with agent audit_log = espioia do ...`), baita langile anitzen artean parteka daitezkeen `Channel` eta `Agent` deskribatzaileak ere. |
| 258 | |
| 259 | Eta orain, lotu gerrikoa **08. kapitulurako**, amaiera handirako: **konpilazio-garaian egiaztatutako dimentsioak dituzten tentsoreak, esparru-aldagaiak (`context`/`shared`), memoria-eskualde deterministak, MMIO hardwarerako sarbide zuzena eta UNIX/POSIX sistemen programazioa**. |