Skip to content
Ridgun
English

Ridgun

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

08-tensores-regiones-y-sistemas.md210 lines · 14.4 KBRaw
1# Capítulo 08: Tensores, Memoria Compilada RMM (`RMM1`/`RMM2`), Regiones y Sistemas
2
3Hasta aquí hemos visto el lado más amable, elegante y matemático de Ridgun: funciones puras, tipos algebraicos, *pattern matching*, árboles de decisión con `|` / `|.` y grafos paralelos `||` con `->` que aprovechan todos los núcleos de tu procesador sin pedirte ni un triste `Mutex`.
4
5Llegados a este punto, el programador veterano de sistemas suele levantar la mano al fondo del aula con media sonrisa y preguntar:
6
7> *"Todo esto es precioso para calcular facturas o compilar ASTs, pero ¿qué pasa cuando necesito multiplicar matrices de millones de flotantes a velocidad de vértigo? ¿Qué pasa con la memoria? ¿Hay un recolector de basura (Garbage Collector) escondido debajo de la alfombra esperando el peor momento para congelar mi programa durante 200 milisegundos? ¿Y cómo hablo con el sistema operativo Unix de verdad?"*
8
9Excelentes preguntas. Vamos a levantar el capó de Ridgun para ver cómo se las gasta cuando toca bajar al barro de los sistemas, el cálculo numérico y la memoria determinista.
10
11---
12
13## 8.1. El dilema de la memoria: ni Garbage Collector ni peleas con el Borrow Checker (`RMM1` y `RMM2`)
14
15En el mundo de los lenguajes modernos suele haber dos bandos irreconciliables:
16
171. **El bando del Garbage Collector (Java, Go, Python, JS, Haskell, OCaml):** tú pides memoria alegremente y, de vez en cuando, un camión de la basura invisible detiene el tráfico de tu programa para rastrear qué objetos siguen vivos y cuáles no. Es comodísimo... hasta que estás haciendo una herramienta de sistema ultrarrápida o un servidor con latencias estrictas y el camión decide aparcar en doble fila.
182. **El bando del control manual / Borrow Checker (C, C++, Rust):** no hay recolector de basura, pero a cambio tienes que gestionar punteros a mano (jugándote un `Segmentation fault` o un *use-after-free*) o pasarte media tarde negociando con el *borrow checker* por qué `'a` no vive tanto como `'b`.
19
20Ridgun resuelve este dilema de raíz con **Ridgun Memory Management (`RMM`)**, un sistema determinista de dos políticas complementarias (**`RMM1`** y **`RMM2`**) combinado con **regiones léxicas (`with region`)**:
21
22### Política `RMM1` — Gestión de Memoria Compilada (CMM Lineal y Condicional)
23Cuando el compilador conoce estáticamente el flujo de las variables locales dentro de una función:
24- **CMM Lineal**: `ridgunc` calcula en tiempo de compilación cuál es la **última línea** en la que se lee cada variable local (`a`, `b`, ...) e inserta justo después su liberación determinista (`rg_cmm_drop`), o transfiere su propiedad sin copias (`rg_cmm_move`) cuando la variable se devuelve como resultado.
25- **CMM Condicional (`ptr(a) != ptr(c)`)**: ¿y si una función como `elegir cond a b` devuelve `a` o `b` según una condición en tiempo de ejecución? `ridgunc` construye la tabla de ancestros y emite una guarda de identidad de punteros de **1 ciclo de CPU**: si `b` deja de usarse mientras `c` sigue viva, comprueba `if ptr(b) != ptr(c) { free(b) }`. La rama no elegida se libera al instante y la elegida sobrevive intacta.
26
27### Política `RMM2` — Conteo por Asignación y Cascada Estructural
28¿Y qué ocurre cuando metes estructuras dentro de otras estructuras (`xs = [a, b]`), reasignas variables mutables (`mut x = ...`) o encadenas llamadas complejas? Ahí entra **`RMM2`**, que gobierna todo el ciclo de vida con cuatro reglas matemáticas sin fisuras:
29
301. **Los argumentos de entrada de una función no se tocan (`+0` al entrar, `-0` al salir)**: pasar una lista o un registro a una función jamás incrementa ni decrementa contadores en vano.
312. **Toda asignación a una variable local interna o celda de contenedor suma `+1`**:
32 - `a = [10, 20]` $\rightarrow$ el bloque `M1` nace con contador `1`.
33 - `xs = [a, b]` $\rightarrow$ la lista `xs` nace con `1`, y como guarda referencias a `a` y `b`, ambos suben a `2`.
34 - Si reasignas una variable mutable (`mut x = ...` y luego `x = nuevo`), `RMM2` aplica primero **`+1`** al valor nuevo antes de aplicar **`-1`** al antiguo.
353. **Al salir de la función, las variables locales que NO se devuelven reciben `-1`**: si el contador de una estructura llega a `0`, se libera su memoria en el acto y **propaga `-1` en cascada, uno por uno, a cada uno de sus hijos directos**.
364. **El valor devuelto por la función se exime en tránsito**: recibe su `-1` de variable local pero no se destruye al cruzar el `return`, de modo que cuando quien llamó hace `c = mi funcion ()`, ese `+1` de la asignación lo deja exactamente en `1`.
37
38```ridgun
39elegir Bool [Int] [Int] => [Int]
40elegir cond a b =
41 | cond = a
42 | otherwise = b
43
44ejemplo rmm2 Bool => Int
45ejemplo rmm2 cond =
46 a = [10, 20, 30] -- M1 (rc = 1)
47 b = [40, 50, 60] -- M2 (rc = 1)
48 c = elegir cond a b -- `c` recibe +1; el no elegido muere aquí mismo (rc -> 0)
49 (contar a) + (contar c) -- Al terminar, `a` y `c` reciben -1 y todo queda en 0 bloques vivos
50```
51
52---
53
54## 8.2. Regiones Léxicas con Cuota (`with region`): Cortafuegos de memoria en $O(1)$
55
56Además de `RMM1` y `RMM2`, cuando estás procesando un paquete de red externo, un archivo gigante o un lote de datos no confiables y quieres poner un **techo estricto de memoria RAM**, envuelves el cálculo en un bloque **`with region`**:
57
58```ridgun
59procesar trama String => Result
60procesar trama crudo =
61 with region 64_KB do
62 limpias = map limpiar linea (dividir lineas crudo)
63 contar validos limpias
64```
65
661. **Cuota garantizada en hardware/runtime**: si dentro del bloque se intenta reservar ni un solo byte por encima de `64_KB` (`65.536` bytes), el runtime aborta limpiamente el bloque, libera toda la arena temporal en tiempo constante $O(1)$ y devuelve `Err OutOfMemory` sin tumbar el proceso.
672. **Liberación instantánea al salir**: en cuanto termina el bloque `with region`, toda la memoria temporal de la arena desaparece de golpe sin dejar fragmentación.
68
69---
70
71## 8.3. Tensores Nativos, Contracción de Einstein (`@`) yDimensiones Simbólicas
72
73Si alguna vez has programado en Python con NumPy o PyTorch, seguro que has sufrido el clásico error en el minuto 41 de ejecución:
74
75```text
76RuntimeError: mat1 and mat2 shapes cannot be multiplied (128x64 and 32x128)
77```
78
79En Ridgun, los **`Tensor`** son ciudadanos nativos de primera clase y sus dimensiones forman parte de la firma de tipos:
80
81```ridgun
82-- Si entra (N × K) y (K × M), ¡por narices sale (N × M)!
83multiplicar matrices Tensor[N, K] Tensor[K, M] => Tensor[N, M]
84multiplicar matrices a b = a_ik @ b_kj
85```
86
87Fíjate en dos detalles espectaculares:
881. **Verificación estática de dimensiones (`Tensor[N, K]`)**: el eje interno `K` de la primera matriz tiene que coincidir con el primer eje `K` de la segunda. Si intentas pasar dimensiones incompatibles, `ridgunc check` te detiene antes de ejecutar nada.
892. **Notación de subíndices de Einstein (`a_ik @ b_kj`) y Hadamard (`a_ij * b_j`)**:
90 - Con **`@`**, los subíndices repetidos en todos los operandos (`k`) se contraen (se suman) y los únicos (`i`, `j`) sobreviven en el tensor resultado.
91 - Con **`+`, `-`, `*`, `/`**, los subíndices alinean dimensiones y hacen *broadcasting* automático (`a_ij * b_j` multiplica cada fila `i` de `a` por el vector `b_j` sin necesidad de escribir `unsqueeze` ni `reshape`).
923. **Tensores tipados (`Tensor[T * d1 * d2]`), subíndices matemáticos `t_(i, j)`, vistas `t_(i,)` y mapeo *in-place* (`in`)**:
93 Puedes declarar tensores multidimensionales con tipo escalar explícito (`U64`, `Int`, `Float`, `Byte`) inicializados a cero en una sola línea, indexarlos con subíndices matemáticos `t_(i, j)`, extraer vistas de fila `t_(i,)` o columna `t_(,j)` sin copiar memoria, desestructurar retornos múltiples directamente sobre celdas del tensor sin alojar tuplas en el heap, y aplicar funciones *in-place* sobre un eje con `map ... in` o `||map ... in`:
94 ```ridgun
95 fn :: blamka p16 Tensor[U64 * 16] => ()
96 blamka p16 v =
97 v_(0), v_(4), v_(8), v_(12) = blamka g v_(0) v_(4) v_(8) v_(12)
98
99 fn :: comprimir bloque Tensor[U64 * 8 * 16] Tensor[U64 * 8 * 16] => Tensor[U64 * 8 * 16]
100 comprimir bloque x y =
101 r_ij = x_ij xor y_ij
102 z_ij = r_ij
103 ||map blamka p16 in z_(i,)
104 ||map blamka p16 in z_(,j)
105 z_ij xor r_ij
106 ```
107
108---
109
110## 8.4. Acceso a Hardware (`hardware ::`) y Primitivas POSIX de Sistemas
111
112El propio compilador `ridgunc`, el gestor `stable` y utilidades de sistemas Unix están escritos en Ridgun. Para ello, el lenguaje incluye bloques de registros mapeados en memoria (`hardware ::`) y primitivas POSIX directas:
113
114```ridgun
115hardware :: UART0 @ 0x1000_0000
116 tx_data : Int @ 0x00
117 status : Int @ 0x04
118```
119
120Y para trabajar con el sistema operativo:
121
122| Primitiva | Firma | Qué hace |
123|---|---|---|
124| `cli args` | `=> [String]` | Argumentos de línea de comandos (`argv`). |
125| `read file` / `write file` | `String => String` / `String String => ()` | Lectura y escritura íntegra de archivos en disco. |
126| `read bytes` / `write bytes` | `String => [Int]` / `String [Int] => ()` | Lectura y escritura binaria exacta byte a byte (`0..255`). |
127| `list dir` / `file lstat` | `String => [String]` / `String => ...` | Listado de directorios e inspección de metadatos POSIX (`lstat`). |
128| `sha256 file` / `sha256 str` | `String => String` | Hash criptográfico SHA-256 nativo en hexadecimal (64 caracteres). |
129| `sha256 raw` / `sha256 bytes` | `String => String` | Digest SHA-256 binario crudo de 32 bytes (con soporte completo para bytes `0x00` intermedios y cero conversiones hex). |
130| `sys exec` / `shell exec` | `... => Int` | Ejecución de procesos del sistema operativo. |
131| `sys getcwd` / `sys chdir` | `=> String` / `String => Int` | Consultar o cambiar el directorio de trabajo. |
132| `sys getenv` / `sys setenv` | `String => String` / `String String => Int` | Variables de entorno del proceso. |
133| `sys getuid` / `sys getgid` | `=> Int` | UID y GID reales del proceso (`doas`, herramientas de seguridad). |
134| `sys open mode` / `sys close` / `sys poll` | `String String => Int` / `Int => Int` / `Int Int Int => Int` | Descriptores PAL multiplataforma y `poll` acotado por `with timeout`. |
135| `sys exit` | `Int => ()` | Terminar el proceso con código de salida. |
136
137---
138
139## 8.5. Caso de Estudio Real: Construyendo `rg-wc`, una utilidad Unix paralela
140
141Vamos a unir todo lo aprendido desde el Capítulo 01 hasta aquí en un programa real de sistemas: una versión paralela de la herramienta Unix `wc` (contador de líneas y bytes para múltiples archivos) escrita en Ridgun puro:
142
143```ridgun
144-- 1. Modelamos las métricas de un archivo con un registro ADT (Capítulo 06)
145type Metricas = archivo: String, lineas: Int, bytes: Int
146
147-- 2. Contamos saltos de línea (código ASCII 10) con recursión de cola TCO (Capítulo 04)
148fn :: contar saltos String Int Int Int => Int
149 contar saltos texto pos fin acum =
150 | pos >= fin = acum
151 | (str char at texto pos) == 10 = contar saltos texto (pos + 1) fin (acum + 1)
152 | otherwise = contar saltos texto (pos + 1) fin acum
153
154fn :: numero de lineas String => Int
155 numero de lineas texto = contar saltos texto 0 (str len texto) 0
156
157-- 3. Analizamos un archivo y devolvemos su estructura Metricas
158fn :: analizar archivo String => Metricas
159 analizar archivo ruta =
160 contenido = read file ruta
161 || numero de lineas contenido -> n_lineas
162 || str len contenido -> n_bytes ||.
163 Metricas ruta n_lineas n_bytes
164
165-- 4. Formateamos cada fila y sumamos totales con `fold` y `||` (Capítulo 07)
166fn :: formatear metrica Metricas => String
167 formatear metrica m = "{lineas m}\t{bytes m}\t{archivo m}"
168
169fn :: sumar lineas Int Metricas => Int
170 sumar lineas acc m = acc + (lineas m)
171
172fn :: sumar bytes Int Metricas => Int
173 sumar bytes acc m = acc + (bytes m)
174
175fn :: generar informe [String] => String
176 generar informe rutas =
177 metricas = ||map analizar archivo rutas
178 || map formatear metrica metricas -> filas
179 || fold sumar lineas 0 metricas -> total_l
180 || fold sumar bytes 0 metricas -> total_b ||.
181 "{filas}\nTotal: {total_l} líneas, {total_b} bytes"
182
183fn :: main => ()
184 main =
185 echo (generar informe ["README.md", "AGENTS.md"])
186```
187
188Párate un segundo a mirar esas ~40 líneas de código:
189- No hay ni un solo `null`, ni una excepción descontrolada, ni un `Mutex`.
190- `contar saltos` se compila a un `loop` nativo con 0 bytes de pila gracias al TCO automático.
191- `||map analizar archivo rutas` procesa los archivos en paralelo en todos los núcleos de tu CPU, y el bloque `||` calcula simultáneamente las filas formateadas, el total de líneas y el total de bytes.
192- La memoria temporal se libera determinísticamente mediante **`RMM1`/`RMM2`**.
193- Y cuando ejecutas `ridgunc build rg_wc.rg -o rg-wc`, obtienes un binario nativo autónomo de ~350 KB listo para copiar a `/usr/local/bin`.
194
195---
196
197## 8.6. Hoja de Ruta Final: Las 10 Reglas de Oro del Programador Ridgun
198
1991. **Escribe siempre primero el contrato (`=>`) y después las ecuaciones (`=`):** pensar en los tipos primero hace que el compilador trabaje para ti.
2002. **Deja que `stable format` maquete tus tablas:** escribe con un espacio y deja que la herramienta alinee columnas, `=>`, `=` y `->`.
2013. **Recuerda la regla universal del `=`:** detrás de cualquier `=` puedes abrir un bloque secuencial, un árbol de guardas `|` o un grafo paralelo `||`.
2024. **Usa `|.` y `||.` solo cuando cierres un bloque anidado para volver a un nivel superior:** mantén los árboles de decisión profundos compactos y sin ambigüedad.
2035. **Inmutable por defecto (`x = ...`), mutable explícito (`mut x = ...`):** usa `mut` únicamente cuando un acumulador local lo pida de verdad.
2046. **Usa operadores booleanos claros en minúsculas (`and`, `or`, `xor`, `not`) y cierra siempre tus guardas con `| otherwise =`.**
2057. **Modela tu dominio con ADTs (`type`):** usa registros con `with campo: valor` y tipos suma (`Option`, `Result`) en lugar de `null`.
2068. **Paraleliza declarando dependencias con `|| expr -> var` y `||map` / `||filter`:** deja que el compilador verifique que no hay ciclos y reparta el trabajo entre los núcleos.
2079. **Ponle dimensiones simbólicas a tus Tensores (`Tensor[N, K]`) y subíndices a tus contracciones (`a_ik @ b_kj`).**
20810. **Confía en `RMM` (`RMM1`/`RMM2`), acota entradas externas con `with region` y verifica siempre con `stable check` y `stable test`.**
209
210¡Feliz viaje programando en Ridgun!