08 El Llibre de Ridgun
Tensors, Memòria Compilada RMM (RMM1/RMM2), Regions i Sistemes
Fins aquí hem vist el costat més amable, elegant i matemàtic de Ridgun: funcions pures, tipus algebraics, pattern matching, arbres de decisió amb | / |. i grafs paral·lels || amb -> que aprofiten tots els nuclis del teu processador sense demanar-te ni un trist Mutex.
Arribats a aquest punt, el programador veterà de sistemes sol aixecar la mà al fons de l'aula amb mig somriure i preguntar:
"Tot això és preciós per calcular factures o compilar ASTs, però què passa quan necessito multiplicar matrius de milions de flotants a velocitat de vertigen? Què passa amb la memòria? Hi ha un recol·lector de brossa (Garbage Collector) amagat sota la catifa esperant el pitjor moment per congelar el meu programa durant 200 mil·lisegons? I com parlo amb el sistema operatiu Unix de debò?"
Excel·lents preguntes. Aixequem el capó de Ridgun per veure com se les gasta quan toca baixar al fang dels sistemes, el càlcul numèric i la memòria determinista.
8.1. El dilema de la memòria: ni Garbage Collector ni baralles amb el Borrow Checker (RMM1 i RMM2)
Al món dels llenguatges moderns sol haver-hi dos bàndols irreconciliables:
- El bàndol del Garbage Collector (Java, Go, Python, JS, Haskell, OCaml): tu demanes memòria alegrement i, de tant en tant, un camió de la brossa invisible atura el trànsit del teu programa per rastrejar quins objectes continuen vius i quins no. És comodíssim... fins que estàs fent una eina de sistema ultraràpida o un servidor amb latències estrictes i el camió decideix aparcar en doble fila.
- El bàndol del control manual / Borrow Checker (C, C++, Rust): no hi ha recol·lector de brossa, però a canvi has de gestionar punters a mà (jugant-te un
Segmentation faulto un use-after-free) o passar-te mitja tarda negociant amb el borrow checker per què'ano viu tant com'b.
Ridgun resol aquest dilema de soca-rel amb Ridgun Memory Management (RMM), un sistema determinista de dues polítiques complementàries (RMM1 i RMM2) combinat amb regions lèxiques (with region):
Política RMM1 — Gestió de Memòria Compilada (CMM Lineal i Condicional)
Quan el compilador coneix estàticament el flux de les variables locals dins d'una funció:
- CMM Lineal:
ridgunccalcula en temps de compilació quina és l'última línia en què es llegeix cada variable local (a,b, ...) i insereix just després el seu alliberament determinista (rg_cmm_drop), o transfereix la seva propietat sense còpies (rg_cmm_move) quan la variable es retorna com a resultat. - CMM Condicional (
ptr(a) != ptr(c)): i si una funció comtriar cond a bretornaaobsegons una condició en temps d'execució?ridguncconstrueix la taula d'ancestres i emet una guarda d'identitat de punters d'1 cicle de CPU: sibdeixa de fer-se servir mentreccontinua viva, comprovaif ptr(b) != ptr(c) { free(b) }. La branca no triada s'allibera a l'instant i la triada sobreviu intacta.
Política RMM2 — Recompte per Assignació i Cascada Estructural
I què passa quan fiques estructures dins d'altres estructures (xs = [a, b]), reassignes variables mutables (mut x = ...) o encadenes crides complexes? Aquí entra RMM2, que governa tot el cicle de vida amb quatre regles matemàtiques sense fissures:
- Els arguments d'entrada d'una funció no es toquen (
+0en entrar,-0en sortir): passar una llista o un registre a una funció mai no incrementa ni decrementa comptadors en va. - Tota assignació a una variable local interna o cel·la de contenidor suma
+1:
a = [10, 20]$\rightarrow$ el blocM1neix amb comptador1.xs = [a, b]$\rightarrow$ la llistaxsneix amb1, i com que desa referències aaib, tots dos pugen a2.- Si reassignes una variable mutable (
mut x = ...i desprésx = nou),RMM2aplica primer+1al valor nou abans d'aplicar-1a l'antic.
- En sortir de la funció, les variables locals que NO es retornen reben
-1: si el comptador d'una estructura arriba a0, s'allibera la seva memòria a l'instant i propaga-1en cascada, un per un, a cadascun dels seus fills directes. - El valor retornat per la funció s'eximeix en trànsit: rep el seu
-1de variable local però no es destrueix en creuar elreturn, de manera que quan qui ha cridat fac = la meva funcio (), aquell+1de l'assignació el deixa exactament en1.
triar Bool [Int] [Int] => [Int]
triar cond a b =
| cond = a
| otherwise = b
exemple rmm2 Bool => Int
exemple rmm2 cond =
a = [10, 20, 30] -- M1 (rc = 1)
b = [40, 50, 60] -- M2 (rc = 1)
c = triar cond a b -- `c` rep +1; el no triat mor aquí mateix (rc -> 0)
(comptar a) + (comptar c) -- En acabar, `a` i `c` reben -1 i tot queda en 0 blocs vius
8.2. Regions Lèxiques amb Quota (with region): Tallafocs de memòria en $O(1)$
A més d'RMM1 i RMM2, quan estàs processant un paquet de xarxa extern, un fitxer gegant o un lot de dades no confiables i vols posar un sostre estricte de memòria RAM, envoltes el càlcul en un bloc with region:
processar trama String => Result
processar trama cru =
with region 64_KB do
netes = map netejar linia (dividir linies cru)
comptar valids netes
- Quota garantida en maquinari/runtime: si dins del bloc s'intenta reservar ni un sol byte per sobre de
64_KB(65.536bytes), el runtime avorta netament el bloc, allibera tota l'arena temporal en temps constant $O(1)$ i retornaErr OutOfMemorysense tombar el procés. - Alliberament instantani en sortir: tan bon punt acaba el bloc
with region, tota la memòria temporal de l'arena desapareix de cop sense deixar fragmentació.
8.3. Tensors Natius, Contracció d'Einstein (@) i Dimensions Simbòliques
Si alguna vegada has programat en Python amb NumPy o PyTorch, segur que has patit el clàssic error al minut 41 d'execució:
RuntimeError: mat1 and mat2 shapes cannot be multiplied (128x64 and 32x128)
En Ridgun, els Tensor són ciutadans natius de primera classe i les seves dimensions formen part de la signatura de tipus:
-- Si entra (N × K) i (K × M), per nassos surt (N × M)!
multiplicar matrius Tensor[N, K] Tensor[K, M] => Tensor[N, M]
multiplicar matrius a b = a_ik @ b_kj
Fixa't en tres detalls espectaculars:
- Verificació estàtica de dimensions (
Tensor[N, K]): l'eix internKde la primera matriu ha de coincidir amb el primer eixKde la segona. Si intentes passar dimensions incompatibles,ridgunc checkt'atura abans d'executar res. - Notació de subíndexs d'Einstein (
a_ik @ b_kj) i Hadamard (a_ij * b_j):
- Amb
@, els subíndexs repetits en tots els operands (k) es contrauen (es sumen) i els únics (i,j) sobreviuen al tensor resultat. - Amb
+,-,*,/, els subíndexs alineen dimensions i fan broadcasting automàtic (a_ij * b_jmultiplica cada filaid'apel vectorb_jsense necessitat d'escriureunsqueezenireshape).
- Tensors tipats d'alt rendiment (
Tensor[U64 * N]) i mutació in-place ambt_(i, j): En sistemes criptogràfics, simulacions físiques o àlgebra lineal pots declarar tensors plans sobre un bloc contigu d'enters sense signe de 64 bits (Tensor[U64 * 4],Tensor[U64 * 8 * 16]), operar en paral·lel amb^(XOR natiu de bloc) i llegir o escriure elements en temps $O(1)$ amb la sintaxi de subíndex explícitt_(i)ot_(i, j):
crear bloc estat => Tensor[U64 * 2 * 2]
crear bloc estat =
mut m = tensor fill [2, 2] (u64 0)
m_(0, 1) = u64 0x6a09e667f3bcc908
m
8.4. Accés a Maquinari (hardware ::) i Primitives POSIX de Sistemes
El mateix compilador ridgunc, el gestor stable i utilitats de sistemes Unix estan escrits en Ridgun. Per a això, el llenguatge inclou blocs de registres mapejats en memòria (hardware ::) i primitives POSIX directes:
hardware :: UART0 @ 0x1000_0000
tx_data : Int @ 0x00
status : Int @ 0x04
I per treballar amb el sistema operatiu:
| Primitiva | Signatura | Què fa |
|---|---|---|
cli args | => [String] | Arguments de línia d'ordres (argv). |
read file / write file | String => String / String String => () | Lectura i escriptura íntegra de fitxers en disc. |
read bytes / write bytes | String => [Int] / String [Int] => () | Lectura i escriptura binària exacta byte a byte (0..255). |
list dir / file lstat | String => [String] / String => ... | Llistat de directoris i inspecció de metadades POSIX (lstat). |
sha256 file / sha256 str / sha256 raw | String => String | Resum criptogràfic SHA-256 natiu (en hexadecimal de 64 caràcters o digest binari cru de 32 bytes amb sha256 raw / sha256 bytes). |
sys exec / shell exec | ... => Int | Execució de processos del sistema operatiu. |
sys getcwd / sys chdir | => String / String => Int | Consultar o canviar el directori de treball. |
sys getenv / sys setenv | String => String / String String => Int | Variables d'entorn del procés. |
sys getuid / sys getgid | => Int | UID i GID reals del procés (doas, eines de seguretat). |
sys exit | Int => () | Acabar el procés amb codi de sortida. |
8.5. L'Ecosistema de Llibreries Estàndard (ridgun-libs)
A més de les primitives del compilador, l'ecosistema oficial de Ridgun inclou 32 llibreries pures (.rglib) escrites íntegrament en Ridgun que pots importar amb import <nom> en qualsevol projecte:
| Categoria | Llibreries disponibles | Què aporten |
|---|---|---|
| Col·leccions, Text i Dades | collections, set, string, regex, utf8, fmt, csv, json, toml, yaml, xml, html, markdown, base64, hex, compress, sql | Mapes, conjunts, expressions regulars, parsers/serialitzadors estructurats, codificació i motors de consultes. |
| Matemàtiques, Temps i Aleatorietat | math, random, datetime, uuid, log, cli, bench, test | Funcions matemàtiques, PRNG determinista i de sistema, dates ISO-8601, UUIDs, marcs de tests i benchmarks amb clock_gettime. |
| Sistemes, Xarxa i Web | fs, path, process, env, net, http, uri | Sistema de fitxers d'alt nivell, manipulació de rutes, sub-processos, sockets TCP/UDP i client/servidor HTTP. |
Criptografia i Seguretat (crypto) | crypto (sha256, hmac, hkdf, pbkdf2, argon2id, chacha20, aes, x25519, ed25519, csprng) | Suite criptogràfica verificada amb vectors oficials RFC/NIST, implementada sobre U64, Tensor[U64 * ...] i sha256 raw amb optimització d'inlining de tuples sense reserves al heap. |
8.6. Cas d'Estudi Real: Construint rg-wc, una utilitat Unix paral·lela
Unirem tot el que hem après des del Capítol 01 fins aquí en un programa real de sistemes: una versió paral·lela de l'eina Unix wc (comptador de línies i bytes per a múltiples fitxers) escrita en Ridgun pur:
-- 1. Modelem les mètriques d'un fitxer amb un registre ADT (Capítol 06)
type Metriques = fitxer: String, linies: Int, bytes: Int
-- 2. Comptem salts de línia (codi ASCII 10) amb recursió de cua TCO (Capítol 04)
fn :: comptar salts String Int Int Int => Int
comptar salts text pos fi acum =
| pos >= fi = acum
| (str char at text pos) == 10 = comptar salts text (pos + 1) fi (acum + 1)
| otherwise = comptar salts text (pos + 1) fi acum
fn :: nombre de linies String => Int
nombre de linies text = comptar salts text 0 (str len text) 0
-- 3. Analitzem un fitxer i retornem la seva estructura Metriques
fn :: analitzar fitxer String => Metriques
analitzar fitxer ruta =
contingut = read file ruta
|| nombre de linies contingut -> n_linies
|| str len contingut -> n_bytes ||.
Metriques ruta n_linies n_bytes
-- 4. Formatem cada fila i sumem totals amb `fold` i `||` (Capítol 07)
fn :: formatar metrica Metriques => String
formatar metrica m = "{linies m}\t{bytes m}\t{fitxer m}"
fn :: sumar linies Int Metriques => Int
sumar linies acc m = acc + (linies m)
fn :: sumar bytes Int Metriques => Int
sumar bytes acc m = acc + (bytes m)
fn :: generar informe [String] => String
generar informe rutes =
metriques = ||map analitzar fitxer rutes
|| map formatar metrica metriques -> files
|| fold sumar linies 0 metriques -> total_l
|| fold sumar bytes 0 metriques -> total_b ||.
"{files}\nTotal: {total_l} línies, {total_b} bytes"
fn :: main => ()
main =
echo (generar informe ["README.md", "AGENTS.md"])
Atura't un segon a mirar aquestes ~40 línies de codi:
- No hi ha ni un sol
null, ni una excepció descontrolada, ni unMutex. comptar saltses compila a unloopnatiu amb 0 bytes de pila gràcies al TCO automàtic.||map analitzar fitxer rutesprocessa els fitxers en paral·lel en tots els nuclis de la teva CPU, i el bloc||calcula simultàniament les files formatades, el total de línies i el total de bytes.- La memòria temporal s'allibera determinísticament mitjançant
RMM1/RMM2. - I quan executes
ridgunc build rg_wc.rg -o rg-wc, obtens un binari natiu autònom de ~350 KB a punt per copiar a/usr/local/bin.
8.7. Full de Ruta Final: Les 10 Regles d'Or del Programador Ridgun
- Escriu sempre primer el contracte (
=>) i després les equacions (=): pensar en els tipus primer fa que el compilador treballi per a tu. - Deixa que
stable formatmaqueti les teves taules: escriu amb un espai i deixa que l'eina alineï columnes,=>,=i->. - Recorda la regla universal de l'
=: darrere de qualsevol=pots obrir un bloc seqüencial, un arbre de guardes|o un graf paral·lel||. - Fes servir
|.i||.només quan tanquis un bloc imbricat per tornar a un nivell superior: mantén els arbres de decisió profunds compactes i sense ambigüitat. - Immutable per defecte (
x = ...), mutable explícit (mut x = ...): fes servirmutúnicament quan un acumulador local ho demani de debò. - Fes servir operadors booleans clars en minúscules (
and,or,xor,not) i tanca sempre les teves guardes amb| otherwise =. - Modela el teu domini amb ADTs (
type): fes servir registres ambwith camp: valori tipus suma (Option,Result) en lloc denull. - Paral·lelitza declarant dependències amb
|| expr -> vari||map/||filter: deixa que el compilador verifiqui que no hi ha cicles i reparteixi la feina entre els nuclis. - Posa dimensions simbòliques als teus Tensors (
Tensor[N, K]) i subíndexs a les teves contraccions (a_ik @ b_kj). - Confia en
RMM(RMM1/RMM2), afita entrades externes ambwith regioni verifica sempre ambstable checkistable test.
Bon viatge programant en Ridgun!