Skip to content
Ridgun
English

Ridgun

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

07-ordre-superior-i-parallelisme-dag.md259 lines · 13.7 KBRaw
1# Capítol 07: Funcions d'Ordre Superior i Paral·lelisme sense Suor (Ni `Mutex`, ni `Deadlocks`)
2
3En gairebé tots els llenguatges de programació hi ha un moment dramàtic en la vida d'un projecte: el dia en què algú diu en una reunió:
4
5> *"Això va lent. Cal paral·lelitzar-ho amb fils."*
6
7Es fa el silenci a la sala. Tothom sap què ve després: setmanes caçant *race conditions* fantasma que només passen els dimarts amb lluna plena, bloquejos mutus (*deadlocks*) i errors irreproduïbles.
8
9En Ridgun, quan algú diu *"cal paral·lelitzar-ho"*, n'hi ha prou d'anteposar `||` a les teves operacions de llista (**`||map`** i **`||filter`**) o posar els teus càlculs en un bloc paral·lel `||` amb fletxes `->` i te'n vas a fer un cafè.
10
11Per què? Perquè **quan les teves funcions són pures i les teves dades són immutables per defecte, dos nuclis de CPU treballant alhora mai no es poden trepitjar la mànega**.
12
13Vegem-ho pas a pas.
14
15---
16
17## 7.1. Funcions que reben funcions: `map`, `||map`, `filter`, `||filter` i `fold`
18
19En Ridgun una funció és un valor més. Pots passar-la per paràmetre igual que passes el número `7`.
20
21El llenguatge porta de sèrie els combinadors de col·leccions per transformar llistes sense escriure ni una sola recursió manual, tant en mode seqüencial (`map`, `filter`, `fold`) com en mode paral·lel multicore amb el prefix `||` (**`||map`**, **`||filter`**):
22
23### 1. `map` (seqüencial) i `||map` (paral·lel multicore)
24Suposa que tens una funció `costos Int => Int` i una llista amb números:
25
26```ridgun
27costos Int => Int
28costos x = (x * x) + (x * 3)
29
30main => ()
31main =
32 dades = [1, 2, 3, 4, 5, 6, 7, 8]
33 -- Seqüencial estricte d'esquerra a dreta:
34 r1 = map costos dades
35 -- Paral·lel multicore amb el prefix `||`:
36 r2 = ||map costos dades
37 echo r1
38 echo r2
39```
40
41- Amb **`map`**, Ridgun aplica la funció element a element d'esquerra a dreta en el fil actual (ideal per a llistes petites o funcions amb efectes d'entrada/sortida ordenats).
42- Amb **`||map`**, el runtime de Ridgun reparteix els elements de `dades` entre els fils treballadors del teu processador i reconstrueix la llista final **exactament en el mateix ordre**, garantint que `||map f xs == map f xs`.
43
44### 2. `filter` (seqüencial) i `||filter` (paral·lel): Quedar-te només amb els que compleixin una condició
45Li passes una funció que retorni `Bool` (un *predicat*) i una llista:
46
47```ridgun
48es positiu Int => Bool
49es positiu n = n > 0
50
51main => ()
52main =
53 nets = filter es positiu [-5, 10, 0, -2, 42]
54 nets_par = ||filter es positiu [-5, 10, 0, -2, 42]
55 echo nets -- Imprimeix [10, 42]
56 echo nets_par -- Imprimeix [10, 42]
57```
58
59### 3. `fold`: Reduir tota una llista a un sol resultat
60Li passes una funció combinadora de dos arguments `(Acumulat Element => Acumulat)`, el valor inicial i la llista:
61
62```ridgun
63acumular producte Int Int => Int
64acumular producte acc x = acc * x
65
66main => ()
67main =
68 echo (fold acumular producte 1 [2, 3, 4, 5]) -- 1 * 2 * 3 * 4 * 5 = 120
69```
70
71### Com escric jo la meva pròpia funció que rebi una altra funció?
72Molt fàcil: a la signatura de tipus poses la signatura de la funció convidada entre parèntesis `(Entrada => Sortida)`:
73
74```ridgun
75executar dues vegades (A => A) A => A
76executar dues vegades f x = f (f x)
77```
78
79### 4. Canonades fluides d'esquerra a dreta (`->`) — L'estil 100% Ridguner!
80Quan encadenes diverses transformacions (`map`, `filter`, `fold` o les teves pròpies funcions i operadors `op ::`), niar parèntesis de dreta a esquerra (`compact l (filter g (map f xs))`) t'obliga a llegir de dins cap a fora.
81
82En Ridgun, l'**estil idiomàtic recomanat (`2b`)** es llegeix d'esquerra a dreta amb l'operador de canonada **`->`**, on **el que ve de l'esquerra entra sempre com a primer argument** de la funció o `op ::` següent:
83
84```ridgun
85-- Estil idiomàtic recomanat en Ridgun (2b):
86xs map (x => x * 2) -> filter (x => x > 10) -> fold (acc x => acc + x) 0 -> resultat
87
88-- I si una funció retorna una tupla, la filtres amb una màscara i es desempaqueta sola:
89parsejar paquet "v0.9.9" -> (+ _ _ +) -> combinar codi -> resum
90```
91
92A més, qualsevol expressió amb efectes secundaris (escriure un fitxer, invocar el sistema) es pot posar sola a la seva línia sense `echo` ni variables mudes `_ =`, o acabar en `-> ()` si vols descartar explícitament el seu retorn.
93
94### 5. Clausures lèxiques i funcions anònimes (`(x => expr)`, `(acc x => expr)`)
95Quan vols passar una funció curta al vol o capturar variables locals del teu àmbit actual (com un desplaçament, un prefix o una sessió activa) sense declarar una funció global auxiliar, fas servir una **clausura lèxica** entre parèntesis amb **`=>`**:
96
97```ridgun
98fn :: processar amb sessio => [Int]
99 processar amb sessio =
100 base = 10
101 bo = 3
102 sumats = map (x => x + base) [1, 2, 3]
103 filtrats = filter (x => x >= bo) [1, 2, 3, 4]
104 total = fold (acc x => acc + x + bo) 0 [1, 2, 3]
105 parells = map ((a b) => a + b + base) [(1, 2), (3, 4)]
106 sumats :: filtrats :: [total] :: parells
107```
108
109Cinc regles mantenen les lambdes netes i sense ambigüitat:
110- **Parèntesis obligatoris**: tota lambda va sempre entre parèntesis `(... => ...)`.
111- **Lambda sense paràmetres (`(=> expr)`)**: igual que `fn :: main => ()` no porta `()` abans de `=>`, una lambda sense paràmetres s'escriu `(=> expr)` (i el seu tipus és `(=> Tipus)`), mai `(() => expr)`.
112- **Diversos paràmetres enfront de tupla (`(a b => ...)` vs. `((a b) => ...)`)**: `(acc x => ...)` rep dos paràmetres separats; `((a b) => ...)` rep una sola tupla i la desestructura.
113- **Cos de diverses línies**: si poses un salt de línia després de `=>`, pots escriure un bloc sagnat de diversos passos abans del `)` de tancament:
114 ```ridgun
115 dobles = map (x =>
116 pas = x * 2
117 pas + base
118 ) [1, 2, 3]
119 ```
120- **Majúscules enfront de minúscules**: `(Conn => Conn)` és un tipus de funció (inicial majúscula) i `(conn => conn)` és un valor lambda (inicial minúscula).
121
122Les variables capturades (`base`, `bo`) s'empaqueten automàticament amb gestió determinista de memòria (`RMM1`/`RMM2`), sense fuites i sense pauses de recol·lector de brossa.
123
124---
125
126## 7.2. La joia de la corona: Blocs `||` amb `->` com a Grafs Paral·lels (DAGs)
127
128Aquí és on Ridgun canvia la teva manera de pensar.
129
130En un llenguatge imperatiu normal, quan escrius tres línies seguides:
1311. Primer s'executa la línia 1.
1322. Després la línia 2 (encara que no tingui res a veure amb la línia 1).
1333. Després la línia 3.
134
135Això és obligar l'ordinador a treballar amb clucales. En Ridgun, quan fas servir barres paral·leles **`||`** amb fletxes **`->`** (sense necessitat d'escriure cap paraula clau com `where`), no estàs donant ordres seqüencials: estàs dibuixant un **Graf Acíclic Dirigit (DAG)** que diu quin càlcul alimenta quina variable:
136
137```ridgun
138fib Int => Int
139fib 0 = 0
140fib 1 = 1
141fib n = (fib (n - 1)) + (fib (n - 2))
142
143fact Int => Int
144fact 0 = 1
145fact n = n * (fact (n - 1))
146
147super calcul => Int
148super calcul = branca_esquerra + branca_dreta
149 || fib 10 -> branca_esquerra
150 || fact 6 -> branca_dreta
151```
152
153Llegeix-ho en veu alta:
154- *"Vull calcular `branca_esquerra + branca_dreta`, mentre en paral·lel (`||`) `fib 10` va a `branca_esquerra` (`->`) i `fact 6` va a `branca_dreta` (`->`)."*
155
156### Què fa `ridgunc` entre bastidors amb les teves línies `||`:
1571. Mira les fletxes `->` i munta l'arbre de dependències (DAG).
1582. **Caça cicles estàticament**: si per error escrius `|| a + 1 -> b` i `|| b + 1 -> a`, el compilador et diu a l'instant *"Quiets! Hi ha una dependència circular"* i no et deixa compilar un bloqueig infinit.
1593. Veu que `fib 10` i `fact 6` no depenen l'un de l'altre, així que tenen llum verda per córrer en paral·lel abans de sumar-se a `branca_esquerra + branca_dreta`.
160
161Fins i tot pots muntar una canonada de diverses plantes dins del mateix bloc `||` —ja sigui al peu de l'expressió o just després de l'`=`— i el compilador deduirà tot sol quines plantes poden anar alhora i quines s'han d'esperar:
162
163```ridgun
164balanc trimestral Int => Int
165balanc trimestral base =
166 || base * 100 -> vendes_brutes
167 || base * 40 -> costos_fixos
168 || vendes_brutes - costos_fixos -> benefici_brut ||.
169 benefici_brut * 80 / 100
170```
171
172Fixa't en el detall **`||.`** al final de l'última línia `||`: igual que `|.` tanca un arbre de guardes imbricat, **`||.`** tanca el bloc paral·lel `||` quan a sota continuen sentències seqüencials normals. Aquí `vendes_brutes` i `costos_fixos` arrenquen en paral·lel; quan tots dos estan a punt es calcula `benefici_brut`, i finalment es retorna `benefici_brut * 80 / 100`. Tot sense un sol `async`, `Future`, `Promise` ni `Mutex`.
173
174---
175
176## 7.3. La drecera `||` sense noms: Canonades paral·leles que retornen una tupla
177
178I si només vols llançar dos o tres càlculs en paral·lel i recollir-los junts en una tupla sense ni tan sols inventar noms de variables amb `->`?
179
180Fas servir `||` directament:
181
182```ridgun
183parell parallel Int => (Int, Int)
184parell parallel n =
185 || fib n
186 || fact n
187```
188
189Directe, visual i retorna la tupla `(fib n, fact n)`.
190
191---
192
193## 7.4. Quan vols el volant manual: el Model Natiu d'Agents (`agent`, `send ... to`, `receive from`) i Canals CSP
194
195El paral·lelisme automàtic de `||` i `||map` cobreix el 90% dels casos. Però quan programes un servidor persistent, sessions d'usuari amb estat aïllat, cues de treballs o una canonada productor-consumidor, vols **agents concurrents amb memòria aïllada i pas de missatges tipat**.
196
197En Ridgun no cal inventar cap sintaxi estranya com `actor ::`: **qualsevol funció normal (`fn ::` o `op ::`) es converteix en un agent concurrent en una sola línia amb `agent`**:
198
199### 1. Agents sense estat i amb estat aïllat (`agent`, `send ... to`, `receive from`, `stop`)
200- `ag = agent (la meva funcio)`: converteix una funció `M => R` en un agent sense estat de tipus `Agent M R`.
201- `ag = agent (funcio amb estat) estat_inicial`: converteix una funció `S M => S` (o `S M => S R`) en un agent amb estat aïllat que recorda el seu estat missatge rere missatge sense cap `Mutex`.
202- `send msg to ag` (o amb diversos arguments: `send a b to ag`): envia un missatge tipat a la bústia de l'agent.
203- `res = receive from ag` (o `try receive from ag` sense bloquejar): recull la següent resposta de la bústia de sortida de l'agent.
204- `stop ag`: atura l'agent i allibera el seu estat automàticament mitjançant `RMM1`/`RMM2`.
205
206```ridgun
207fn :: sumar visita Int Int => Int
208 sumar visita acum n = acum + n
209
210fn :: processar lot Int Int => Int
211 processar lot id t = (id * 100) + t
212
213fn :: main => ()
214 main =
215 comptador = agent (sumar visita) 10
216 send 5 to comptador
217 send 7 to comptador
218 v1 = receive from comptador
219 v2 = receive from comptador
220 stop comptador
221
222 treballador = agent (processar lot)
223 send 3 25 to treballador
224 lot = receive from treballador
225 stop treballador
226 echo "Visites:" v1 "->" v2 "| Lot:" lot
227```
228
229### 2. Elecció intel·ligent en temps de compilació (*Green Threads* vs. Fils Reals del Nucli)
230Una pregunta habitual és: *"Els blocs `||`, `||map` i els agents `agent` creen sempre fils del sistema operatiu o corren com a tasques lleugeres?"*
231
232En Ridgun, **el compilador pren aquesta decisió per tu analitzant estàticament cada funció**:
233- **Funcions lleugeres (`TierTrivial` / `StratInlineSequential`)**: Si la funció que passes a `||`, `||map`, `||filter` o `agent` té poca profunditat (aritmètica, formatat de cadenes, transició d'estat ràpida d'una sessió HTTP), escalfar fils del sistema operatiu costaria més que el propi càlcul. El compilador l'executa com un pas *green-thread* ultralleuger sense sobrecàrrega de `SYS_clone`.
234- **Càlculs pesats o recursius profunds (`TierDynamicRecursive` / `TierHeavyCPU`)**: Si la funció té recursió profunda (com `fib` o `fact`) o àlgebra de tensors (`einsum`, `hadamard`, `@`), el compilador genera automàticament el despatx multinucli sobre **fils reals del nucli (`SYS_clone`)**, processant les branques `||` o la cua de missatges de l'`agent` en paral·lel real sobre tots els teus nuclis de CPU.
235
236### 3. Passar missatges per canals afitats (`new bounded channel`)
237Igual que en Go o Erlang, a més d'agents també pots crear tubs explícits per comunicar etapes:
238
239- `new bounded channel capacitat`: crea un canal amb espai per a `capacitat` missatges.
240- `send through valor canal` (o `chan send valor canal`): fica un missatge al tub.
241- `receive from canal` (o `chan recv canal`): treu el següent missatge del tub.
242- `try receive from canal`: mira si hi ha missatge sense quedar-se esperant; retorna `Some valor` o `None`.
243- `receive from timeout canal ticks`: espera un límit de temps i retorna `Ok valor` o `Err Timeout`.
244
245```ridgun
246fn :: main => ()
247 main =
248 tub = new bounded channel 4
249 _ = send through "Tasca A completada" tub
250 _ = send through "Tasca B completada" tub
251 primer = receive from tub
252 segon = receive from tub
253 echo primer "|" segon
254```
255
256### 4. Agents embornal (*fire-and-forget* `agent sink`), bústies globals (`agent ::`) i proves deterministes (`with agent`)
257Quan necessites auditoria asíncrona, mètriques o traçabilitat transversal sense embrutar totes les signatures ni acumular respostes sense llegir en una cua de sortida, Ridgun ofereix **agents embornal (`agent sink`)** i **bústies globals de nivell superior (`agent ::`)** (`agent dropped`, `agent flush`, `agent state`, i `with agent audit_log = espia do ...`), a més de descriptors `Channel` i `Agent` compartibles entre múltiples treballadors.
258
259I ara, corda't el cinturó per al **Capítol 08**, el gran final: **tensors amb dimensions verificades en compilació, variables d'àmbit (`context`/`shared`), regions de memòria deterministes, accés directe a maquinari MMIO i programació de sistemes UNIX/POSIX**.