Skip to content
Ridgun
English

Ridgun

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

07-orde-superior-e-paralelismo-dag.md259 lines · 13.6 KBRaw
1# Capítulo 07: Funcións de Orde Superior e Paralelismo sen Suor (Nin `Mutex`, nin `Deadlocks`)
2
3En case todas as linguaxes de programación hai un momento dramático na vida dun proxecto: o día en que alguén di nunha xuntanza:
4
5> *"Isto vai lento. Hai que paralelizalo con fíos."*
6
7Faise o silencio na sala. Todo o mundo sabe o que vén despois: semanas cazando *race conditions* pantasma que só ocorren os martes con lúa chea, bloqueos mutuos (*deadlocks*) e erros irreproducibles.
8
9En Ridgun, cando alguén di *"hai que paralelizalo"*, abonda con antepoñer `||` ás túas operacións de lista (**`||map`** e **`||filter`**) ou poñer os teus cálculos nun bloque paralelo `||` con frechas `->` e vas tomar un café.
10
11Por que? Porque **cando as túas funcións son puras e os teus datos son inmutables por defecto, dous núcleos de CPU traballando á vez xamais poden pisarse a mangueira**.
12
13Imos velo paso a paso.
14
15---
16
17## 7.1. Funcións que reciben funcións: `map`, `||map`, `filter`, `||filter` e `fold`
18
19En Ridgun unha función é un valor máis. Podes pasala por parámetro igual que pasas o número `7`.
20
21A linguaxe trae de serie os combinadores de coleccións para transformar listas sen escribir nin unha soa recursión manual, tanto en modo secuencial (`map`, `filter`, `fold`) como en modo paralelo multicore co prefixo `||` (**`||map`**, **`||filter`**):
22
23### 1. `map` (secuencial) e `||map` (paralelo multicore)
24Supón que tes unha función `custoso Int => Int` e unha lista con números:
25
26```ridgun
27custoso Int => Int
28custoso x = (x * x) + (x * 3)
29
30main => ()
31main =
32 datos = [1, 2, 3, 4, 5, 6, 7, 8]
33 -- Secuencial estrito de esquerda a dereita:
34 r1 = map custoso datos
35 -- Paralelo multicore co prefixo `||`:
36 r2 = ||map custoso datos
37 echo r1
38 echo r2
39```
40
41- Con **`map`**, Ridgun aplica a función elemento a elemento de esquerda a dereita no fío actual (ideal para listas pequenas ou funcións con efectos de entrada/saída ordenados).
42- Con **`||map`**, o runtime de Ridgun reparte os elementos de `datos` entre os fíos traballadores do teu procesador e reconstrúe a lista final **exactamente na mesma orde**, garantindo que `||map f xs == map f xs`.
43
44### 2. `filter` (secuencial) e `||filter` (paralelo): Quedar só cos que cumpran unha condición
45Pásaslle unha función que devolva `Bool` (un *predicado*) e unha lista:
46
47```ridgun
48e positivo Int => Bool
49e positivo n = n > 0
50
51main => ()
52main =
53 limpos = filter e positivo [-5, 10, 0, -2, 42]
54 limpos_par = ||filter e positivo [-5, 10, 0, -2, 42]
55 echo limpos -- Imprime [10, 42]
56 echo limpos_par -- Imprime [10, 42]
57```
58
59### 3. `fold`: Reducir toda unha lista a un só resultado
60Pásaslle unha función combinadora de dous argumentos `(Acumulado Elemento => Acumulado)`, o valor inicial e a lista:
61
62```ridgun
63acumular produto Int Int => Int
64acumular produto acc x = acc * x
65
66main => ()
67main =
68 echo (fold acumular produto 1 [2, 3, 4, 5]) -- 1 * 2 * 3 * 4 * 5 = 120
69```
70
71### Como escribo eu a miña propia función que reciba outra función?
72Moi doado: na sinatura de tipos pos a sinatura da función convidada entre parénteses `(Entrada => Saida)`:
73
74```ridgun
75executar duas veces (A => A) A => A
76executar duas veces f x = f (f x)
77```
78
79### 4. Tubaxes fluídas de esquerda a dereita (`->`) — O estilo 100% Ridgun!
80Cando encadeas varias transformacións (`map`, `filter`, `fold` ou as túas propias funcións e operadores `op ::`), aniñar parénteses de dereita a esquerda (`compact l (filter g (map f xs))`) obrígache a ler de dentro cara a fóra.
81
82En Ridgun, o **estilo idiomático recomendado (`2b`)** lese de esquerda a dereita co operador de tubaxe **`->`**, onde **o que vén da esquerda entra sempre como primeiro argumento** da función ou `op ::` seguinte:
83
84```ridgun
85-- Estilo idiomático recomendado en Ridgun (2b):
86xs map (x => x * 2) -> filter (x => x > 10) -> fold (acc x => acc + x) 0 -> resultado
87
88-- E se unha función devolve unha tupla, fíltrala cunha máscara e desempaquétase soa:
89parsear paquete "v0.9.9" -> (+ _ _ +) -> combinar codigo -> resumo
90```
91
92Ademais, calquera expresión con efectos secundarios (escribir un ficheiro, invocar o sistema) pódese poñer soa na súa liña sen `echo` nin variables mudas `_ =`, ou rematar en `-> ()` se queres descartar explicitamente o seu retorno.
93
94### 5. Clausuras léxicas e funcións anónimas (`(x => expr)`, `(acc x => expr)`)
95Cando queres pasar unha función curta ao voo ou capturar variables locais do teu ámbito actual (como un desprazamento, un prefixo ou unha sesión activa) sen declarar unha función global auxiliar, usas unha **clausura léxica** entre parénteses con **`=>`**:
96
97```ridgun
98fn :: procesar con sesion => [Int]
99 procesar con sesion =
100 base = 10
101 bono = 3
102 sumados = map (x => x + base) [1, 2, 3]
103 filtrados = filter (x => x >= bono) [1, 2, 3, 4]
104 total = fold (acc x => acc + x + bono) 0 [1, 2, 3]
105 pares = map ((a b) => a + b + base) [(1, 2), (3, 4)]
106 sumados :: filtrados :: [total] :: pares
107```
108
109Cinco regras manteñen as lambdas limpas e sen ambigüidade:
110- **Parénteses obrigatorias**: toda lambda vai sempre entre parénteses `(... => ...)`.
111- **Lambda sen parámetros (`(=> expr)`)**: igual que `fn :: main => ()` non leva `()` antes de `=>`, unha lambda sen parámetros escríbese `(=> expr)` (e o seu tipo é `(=> Tipo)`), nunca `(() => expr)`.
112- **Varios parámetros fronte a tupla (`(a b => ...)` vs. `((a b) => ...)`)**: `(acc x => ...)` recibe dous parámetros separados; `((a b) => ...)` recibe unha soa tupla e desestrutúraa.
113- **Corpo de varias liñas**: se pos un salto de liña tras `=>`, podes escribir un bloque sangrado de varios pasos antes do `)` de peche:
114 ```ridgun
115 dobres = map (x =>
116 paso = x * 2
117 paso + base
118 ) [1, 2, 3]
119 ```
120- **Maiúsculas fronte a minúsculas**: `(Conn => Conn)` é un tipo de función (inicial maiúscula) e `(conn => conn)` é un valor lambda (inicial minúscula).
121
122As variables capturadas (`base`, `bono`) empaquétanse automaticamente con xestión determinista de memoria (`RMM1`/`RMM2`), sen fugas e sen pausas de colector de lixo.
123
124---
125
126## 7.2. A xoia da coroa: Bloques `||` con `->` como Grafos Paralelos (DAGs)
127
128Aquí é onde Ridgun muda a túa forma de pensar.
129
130Nunha linguaxe imperativa normal, cando escribes tres liñas seguidas:
1311. Primeiro execútase a liña 1.
1322. Logo a liña 2 (aínda que non teña nada que ver coa liña 1).
1333. Logo a liña 3.
134
135Iso é obrigar o ordenador a traballar con anteolleiras. En Ridgun, cando usas barras paralelas **`||`** con frechas **`->`** (sen necesidade de escribir ningunha palabra chave como `where`), non estás dando ordes secuenciais: estás debuxando un **Grafo Acíclico Dirixido (DAG)** que di que cálculo alimenta a que 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 calculo => Int
148super calculo = rama_esquerda + rama_dereita
149 || fib 10 -> rama_esquerda
150 || fact 6 -> rama_dereita
151```
152
153Leo en voz alta:
154- *"Quero calcular `rama_esquerda + rama_dereita`, mentres en paralelo (`||`) `fib 10` vai a `rama_esquerda` (`->`) e `fact 6` vai a `rama_dereita` (`->`)."*
155
156### O que fai `ridgunc` entre bastidores coas túas liñas `||`:
1571. Mira as frechas `->` e arma a árbore de dependencias (DAG).
1582. **Caza ciclos estaticamente**: se por erro escribes `|| a + 1 -> b` e `|| b + 1 -> a`, o compilador diche no acto *"Quietos! Hai unha dependencia circular"* e non che deixa compilar un colgue infinito.
1593. Ve que `fib 10` e `fact 6` non dependen o un do outro, así que teñen luz verde para correr en paralelo antes de sumarse en `rama_esquerda + rama_dereita`.
160
161Mesmo podes montar unha tubaxe de varias plantas dentro do mesmo bloque `||` —xa sexa ao pé da expresión ou xusto despois do `=`— e o compilador deducirá el só que plantas poden ir á vez e cales teñen que agardar:
162
163```ridgun
164balance trimestral Int => Int
165balance trimestral base =
166 || base * 100 -> vendas_brutas
167 || base * 40 -> custos_fixos
168 || vendas_brutas - custos_fixos -> beneficio_bruto ||.
169 beneficio_bruto * 80 / 100
170```
171
172Fíxate no detalle **`||.`** ao final da última liña `||`: igual que `|.` pecha unha árbore de gardas aniñada, **`||.`** pecha o bloque paralelo `||` cando debaixo seguen sentenzas secuenciais normais. Aquí `vendas_brutas` e `custos_fixos` arrincan en paralelo; cando ambos están listos calcúlase `beneficio_bruto`, e finalmente devólvese `beneficio_bruto * 80 / 100`. Todo sen un só `async`, `Future`, `Promise` nin `Mutex`.
173
174---
175
176## 7.3. O atallo `||` sen nomes: Tubaxes paralelas que devolven unha tupla
177
178E se só queres lanzar dous ou tres cálculos en paralelo e recollelos xuntos nunha tupla sen sequera inventar nomes de variables con `->`?
179
180Usas `||` directamente:
181
182```ridgun
183par paralelo Int => (Int, Int)
184par paralelo n =
185 || fib n
186 || fact n
187```
188
189Directo, visual e devolve a tupla `(fib n, fact n)`.
190
191---
192
193## 7.4. Cando queres o volante manual: o Modelo Nativo de Axentes (`agent`, `send ... to`, `receive from`) e Canles CSP
194
195O paralelismo automático de `||` e `||map` cobre o 90% dos casos. Pero cando programas un servidor persistente, sesións de usuario con estado illado, filas de traballos ou unha tubaxe produtor-consumidor, queres **axentes concorrentes con memoria illada e paso de mensaxes tipado**.
196
197En Ridgun non fai falta inventar unha sintaxe estraña como `actor ::`: **calquera función normal (`fn ::` ou `op ::`) convértese nun axente concorrente nunha liña con `agent`**:
198
199### 1. Axentes sen estado e con estado illado (`agent`, `send ... to`, `receive from`, `stop`)
200- `ag = agent (miña funcion)`: converte unha función `M => R` nun axente sen estado de tipo `Agent M R`.
201- `ag = agent (funcion con estado) estado_inicial`: converte unha función `S M => S` (ou `S M => S R`) nun axente con estado illado que lembra o seu estado mensaxe tras mensaxe sen un só `Mutex`.
202- `send msg to ag` (ou con varios argumentos: `send a b to ag`): envía unha mensaxe tipada á caixa de correo do axente.
203- `res = receive from ag` (ou `try receive from ag` sen bloquear): recolle a seguinte resposta da caixa de saída do axente.
204- `stop ag`: detén o axente e libera o seu estado automaticamente mediante `RMM1`/`RMM2`.
205
206```ridgun
207fn :: sumar visita Int Int => Int
208 sumar visita acum n = acum + n
209
210fn :: procesar lote Int Int => Int
211 procesar lote id t = (id * 100) + t
212
213fn :: main => ()
214 main =
215 contador = agent (sumar visita) 10
216 send 5 to contador
217 send 7 to contador
218 v1 = receive from contador
219 v2 = receive from contador
220 stop contador
221
222 traballador = agent (procesar lote)
223 send 3 25 to traballador
224 lote = receive from traballador
225 stop traballador
226 echo "Visitas:" v1 "->" v2 "| Lote:" lote
227```
228
229### 2. Elección intelixente en tempo de compilación (*Green Threads* vs. Fíos Reais do Kernel)
230Unha pregunta habitual é: *"Os bloques `||`, `||map` e os axentes `agent` crean sempre fíos do sistema operativo ou corren como tarefas lixeiras?"*
231
232En Ridgun, **o compilador toma esa decisión por ti analizando estaticamente cada función**:
233- **Funcións lixeiras (`TierTrivial` / `StratInlineSequential`)**: Se a función que pasas a `||`, `||map`, `||filter` ou `agent` ten pouca profundidade (aritmética, formatado de cadeas, transición de estado rápida dunha sesión HTTP), quentar fíos do sistema operativo custaría máis có propio cálculo. O compilador execútaa como un paso *green-thread* ultralixeiro sen sobrecarga de `SYS_clone`.
234- **Cálculos pesados ou recursivos profundos (`TierDynamicRecursive` / `TierHeavyCPU`)**: Se a función ten recursión profunda (como `fib` ou `fact`) ou álxebra de tensores (`einsum`, `hadamard`, `@`), o compilador xera automaticamente o despacho multinúcleo sobre **fíos reais do kernel (`SYS_clone`)**, procesando as ramas `||` ou a cola de mensaxes do `agent` en paralelo real sobre todos os teus núcleos de CPU.
235
236### 3. Pasar mensaxes por canles acoutadas (`new bounded channel`)
237Igual ca en Go ou Erlang, ademais de axentes tamén podes crear tubos explícitos para comunicar etapas:
238
239- `new bounded channel capacidade`: crea unha canle con espazo para `capacidade` mensaxes.
240- `send through valor canle` (ou `chan send valor canle`): mete unha mensaxe no tubo.
241- `receive from canle` (ou `chan recv canle`): saca a seguinte mensaxe do tubo.
242- `try receive from canle`: mira se hai mensaxe sen quedar agardando; devolve `Some valor` ou `None`.
243- `receive from timeout canle ticks`: agarda un límite de tempo e devolve `Ok valor` ou `Err Timeout`.
244
245```ridgun
246fn :: main => ()
247 main =
248 tubo = new bounded channel 4
249 _ = send through "Tarefa A completada" tubo
250 _ = send through "Tarefa B completada" tubo
251 primeiro = receive from tubo
252 segundo = receive from tubo
253 echo primeiro "|" segundo
254```
255
256### 4. Axentes sumidoiro (*fire-and-forget* `agent sink`), caixas de correo globais (`agent ::`) e probas deterministas (`with agent`)
257Cando precisas auditoría asíncrona, métricas ou trazabilidade transversal sen ensuciar todas as sinaturas nin acumular respostas sen ler nunha cola de saída, Ridgun ofrece **axentes sumidoiro (`agent sink`)** e **caixas de correo globais de nivel superior (`agent ::`)** (`agent dropped`, `agent flush`, `agent state` e `with agent audit_log = espia do ...`), ademais de descritores `Channel` e `Agent` compartibles entre múltiples traballadores.
258
259E agora, abrocha o cinto para o **Capítulo 08**, o gran final: **tensores con dimensións verificadas en compilación, variables de ámbito (`context`/`shared`), rexións de memoria deterministas, acceso directo a hardware MMIO e programación de sistemas UNIX/POSIX**.