Ridgun
The language itself: a compiler that compiles itself, the stable project tool, the specification and the book
| 1 | <!DOCTYPE html> |
| 2 | <html lang="es" data-theme="dark"> |
| 3 | <head> |
| 4 | <meta charset="UTF-8" /> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| 6 | <title>El Libro de Ridgun — Guía Completa del Lenguaje</title> |
| 7 | <style> |
| 8 | :root, [data-theme="dark"] { |
| 9 | --bg: #0f141c; |
| 10 | --bg-sidebar: #141b26; |
| 11 | --bg-card: #18212f; |
| 12 | --bg-code: #0b0f16; |
| 13 | --border: #263246; |
| 14 | --text: #e6edf3; |
| 15 | --text-muted: #94a3b8; |
| 16 | --accent: #38bdf8; |
| 17 | --accent-soft: rgba(56, 189, 248, 0.14); |
| 18 | --kw: #ff7b72; |
| 19 | --ty: #79c0ff; |
| 20 | --fn: #d2a8ff; |
| 21 | --str: #a5d6ff; |
| 22 | --num: #f2cc60; |
| 23 | --com: #6e7681; |
| 24 | --op: #ffa657; |
| 25 | --shadow: 0 8px 24px rgba(0, 0, 0, 0.35); |
| 26 | } |
| 27 | [data-theme="light"] { |
| 28 | --bg: #f8fafc; |
| 29 | --bg-sidebar: #f1f5f9; |
| 30 | --bg-card: #ffffff; |
| 31 | --bg-code: #0f172a; |
| 32 | --border: #cbd5e1; |
| 33 | --text: #0f172a; |
| 34 | --text-muted: #475569; |
| 35 | --accent: #0284c7; |
| 36 | --accent-soft: rgba(2, 132, 199, 0.12); |
| 37 | --kw: #ff7b72; |
| 38 | --ty: #79c0ff; |
| 39 | --fn: #d2a8ff; |
| 40 | --str: #a5d6ff; |
| 41 | --num: #f2cc60; |
| 42 | --com: #94a3b8; |
| 43 | --op: #ffa657; |
| 44 | --shadow: 0 4px 16px rgba(15, 23, 42, 0.08); |
| 45 | } |
| 46 | * { box-sizing: border-box; } |
| 47 | body { |
| 48 | margin: 0; |
| 49 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif; |
| 50 | background: var(--bg); |
| 51 | color: var(--text); |
| 52 | line-height: 1.72; |
| 53 | display: flex; |
| 54 | min-height: 100vh; |
| 55 | } |
| 56 | /* Sidebar */ |
| 57 | aside.sidebar { |
| 58 | width: 310px; |
| 59 | background: var(--bg-sidebar); |
| 60 | border-right: 1px solid var(--border); |
| 61 | display: flex; |
| 62 | flex-direction: column; |
| 63 | position: fixed; |
| 64 | top: 0; |
| 65 | bottom: 0; |
| 66 | left: 0; |
| 67 | z-index: 20; |
| 68 | overflow-y: auto; |
| 69 | } |
| 70 | .brand { |
| 71 | padding: 1.35rem 1.25rem 1rem; |
| 72 | border-bottom: 1px solid var(--border); |
| 73 | } |
| 74 | .brand-top { |
| 75 | display: flex; |
| 76 | justify-content: space-between; |
| 77 | align-items: center; |
| 78 | margin-bottom: 0.45rem; |
| 79 | } |
| 80 | .brand-badge { |
| 81 | display: inline-block; |
| 82 | font-size: 0.72rem; |
| 83 | font-weight: 700; |
| 84 | text-transform: uppercase; |
| 85 | letter-spacing: 0.08em; |
| 86 | color: var(--accent); |
| 87 | background: var(--accent-soft); |
| 88 | padding: 0.2rem 0.55rem; |
| 89 | border-radius: 999px; |
| 90 | } |
| 91 | .lang-bar { |
| 92 | display: flex; |
| 93 | gap: 0.25rem; |
| 94 | } |
| 95 | .lang-link { |
| 96 | font-size: 0.72rem; |
| 97 | font-weight: 600; |
| 98 | text-decoration: none; |
| 99 | color: var(--text-muted); |
| 100 | padding: 0.16rem 0.42rem; |
| 101 | border-radius: 5px; |
| 102 | border: 1px solid var(--border); |
| 103 | } |
| 104 | .lang-link:hover, .lang-link.active { |
| 105 | color: var(--accent); |
| 106 | border-color: var(--accent); |
| 107 | background: var(--accent-soft); |
| 108 | } |
| 109 | .brand h1 { |
| 110 | margin: 0; |
| 111 | font-size: 1.25rem; |
| 112 | font-weight: 800; |
| 113 | letter-spacing: -0.02em; |
| 114 | } |
| 115 | .brand p { |
| 116 | margin: 0.25rem 0 0; |
| 117 | font-size: 0.83rem; |
| 118 | color: var(--text-muted); |
| 119 | } |
| 120 | .search-box { |
| 121 | padding: 0.85rem 1.1rem; |
| 122 | border-bottom: 1px solid var(--border); |
| 123 | } |
| 124 | .search-box input { |
| 125 | width: 100%; |
| 126 | padding: 0.55rem 0.75rem; |
| 127 | border-radius: 8px; |
| 128 | border: 1px solid var(--border); |
| 129 | background: var(--bg); |
| 130 | color: var(--text); |
| 131 | font-size: 0.88rem; |
| 132 | outline: none; |
| 133 | } |
| 134 | .search-box input:focus { |
| 135 | border-color: var(--accent); |
| 136 | } |
| 137 | .nav-list { |
| 138 | list-style: none; |
| 139 | padding: 0.75rem 0.65rem 1.5rem; |
| 140 | margin: 0; |
| 141 | flex: 1; |
| 142 | } |
| 143 | .nav-item { |
| 144 | margin-bottom: 0.25rem; |
| 145 | } |
| 146 | .nav-btn { |
| 147 | width: 100%; |
| 148 | text-align: left; |
| 149 | background: transparent; |
| 150 | border: none; |
| 151 | color: var(--text-muted); |
| 152 | padding: 0.62rem 0.75rem; |
| 153 | border-radius: 8px; |
| 154 | font-size: 0.9rem; |
| 155 | cursor: pointer; |
| 156 | display: flex; |
| 157 | align-items: flex-start; |
| 158 | gap: 0.55rem; |
| 159 | transition: all 0.15s ease; |
| 160 | line-height: 1.38; |
| 161 | } |
| 162 | .nav-btn:hover { |
| 163 | background: var(--accent-soft); |
| 164 | color: var(--text); |
| 165 | } |
| 166 | .nav-btn.active { |
| 167 | background: var(--accent-soft); |
| 168 | color: var(--accent); |
| 169 | font-weight: 600; |
| 170 | } |
| 171 | .nav-num { |
| 172 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; |
| 173 | font-size: 0.78rem; |
| 174 | opacity: 0.8; |
| 175 | min-width: 1.5rem; |
| 176 | padding-top: 0.08rem; |
| 177 | } |
| 178 | .sidebar-footer { |
| 179 | padding: 0.85rem 1.1rem; |
| 180 | border-top: 1px solid var(--border); |
| 181 | display: flex; |
| 182 | justify-content: space-between; |
| 183 | align-items: center; |
| 184 | font-size: 0.8rem; |
| 185 | color: var(--text-muted); |
| 186 | } |
| 187 | .theme-btn { |
| 188 | background: var(--bg-card); |
| 189 | color: var(--text); |
| 190 | border: 1px solid var(--border); |
| 191 | border-radius: 6px; |
| 192 | padding: 0.35rem 0.65rem; |
| 193 | font-size: 0.8rem; |
| 194 | cursor: pointer; |
| 195 | } |
| 196 | /* Main content */ |
| 197 | main.reader { |
| 198 | margin-left: 310px; |
| 199 | flex: 1; |
| 200 | display: flex; |
| 201 | justify-content: center; |
| 202 | padding: 2.5rem 2rem 5rem; |
| 203 | } |
| 204 | article.chapter { |
| 205 | max-width: 820px; |
| 206 | width: 100%; |
| 207 | } |
| 208 | article.chapter h1 { |
| 209 | font-size: 2.15rem; |
| 210 | line-height: 1.22; |
| 211 | margin-top: 0; |
| 212 | margin-bottom: 1.25rem; |
| 213 | letter-spacing: -0.025em; |
| 214 | border-bottom: 1px solid var(--border); |
| 215 | padding-bottom: 0.85rem; |
| 216 | } |
| 217 | article.chapter h2 { |
| 218 | font-size: 1.48rem; |
| 219 | margin-top: 2.6rem; |
| 220 | margin-bottom: 0.85rem; |
| 221 | color: var(--accent); |
| 222 | letter-spacing: -0.015em; |
| 223 | } |
| 224 | article.chapter h3 { |
| 225 | font-size: 1.16rem; |
| 226 | margin-top: 1.8rem; |
| 227 | margin-bottom: 0.6rem; |
| 228 | } |
| 229 | article.chapter p { |
| 230 | margin: 0 0 1.1rem; |
| 231 | } |
| 232 | article.chapter ul, article.chapter ol { |
| 233 | margin: 0 0 1.25rem; |
| 234 | padding-left: 1.45rem; |
| 235 | } |
| 236 | article.chapter li { |
| 237 | margin-bottom: 0.4rem; |
| 238 | } |
| 239 | article.chapter hr { |
| 240 | border: none; |
| 241 | border-top: 1px solid var(--border); |
| 242 | margin: 2.2rem 0; |
| 243 | } |
| 244 | article.chapter blockquote { |
| 245 | margin: 1.35rem 0; |
| 246 | padding: 0.9rem 1.15rem; |
| 247 | border-left: 4px solid var(--accent); |
| 248 | background: var(--accent-soft); |
| 249 | border-radius: 0 8px 8px 0; |
| 250 | } |
| 251 | article.chapter blockquote p:last-child { |
| 252 | margin-bottom: 0; |
| 253 | } |
| 254 | /* Code blocks */ |
| 255 | .code-wrapper { |
| 256 | position: relative; |
| 257 | margin: 1.25rem 0 1.5rem; |
| 258 | border-radius: 10px; |
| 259 | overflow: hidden; |
| 260 | border: 1px solid var(--border); |
| 261 | background: var(--bg-code); |
| 262 | box-shadow: var(--shadow); |
| 263 | } |
| 264 | .code-header { |
| 265 | display: flex; |
| 266 | justify-content: space-between; |
| 267 | align-items: center; |
| 268 | padding: 0.42rem 0.9rem; |
| 269 | background: rgba(255, 255, 255, 0.04); |
| 270 | border-bottom: 1px solid rgba(255, 255, 255, 0.07); |
| 271 | font-size: 0.75rem; |
| 272 | color: #94a3b8; |
| 273 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; |
| 274 | } |
| 275 | .copy-btn { |
| 276 | background: rgba(255, 255, 255, 0.08); |
| 277 | color: #e2e8f0; |
| 278 | border: 1px solid rgba(255, 255, 255, 0.14); |
| 279 | border-radius: 5px; |
| 280 | padding: 0.18rem 0.55rem; |
| 281 | font-size: 0.73rem; |
| 282 | cursor: pointer; |
| 283 | } |
| 284 | .copy-btn:hover { |
| 285 | background: rgba(56, 189, 248, 0.25); |
| 286 | } |
| 287 | pre { |
| 288 | margin: 0; |
| 289 | padding: 1rem 1.15rem; |
| 290 | overflow-x: auto; |
| 291 | font-family: "JetBrains Mono", "Fira Code", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; |
| 292 | font-size: 0.89rem; |
| 293 | line-height: 1.58; |
| 294 | color: #e6edf3; |
| 295 | } |
| 296 | p code, li code, td code { |
| 297 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; |
| 298 | font-size: 0.86em; |
| 299 | background: var(--accent-soft); |
| 300 | color: var(--accent); |
| 301 | padding: 0.15rem 0.38rem; |
| 302 | border-radius: 5px; |
| 303 | } |
| 304 | /* Tables */ |
| 305 | .table-wrap { |
| 306 | overflow-x: auto; |
| 307 | margin: 1.35rem 0; |
| 308 | border: 1px solid var(--border); |
| 309 | border-radius: 10px; |
| 310 | } |
| 311 | table { |
| 312 | width: 100%; |
| 313 | border-collapse: collapse; |
| 314 | font-size: 0.92rem; |
| 315 | } |
| 316 | th, td { |
| 317 | padding: 0.7rem 0.95rem; |
| 318 | border-bottom: 1px solid var(--border); |
| 319 | text-align: left; |
| 320 | } |
| 321 | th { |
| 322 | background: var(--bg-sidebar); |
| 323 | font-weight: 700; |
| 324 | } |
| 325 | tr:last-child td { |
| 326 | border-bottom: none; |
| 327 | } |
| 328 | /* Syntax tokens */ |
| 329 | .rg-com { color: var(--com); font-style: italic; } |
| 330 | .rg-kw { color: var(--kw); font-weight: 600; } |
| 331 | .rg-ty { color: var(--ty); font-weight: 600; } |
| 332 | .rg-str { color: var(--str); } |
| 333 | .rg-num { color: var(--num); } |
| 334 | .rg-op { color: var(--op); font-weight: 600; } |
| 335 | /* Chapter pager */ |
| 336 | .pager { |
| 337 | margin-top: 3.5rem; |
| 338 | padding-top: 1.5rem; |
| 339 | border-top: 1px solid var(--border); |
| 340 | display: flex; |
| 341 | justify-content: space-between; |
| 342 | gap: 1rem; |
| 343 | } |
| 344 | .pager-btn { |
| 345 | flex: 1; |
| 346 | padding: 0.9rem 1.1rem; |
| 347 | border-radius: 10px; |
| 348 | border: 1px solid var(--border); |
| 349 | background: var(--bg-card); |
| 350 | color: var(--text); |
| 351 | cursor: pointer; |
| 352 | text-align: left; |
| 353 | transition: border-color 0.15s ease; |
| 354 | } |
| 355 | .pager-btn.next { text-align: right; } |
| 356 | .pager-btn:hover { border-color: var(--accent); } |
| 357 | .pager-label { |
| 358 | display: block; |
| 359 | font-size: 0.76rem; |
| 360 | color: var(--text-muted); |
| 361 | margin-bottom: 0.2rem; |
| 362 | } |
| 363 | .pager-title { |
| 364 | font-weight: 600; |
| 365 | color: var(--accent); |
| 366 | font-size: 0.95rem; |
| 367 | } |
| 368 | .search-results { |
| 369 | margin-bottom: 2rem; |
| 370 | padding: 1rem 1.2rem; |
| 371 | border-radius: 10px; |
| 372 | border: 1px solid var(--accent); |
| 373 | background: var(--bg-card); |
| 374 | } |
| 375 | .search-hit { |
| 376 | padding: 0.6rem 0; |
| 377 | border-bottom: 1px solid var(--border); |
| 378 | cursor: pointer; |
| 379 | } |
| 380 | .search-hit:last-child { border-bottom: none; } |
| 381 | .search-hit-title { color: var(--accent); font-weight: 600; } |
| 382 | .search-hit-snippet { font-size: 0.86rem; color: var(--text-muted); } |
| 383 | @media (max-width: 860px) { |
| 384 | aside.sidebar { position: static; width: 100%; border-right: none; border-bottom: 1px solid var(--border); } |
| 385 | body { flex-direction: column; } |
| 386 | main.reader { margin-left: 0; padding: 1.5rem 1.1rem 3rem; } |
| 387 | } |
| 388 | </style> |
| 389 | </head> |
| 390 | <body> |
| 391 | <aside class="sidebar"> |
| 392 | <div class="brand"> |
| 393 | <div class="brand-top"> |
| 394 | <span class="brand-badge">Ridgun v0.9.32</span> |
| 395 | <div class="lang-bar"><a class="lang-link active" href="index.html">ES</a><a class="lang-link" href="../ca/index.html">CA</a><a class="lang-link" href="../gl/index.html">GL</a><a class="lang-link" href="../eu/index.html">EU</a><a class="lang-link" href="../en/index.html">EN</a><a class="lang-link" href="../pt/index.html">PT</a><a class="lang-link" href="../sv/index.html">SV</a><a class="lang-link" href="../it/index.html">IT</a></div> |
| 396 | </div> |
| 397 | <h1>El Libro de Ridgun</h1> |
| 398 | <p>De cero a experto en programación funcional tabular y de sistemas</p> |
| 399 | </div> |
| 400 | <div class="search-box"> |
| 401 | <input type="search" id="searchInput" placeholder="Buscar en el libro (ej. ||, Tensor, RMM2, TCO...)" /> |
| 402 | </div> |
| 403 | <ul class="nav-list" id="navList"></ul> |
| 404 | <div class="sidebar-footer"> |
| 405 | <span>9 capítulos (00–08) · Español</span> |
| 406 | <button class="theme-btn" id="themeBtn" type="button">☀️ / 🌙 Tema</button> |
| 407 | </div> |
| 408 | </aside> |
| 409 | |
| 410 | <main class="reader"> |
| 411 | <article class="chapter" id="chapterContent"></article> |
| 412 | </main> |
| 413 | |
| 414 | <script> |
| 415 | const CHAPTERS = [{"num":"00","slug":"00-por-que-necesito-ridgun","file":"00-por-que-necesito-ridgun.md","title":"Capítulo 00: ¿Por qué necesito Ridgun? ¿Ah, sí? No lo sabía... ¡Cuéntame más!","short_title":"¿Por qué necesito Ridgun? ¿Ah, sí? No lo sabía... ¡Cuéntame más!","markdown":"# Capítulo 00: ¿Por qué necesito Ridgun? ¿Ah, sí? No lo sabía... ¡Cuéntame más!\n\nSeamos sinceros: cuando alguien te dice *\"hemos creado un nuevo lenguaje de programación\"*, tu primera reacción natural —y muy sana— es arquear una ceja y pensar:\n\n> *\"¿Otro más? Pero si ya tengo Python para ir rápido, Rust o C para cuando necesito velocidad, y Bash para pegar cosas en la terminal. ¿Para qué demonios necesito yo aprender Ridgun?\"*\n\nEs una pregunta excelente. Y la respuesta corta es: **porque te has acostumbrado a sufrir tres dolores diarios pensando que eran inevitables**.\n\nVamos a ponerles nombre.\n\n---\n\n## Dolor n.º 1: \"El síndrome de los dos mundos\" (Prototipar en uno, reescribir en otro)\n\nSeguro que has vivido esta película:\n\n1. Empiezas una herramienta o un motor de cálculo en **Python**, **JavaScript** o **Ruby** porque quieres pensar en el problema, no en punteros ni en el compilador.\n2. El código queda corto y bonito... pero cuando le pasas un archivo de 500 MB o intentas usar los 16 núcleos de tu máquina, se arrastra. Y si quieres distribuir tu programa a un servidor limpio, tienes que empaquetar medio gigabyte de entorno virtual, intérprete y librerías dinámicas.\n3. Resignado, decides reescribirlo en **C**, **C++** o **Rust**. Ahora vuela y ocupa unos pocos cientos de kilobytes, pero tu código de 40 líneas se ha convertido en 300 líneas llenas de `unwrap()`, `&mut`, `'a`, `Arc<Mutex<...>>`, llaves `{}` y puntos y coma `;`.\n\n**¿Qué cambia con Ridgun?**\nEn Ridgun escribes código **más corto y limpio que en Python**, pero cuando ejecutas `ridgunc build`, obtienes **un único binario nativo ELF de ~350 KB** sin intérprete, sin máquina virtual externa y sin recolector de basura. No hay fase de *\"luego lo reescribimos en el lenguaje rápido\"*: el código limpio donde piensas el problema **ya es** el binario nativo de producción.\n\n---\n\n## Dolor n.º 2: \"Leer código ajeno (o el tuyo de hace 6 meses) es jugar a ser detective\"\n\nMira cómo escribimos lógica condicional en casi todos los lenguajes mainstream:\n\n```text\nif (estado == ACTIVO && (rol == ADMIN || (rol == EDITOR && tienePermiso))) {\n if (cuota > 0) {\n return procesar(usuario);\n } else {\n return denegar(\"Sin cuota\");\n }\n} else {\n return denegar(\"No autorizado\");\n}\n```\n\nNuestros ojos no evolucionaron para emparejar llaves anidadas ni para desentrañar árboles de `if / else` escalonados. Cuando los ingenieros, matemáticos o físicos quieren entender reglas complejas sin equivocarse, no escriben párrafos de `if / else`: **dibujan una tabla**.\n\nEn Ridgun, **el código es esa tabla**:\n\n```ridgun\nautorizar String String Bool Int => String\nautorizar \"activo\" \"admin\" _ cuota =\n | cuota > 0 = \"Procesando petición\"\n | otherwise = \"Denegado: sin cuota\"\nautorizar \"activo\" \"editor\" True cuota =\n | cuota > 0 = \"Procesando petición\"\n | otherwise = \"Denegado: sin cuota\"\nautorizar _ _ _ _ = \"Denegado: no autorizado\"\n```\n\nMira las columnas:\n- Columna 1: el estado (`\"activo\"` o `_`).\n- Columna 2: el rol (`\"admin\"`, `\"editor\"` o `_`).\n- Columna 3: si tiene permiso (`True` o `_`).\n- Columna 4: la `cuota`.\n\nNo tienes que ejecutar mentalmente el programa línea por línea para saber qué pasa cuando entra un `\"editor\"` con permiso `False`: tus ojos bajan verticalmente por la tabla y lo ven en medio segundo.\n\n---\n\n## Dolor n.º 3: \"Mi ordenador tiene 16 núcleos, pero mi programa solo usa 1 (porque me da pánico tocar hilos)\"\n\nHoy hasta el teléfono más modesto tiene 8 núcleos, y cualquier servidor o portátil de trabajo tiene entre 12 y 32. Sin embargo, el 95% de los programas cotidianos siguen ejecutándose en un solo hilo secuencial.\n\n¿Por qué? Porque en los lenguajes imperativos, paralelizar significa abrir la caja de Pandora:\n- ¿Y si dos hilos modifican el mismo diccionario a la vez? (*Data race*).\n- ¿Y si bloqueo el `Mutex` A y luego el B en un hilo, y el B y luego el A en otro? (*Deadlock*).\n- ¿Y si contamino toda mi base de código con `async` y `await` de arriba abajo?\n\nEn Ridgun, **toda función es pura y todo dato es inmutable por defecto**. Eso tiene una consecuencia mágica: **dos expresiones que no dependen una de la otra jamás pueden interferir entre sí**.\n\nPor eso, en Ridgun no tienes que gestionar hilos para aprovechar tu CPU. Solo declaras **qué depende de qué** con barras paralelas `||` y flechas `->`:\n\n```ridgun\ninforme mensual Datos => Informe\ninforme mensual d = combinar metricas_red metricas_disco metricas_cpu\n || analizar red d -> metricas_red\n || analizar disco d -> metricas_disco\n || analizar cpu d -> metricas_cpu\n```\n\nTú solo has escrito tres líneas limpias indicando de dónde sale cada dato. El compilador ve que las tres ramas son independientes y sabe que puede lanzarlas en paralelo sin que exista ni la más mínima posibilidad de un *data race* o un *deadlock*.\n\n¿Y si necesitas agentes persistentes con buzón de mensajes y estado aislado (por ejemplo, para mantener miles de sesiones de clientes, trabajadores en segundo plano o conexiones de red)? En Ridgun no tienes que definir clases `Actor` ni usar `Mutex`: **cualquier función normal se convierte en un agente autónomo en una sola línea con `agent`**:\n\n```ridgun\ncontador = agent (sumar visita) 0\nsend 1 to contador\nsend 5 to contador\ntotal = receive from contador\nstop contador\n```\n\nAdemás, el compilador realiza una **elección inteligente en tiempo de compilación**: analiza estáticamente la profundidad y el coste de cada función en `||`, `||map`, `||filter` y `agent` para ejecutar las funciones ligeras como pasos *green-thread* de sobrecarga cero (sin pagar el coste de calentar hilos del sistema operativo) y despachar automáticamente los cálculos pesados o recursivos a hilos reales del kernel en múltiples núcleos (`SYS_clone`).\n\n---\n\n## Dolor n.º 4: \"El recolector de basura decide pausar el mundo justo en el peor momento\"\n\nLos lenguajes funcionales clásicos (Haskell, OCaml, Erlang, Clojure) son maravillosos para razonar sobre el código, pero casi todos dependen de un **Recolector de Basura (*Garbage Collector*)** en tiempo de ejecución. Eso los descarta cuando necesitas:\n- Latencias predecibles al microsegundo.\n- Controlar exactamente cuánta memoria RAM puede consumir una operación antes de abortarla limpiamente.\n- Escribir utilidades del sistema operativo (`ls`, `find`, `sh`, `doas`, `make`) o tocar registros de hardware (`MMIO`).\n\nRidgun te da **toda la elegancia del mundo funcional puro, pero con Gestión de Memoria de Ridgun (`RMM`: `RMM1` estática y `RMM2` por conteo de asignaciones en cascada) más regiones léxicas con cuota exacta (`with region`)**:\n\n```ridgun\nparsear paquete String => Result\nparsear paquete crudo =\n with region 16_KB do\n decodificar trama crudo\n```\n\nCada estructura temporal es liberada de forma determinista por el compilador (`RMM1` / `RMM2`) en el instante en que deja de usarse, y si un atacante te envía un paquete malicioso diseñado para explotar la memoria, al superar el byte 16.384 la región se corta al instante, libera su arena en $O(1)$ y te devuelve `Err OutOfMemory`. Sin caídas del proceso, sin pausas de GC y sin fugas de memoria.\n\n---\n\n## En resumen: ¿Cuándo brilla Ridgun?\n\nRidgun está hecho para ti si alguna vez has querido:\n\n1. **Escribir herramientas de sistemas, compiladores, analizadores o utilidades CLI** que arranquen en microsegundos, pesen unos pocos cientos de KB y no puedan romperse por punteros nulos ni efectos colaterales ocultos.\n2. **Construir sistemas concurrentes y servidores basados en el Modelo Nativo de Agentes (`agent`, `send ... to`, `receive from`, `stop`)**, donde cualquier función pura se convierte en un agente con o sin estado aislado y el compilador elige inteligentemente entre ejecución *green-thread* ultraligera e hilos nativos multinúcleo.\n3. **Programar algoritmos, pipelines de datos o álgebra de tensores** donde el compilador verifique por ti hasta las dimensiones de las matrices (`Tensor[M, K] * Tensor[K, N] => Tensor[M, N]`) antes de ejecutar nada.\n4. **Disfrutar leyendo tu propio código**: que cada archivo `.rg` parezca una página de un libro bien maquetado gracias al formateador tabular automático (`stable format`).\n\n¿Te pica la curiosidad? Pasa al **Capítulo 01** y escribamos nuestro primer programa en menos de dos minutos.\n"}, {"num":"01","slug":"01-bienvenido-a-ridgun","file":"01-bienvenido-a-ridgun.md","title":"Capítulo 01: Tu primer contacto con Ridgun (Sin cascos, sin llaves y sin puntos y coma)","short_title":"Tu primer contacto con Ridgun (Sin cascos, sin llaves y sin puntos y coma)","markdown":"# Capítulo 01: Tu primer contacto con Ridgun (Sin cascos, sin llaves y sin puntos y coma)\n\nVale, ya te he convencido en el Capítulo 00 de que no estamos locos por crear Ridgun. Ahora seguramente estés pensando:\n\n> *\"Muy bonito el discurso de las tablas y los 16 núcleos, pero enséñame código de verdad. ¿Cuánto tarda uno en escribir algo que funcione sin pelearse con el compilador?\"*\n\nVenga, ¡al turrón!\n\n---\n\n## 1.1. \"¡Hola, mundo!\", pero con chicha\n\nEn la mayoría de los libros de programación te enseñan un `print(\"Hola mundo\")` soso y pasan de página. Nosotros vamos a escribir directamente una función con lógica real para que veas cómo respira Ridgun desde el minuto uno.\n\nCrea un archivo llamado `hola.rg` y escribe esto:\n\n```ridgun\nsaludo String => String\nsaludo \"\" = \"¡Hola, viajero misterioso! (No me has dicho tu nombre)\"\nsaludo \"Root\" = \"¡Hombre, Root! Cuidado con lo que borras hoy.\"\nsaludo nombre = \"¡Hola, {nombre}! Bienvenido a Ridgun.\"\n\nmain => ()\nmain =\n echo (saludo \"Ada\")\n echo (saludo \"Root\")\n echo (saludo \"\")\n```\n\nPara ejecutarlo ahora mismo:\n\n```bash\nridgunc run hola.rg\n```\n\nY verás en tu terminal:\n\n```text\n¡Hola, Ada! Bienvenido a Ridgun.\n¡Hombre, Root! Cuidado con lo que borras hoy.\n¡Hola, viajero misterioso! (No me has dicho tu nombre)\n```\n\nPara un momento y mira el bloque de `saludo` arriba. ¿Notas lo que **NO** hay?\n- **No hay `function`, ni `def`, ni `pub fn`** (si quieres poner `fn ::` delante de la firma puedes hacerlo, pero es opcional).\n- **No hay paréntesis `(String nombre)` ni llaves `{ ... }`** ensuciando la pantalla.\n- **No hay `if (nombre == \"\") ... else if (nombre == \"Root\") ... else ...`**.\n- **No hay `return`**.\n\n---\n\n## 1.2. Cómo lee tu cerebro un archivo `.rg`\n\nVamos a destripar esas cuatro líneas de `saludo`, porque aquí ya está el 50% de la filosofía de Ridgun:\n\n```ridgun\nsaludo String => String\nsaludo \"\" = \"¡Hola, viajero misterioso! (No me has dicho tu nombre)\"\nsaludo \"Root\" = \"¡Hombre, Root! Cuidado con lo que borras hoy.\"\nsaludo nombre = \"¡Hola, {nombre}! Bienvenido a Ridgun.\"\n```\n\n1. **La primera línea es el contrato (`saludo String => String`)**:\n Dice: *\"Me llamo `saludo`, recibo un `String` y te devuelvo (`=>`) otro `String`\"*. Punto. Si alguien intenta llamarme pasándome el número `42`, el compilador le dará un toque en el hombro antes siquiera de ejecutar el programa.\n2. **Las líneas de abajo son los casos (de arriba abajo)**:\n - ¿Me has pasado la cadena vacía `\"\"`? Devuelvo la primera frase.\n - ¿Me has pasado exactamente `\"Root\"`? Devuelvo la segunda frase.\n - ¿Me has pasado cualquier otra cosa? La guardo en la variable `nombre` y la meto dentro del texto usando `{nombre}`.\n3. **¿Y qué es `main => ()`?**:\n Como `main` no recibe argumentos, ponemos `=>` directamente después del nombre. Y devuelve `()` (el tipo vacío o *Unit*, para los amigos), porque su único trabajo es sacar cosas por pantalla con `echo`.\n\n---\n\n## 1.3. \"Espera... ¿tengo que alinear esos espacios a mano como un monje copista?\"\n\n¡Ni de broma! Si tuvieras que darle a la barra espaciadora contando columnas a ojo, abandonarías Ridgun a los diez minutos (y con razón).\n\nTú escribes el código deprisa, con un solo espacio entre cada palabra:\n\n```ridgun\nsaludo String => String\nsaludo \"\" = \"¡Hola, viajero misterioso!\"\nsaludo nombre = \"¡Hola, {nombre}!\"\n```\n\nY cuando ejecutas **`stable format`** (o pulsas `TAB` / guardas el archivo en Emacs con `ridgun-mode`), el formateador canónico coloca cada parámetro debajo de su tipo y alinea todos los `=` con precisión milimétrica. Tú escribes rápido; la máquina maqueta la tabla.\n\n---\n\n## 1.4. De script rápido a binario de combate en un comando\n\nCuando haces `ridgunc run hola.rg`, estás compilando y lanzando el programa al vuelo. Pero imagina que ya has terminado tu herramienta y quieres llevártela a un servidor de producción o compartirla con un compañero que no tiene instalado Ridgun.\n\nEjecutas:\n\n```bash\nridgunc build hola.rg -o hola\n```\n\nY ya tienes `./hola`: un **ejecutable nativo ELF de Linux x86_64**, limpio, sin código muerto (*Tree-Shaking*), con optimización de enlace completo (*LTO*) y sin depender de ningún intérprete externo.\n\n---\n\n## 1.5. La chuleta honesta: ¿De dónde viene cada idea de Ridgun?\n\nRidgun no pretende reinventar la rueda cuadrada; toma prestado sin complejos lo mejor de cuatro tradiciones:\n\n| De quién | Qué toma Ridgun | Qué deja fuera (por tu salud mental) |\n|---|---|---|\n| **Haskell / ML** | Funciones puras, *pattern matching* por casos, tipos algebraicos (`Option`, `Result`) y polimorfismo (`A`..`Z`). | Las mónadas abstractas, los 40 `LANGUAGE pragmas` y el recolector de basura. |\n| **J / APL** | La obsesión por la forma tabular del código, tensores nativos y máscaras de secuencias (`s(_ + _)`). | Los símbolos jeroglíficos imposibles de escribir en un teclado normal. |\n| **Rust / C** | Binarios ELF nativos pequeños, cero GC, control de memoria (`with region`) y llamadas al sistema POSIX. | La pelea constante con el *borrow checker*, los *lifetimes* `'a` y la verbosidad sintáctica. |\n| **Erlang / Go** | Agentes aislados (`agent`, `send ... to`, `receive from`) y concurrencia por canales (`new bounded channel`). | Los punteros `nil` y las mutaciones compartidas por descuido. |\n\n---\n\n## 1.6. Y lo más importante: ¿Qué hace Ridgun que NO tiene nadie más?\n\nReconocer a los clásicos es de buena educación, pero si Ridgun fuera solo un \"recopilatorio de grandes éxitos\" de otros lenguajes, no estarías leyendo este libro.\n\nHay **ocho cosas** que hemos diseñado e integrado en Ridgun que no vas a encontrar juntas —y muchas de ellas, ni siquiera por separado— en ningún otro lenguaje actual:\n\n### 1. Bloques paralelos `||` con `->` que son grafos paralelos reales, no simple azúcar sintáctico\nEn otros lenguajes funcionales un bloque de variables locales solo ordena expresiones que luego se evalúan en un único hilo.\nEn Ridgun, **un bloque `||` con flechas `->` es un Grafo Acíclico Dirigido (DAG) de ejecución paralela** sin siquiera necesitar escribir palabras clave extra como `where`:\n```ridgun\ninforme anual Int => Int\ninforme anual id = ingresos - gastos\n || calcular ventas id -> ingresos\n || calcular costes id -> gastos\n```\nTú dibujas las flechas de flujo (`->`). El compilador analiza quién depende de quién, **verifica estáticamente que no hayas creado ningún ciclo infinito**, y lanza las ramas independientes en paralelo sin que escribas ni un solo `async`, `await`, `Future` ni `Mutex`.\n\n### 2. Modelo Nativo de Agentes (`agent`) de cero fricción con elección inteligente en compilación\nEn otros lenguajes, programar con actores o agentes te obliga a crear clases especiales, implementar *traits*, gestionar *locks* o infectar tus funciones con `async`.\nEn Ridgun, **cualquier función pura normal (`fn ::` u `op ::`) se convierte directamente en un agente autónomo —con o sin estado aislado— usando `agent`**:\n```ridgun\natender http (Sesion, String) => (Sesion, String)\n...\nservidor = agent atender http sesion_inicial\nsend \"GET /perfil\" to servidor\nrespuesta = receive from servidor\nstop servidor\n```\nMejor aún: el compilador **analiza en tiempo de compilación la profundidad y el coste computacional de la función** tanto en `agent` como en `||` y `||map`. Si la función es ligera, la ejecuta como un *green thread* instantáneo sin pagar el coste de calentar hilos del sistema operativo; si es un cálculo pesado o recursivo profundo, la despacha automáticamente en paralelo sobre hilos reales del kernel (`SYS_clone`) repartidos por los núcleos de tu CPU.\n\n### 3. El código como tabla bidimensional donde la firma es la cabecera de las columnas\nEn cualquier otro lenguaje, la firma de tipos va por libre en una línea y el cuerpo de la función va debajo como un párrafo de texto normal.\nEn Ridgun, **la firma de tipos es literalmente la cabecera de una tabla**, y cada caso debajo alinea cada patrón justo en la vertical de su tipo y cada `=` bajo el `=>`:\n```ridgun\ndividir Int Int => Option\ndividir _ 0 = None\ndividir a b = Some (a / b)\n```\nNadie más trata la firma y las ecuaciones como una única matriz visual verificada por el compilador y maquetada automáticamente por `stable format`.\n\n### 4. Operadores frase (*Mixfix* N-arios) tipados y alineados (`op ::`)\nEn la mayoría de los lenguajes, o te aguantas con `+`, `-`, `*` o como mucho defines un operador binario críptico como `<|>` que nadie sabe qué significa.\nEn Ridgun puedes crear **operadores que son frases legibles con tantos huecos tipados como quieras** (incluyendo palabras contextuales como `type` o `with`):\n```ridgun\nop :: Int limitado entre Int y Int => Int\n x limitado entre min y max =\n | x < min = min\n | x > max = max\n | otherwise = x\n```\nY luego en tu código escribes `velocidad limitado entre 0 y 120`. Se lee como castellano (o inglés), pero está 100% tipado y compilado a código nativo.\n\n### 5. Máscaras visuales sobre secuencias (`s(_ + _)` y `-> (+ _ _ +) ->`)\n¿Alguna vez has tenido que escribir una ensalada de índices `filter(\\(i, x) -> i % 2 == 0)` o abrir una tupla con variables mudas solo para quedarte con dos posiciones concretas?\nEn Ridgun le pasas directamente una **máscara visual**:\n- `datos(_ + _)`: descarta el primero (`_`), se queda el segundo (`+`), descarta el tercero (`_`).\n- `parsear paquete \"v0.9.9\" -> (+ _ _ +) -> combinar codigo -> resumen`: filtra una tupla de 4 elementos en plena tubería y pasa el 1.º y el 4.º directamente como los 2 argumentos de la siguiente función.\nCero aritmética de índices, cero errores *off-by-one*.\n\n### 6. Polimorfismo de \"cero ruido\" (`A`..`Z`) gobernado por tu dominio\nEn Rust, TypeScript o Java, cuando una función es genérica tienes que llenar la línea de paréntesis angulares: `fn intercambiar<A, B>(par: (A, B)) -> (B, A)`.\nEn Ridgun simplemente usas una letra mayúscula suelta:\n```ridgun\nintercambiar (A, B) => (B, A)\nintercambiar (x, y) = (y, x)\n```\n*\"¿Y si en mi programa quiero tener un tipo real que se llame `type A`?\"* — Ningún problema: en Ridgun **tus declaraciones `type` mandan**. Si no existe `type A`, `A` es genérico; en el instante en que defines `type A = ...`, pasa a ser tu tipo concreto en todo el módulo.\n\n### 7. Cero archivos de configuración extraños: `build.rg` es Ridgun puro\nOlvídate de aprender TOML para Cargo, JSON para npm, XML para Maven o la sintaxis arcana de `Makefile` con tabuladores invisibles. En Ridgun, la receta que construye tu proyecto (`build.rg`) **es un archivo `.rg` más**, con las mismas funciones puras y el mismo *pattern matching* que el resto de tu código.\n\n### 8. La \"Santísima Trinidad\" que parecía imposible: Nivel Haskell + Memoria RMM sin GC ni Lifetimes + Binarios de ~350 KB\nHasta hoy tenías que elegir:\n- Si querías expresividad funcional pura (Haskell, OCaml, Elixir), pagabas con un recolector de basura y un runtime pesado.\n- Si querías binarios diminutos de sistema sin recolector de basura (C, Rust, Zig), pagabas gestionando punteros o peleándote con el *borrow checker* y escribiendo código imperativo lleno de `let mut`.\n\nRidgun rompe ese dilema combinando **Ridgun Memory Management (`RMM`: `RMM1` estática y `RMM2` por conteo de asignaciones en cascada)**, **regiones léxicas (`with region`)** que liberan arenas enteras en $O(1)$, **recursión de cola transformada en bucles nativos (`loop`)**, y **poda de grafo de llamadas (*Tree-Shaking*)** desde `main`. El resultado: escribes código declarativo de altísimo nivel y obtienes un ejecutable ELF autónomo de **~350 KB** capaz de competir de tú a tú con herramientas escritas en C.\n\nAhora sí: con las cartas sobre la mesa, vamos al **Capítulo 02** para poner a trabajar a **`ridgunc`** y **`stable`**.\n\n"}, {"num":"02","slug":"02-primeros-pasos-y-herramientas","file":"02-primeros-pasos-y-herramientas.md","title":"Capítulo 02: Tus dos mejores amigos: `ridgunc` y `stable` (Cero archivos YAML, prometido)","short_title":"Tus dos mejores amigos: `ridgunc` y `stable` (Cero archivos YAML, prometido)","markdown":"# Capítulo 02: Tus dos mejores amigos: `ridgunc` y `stable` (Cero archivos YAML, prometido)\n\nHay lenguajes cuyo compilador es maravilloso, pero cuyo sistema de proyectos parece diseñado por tu peor enemigo: que si un `Makefile` de 400 líneas con tabuladores invisibles, que si un `pom.xml` infinito, que si un `webpack.config.js` que deja de funcionar si lo miras fijo...\n\nEn Ridgun nos hicimos una promesa muy simple desde el primer día:\n\n> *\"Sólo habrá dos ejecutables en todo el ecosistema (`ridgunc` y `stable`), ambos estarán escritos en el propio Ridgun, y el archivo de configuración del proyecto (`build.rg`) será código Ridgun normal y corriente. Ni JSON, ni YAML, ni TOML, ni inventos raros.\"*\n\nVamos a ver cómo se usan.\n\n---\n\n## 2.1. `ridgunc`: El francotirador para archivos sueltos\n\nCuando tienes un único archivo `.rg` entre manos y quieres probar una idea rápida, analizar sus tipos o compilar un ejecutable, hablas directamente con **`ridgunc`**.\n\nSólo tiene 4 comandos que de verdad necesitas recordar:\n\n```bash\n# 1. \"¿Compila esto o he metido la pata con algún tipo?\"\nridgunc check experimento.rg\n\n# 2. \"¡Ejecútalo ya!\" (Compila y corre de un tirón)\nridgunc run experimento.rg\n\n# 3. \"Hazme un binario nativo ELF64 listo para llevar\"\nridgunc build experimento.rg -o mi-programa\n\n# 4. \"Compílame una librería reutilizable (.rglib y .so ELF64 sin necesitar main)\"\nridgunc build --lib utilidades.rg -o target/utilidades.rglib\nridgunc build --so utilidades.rg -o target/libutilidades.so\n\n# 5. \"Sácame el artefacto intermedio de bytecode (.rgc)\"\nridgunc compile experimento.rg -o experimento.rgc\n```\n\n### El superpoder oculto de `ridgunc check`\nCuando lanzas `ridgunc check`, no solo te dice *\"0 errores\"*. También te chiva qué estrategia ha elegido el analizador estático para cada una de tus funciones:\n- **`InlineSequential`**: *\"Esta función es pequeñita y directa; la voy a ejecutar en línea sin meter sobrecoste de planificación\"*.\n- **`LazyStealableTask`**: *\"Aquí hay recursión o un grafo paralelo `||`; la preparo para que otros núcleos de CPU puedan robar trabajo si hace falta\"*.\n\n### El bisturí de `ridgunc build` (Por qué tus binarios no pesan 20 MB)\nCuando ejecutas `ridgunc build`, el compilador no se limita a escupir todo el código de las librerías que hayas importado:\n1. Empieza en tu función `main` y recorre el grafo de llamadas para ver **qué funciones usas de verdad**. Si importaste una librería enorme de 80 funciones pero solo llamas a 2, las otras 78 se descartan antes siquiera de tocar el ensamblador (*Tree-Shaking*).\n2. Busca todas las funciones recursivas de cola y las convierte en bucles `loop` nativos que consumen **0 bytes de pila**.\n3. Pasa el enlace final con optimización global (*fat LTO*) y limpia la basura de depuración DWARF para dejarte un binario compacto y afilado (~350 KB).\n\n---\n\n## 2.2. `stable`: Cuando tu proyecto crece y quieres orden\n\nUn archivo suelto está genial para un script, pero en cuanto tienes varios módulos, librerías compartidas y tests, necesitas un director de orquesta. Ese es **`stable`**.\n\nLa estructura de carpetas de un proyecto Ridgun no tiene misterio:\n\n```text\nmi-super-herramienta/\n├── build.rg # El manifiesto del proyecto (¡es código Ridgun!)\n├── src/\n│ ├── main/\n│ │ └── ridgun/\n│ │ ├── lib/ # Tus módulos reutilizables (`import utilidades`)\n│ │ └── app/\n│ │ └── main.rg # Punto de entrada (`main => ()`)\n│ └── test/\n│ └── ridgun/ # Tus baterías de tests (`test *.rg`)\n│ └── test todo.rg\n└── target/ # Donde aparecen los binarios compilados\n```\n\n---\n\n## 2.3. `build.rg`: Un archivo de configuración que nunca te mentirá\n\n¿Por qué nos negamos a usar YAML o TOML para configurar un proyecto Ridgun?\nPorque en YAML te equivocas en dos espacios de indentación y no te enteras hasta que falla el despliegue. En cambio, como **`build.rg` es un archivo Ridgun**, tiene tipos estáticos, pasa por `stable check` y se alinea solo con `stable format`:\n\n```ridgun\nfn :: project name => String\n project name = \"mi-super-herramienta\"\n\nfn :: project version => String\n project version = \"0.1.0\"\n\n-- \"exe\" (ejecutable), \"lib\" (librería .rglib + .so) o \"both\" (ambos)\nfn :: project kind => String\n project kind = \"both\"\n\n-- Opcional: modo estricto (prohíbe '_' en nombres fn/op y exige retornos '=> Int Int')\nfn :: strict mode => Bool\n strict mode = True\n\nfn :: main entry => String\n main entry = \"src/main/ridgun/app/main.rg\"\n\nfn :: source dir => String\n source dir = \"src/main/ridgun\"\n\nfn :: test dir => String\n test dir = \"src/test/ridgun\"\n\nfn :: target dir => String\n target dir = \"target\"\n\nfn :: dependencies => [String]\n dependencies = []\n\nfn :: plugins => [String]\n plugins = []\n\nfn :: task verificar todo => String\n task verificar todo = \"./bin/stable check && ./bin/stable test\"\n```\n\nSi te equivocas al escribir un campo o un tipo, el propio compilador te avisa. Y fíjate en que todas las funciones de configuración usan **espacios naturales** (`project name`, `project kind`, `source dir`, `task verificar todo`) en lugar de guiones bajos.\n\n---\n\n## 2.4. Tu rutina diaria con `stable` (Los 4 fantásticos)\n\nCuando estás trabajando dentro de tu proyecto, tu día a día se reduce a cuatro comandos:\n\n### 1. `stable format` — El fin de las discusiones de estilo en el equipo\n```bash\nstable format\n```\n¿Que tu compañero pone tres espacios antes del `=>` y tú pones uno? Da igual. Ejecutas `stable format` y en menos de un segundo deja **todos** los archivos `.rg` del repositorio alineados como una tabla suiza.\n\n### 2. `stable check` — El radar instantáneo\n```bash\nstable check\n```\nRecorre todos los módulos de `src/main/ridgun/`, verifica tipos, constructores, aridades y dimensiones de tensores, y te da el veredicto módulo por módulo.\n\n### 3. `stable test` — Dormir tranquilo por las noches\n```bash\nstable test\n```\nBusca todas las suites `src/test/ridgun/test *.rg`, las ejecuta y comprueba cada `assert equals` y `assert true`.\n\n```ridgun\n-- Ejemplo de src/test/ridgun/test mates.rg\nimport test\n\ndoble Int => Int\ndoble x = x * 2\n\nmain => ()\nmain =\n assert equals (doble 21) 42\n assert true ((doble 5) > 0)\n```\n\n### 4. `stable built` (y `stable clean`) — A producción\n```bash\nstable built\n```\nCompila todos los objetivos nativos en `target/`. Y si algún día quieres borrar `target/` y empezar limpio: `stable clean`.\n\n---\n\n## 2.5. Crear y usar Librerías Reutilizables (`.rglib` y `.so` ELF64) y Plugins de `stable`\n\nUna librería no tiene función `main => ()`: ofrece tipos (`type`, `expand type`), funciones (`fn ::`) y operadores (`op ::`) para que otros proyectos los importen.\n\n### 1. Crear un proyecto de librería con `stable`\n```bash\nstable new --lib geometria\ncd geometria\nstable built lib\n```\nAl compilar en modo librería (`project kind = \"lib\"` o `stable built lib`), el compilador desactiva la poda desde `main` para conservar **todas** las funciones y tipos exportados, y genera dos artefactos en `target/`:\n- **`target/geometria.rglib`**: paquete autocontenido de librería Ridgun (con todas sus fuentes y dependencias internas resueltas), listo para importar desde cualquier otro proyecto con `import geometria` o `import \"target/geometria.rglib\"`.\n- **`target/libgeometria.so`**: biblioteca dinámica nativa ELF64 (`ET_DYN`, para `x86_64` o `arm64-linux`).\n\n```ridgun\n-- src/main/ridgun/lib/geometria.rg (¡Sin función main!)\ntype Punto = x: Int, y: Int\n\nfn :: desplazar punto Punto Int Int => Punto\n desplazar punto p dx dy = Punto ((x p) + dx) ((y p) + dy)\n\nfn :: coordenadas punto Punto => Int Int\n coordenadas punto p = (x p, y p)\n```\n\n### 2. ¿Cómo importo un módulo o librería desde otro archivo? (`import`)\n\nSin rutas relativas infernales tipo `../../../lib/cosas.rg`. Si tienes un módulo `src/main/ridgun/lib/geometria.rg` o un paquete `geometria.rglib` (en `target/`, `lib/` o `.stable/deps/` tras ejecutar `stable deps`), en cualquier otro archivo escribes arriba del todo:\n\n```ridgun\nimport geometria\n```\n\nY listo: todas las funciones, operadores y tipos de `geometria` están disponibles como si estuvieran en tu propio archivo. Si varios módulos hacen `import geometria`, el cargador de Ridgun recuerda qué archivos canónicos ha leído ya y **solo lo incluye una vez**.\n\n### 3. Extender `stable` con tareas (`fn :: task`) y plugins (`plugins/*.rg`)\n¿Quieres añadir comandos propios para tu framework web, tus migraciones de base de datos o tu flujo de despliegue?\n- **En `build.rg`**: declara cualquier función `fn :: task <palabras...> => String` (por ejemplo, `fn :: task levantar servidor => String`) y ejecútala con `stable levantar servidor`.\n- **En `plugins/*.rg` o `.stable/plugins/*.rg`**: coloca módulos `.rg` que definan tareas `fn :: task ...` o su propio `fn :: main => ()`, o extiende el tipo abierto `expand type StableCommand` de `stable`. Ejecuta `stable tasks` y `stable plugins` para inspeccionarlos en cualquier momento.\n\n### 4. El modo estricto opcional (`--strict` y `strict mode = True`)\nPor defecto, ni `ridgunc` ni los proyectos nuevos creados con `stable new` o `stable init` activan el modo estricto. Sin embargo, cuando quieras garantizar en tu propio proyecto que todo el equipo siga al 100% la convención canónica de espacios naturales de Ridgun, puedes pasar `--strict` a `ridgunc` (`ridgunc check --strict`, `ridgunc build --strict`) o declarar `strict mode = True` en tu `build.rg`. El modo estricto convierte en **error de compilación**:\n1. Cualquier guion bajo `_` dentro del nombre de una declaración `fn ::` u `op ::` (debiendo escribirse `fn :: calcular total`, nunca `fn :: calcular_total`; las variables locales y campos de registros sí pueden usar `_`).\n2. Cualquier tipo de salida múltiple escrito con paréntesis o comas tras `=>` (debiendo escribirse `=> Int Int` o `=> RToken [RToken]`, nunca `=> (Int, Int)`).\n\n---\n\n## 2.6. El Ecosistema de Librerías Estándar y Criptográficas (`ridgun-libs`)\n\nRidgun cuenta con una suite oficial de **32 librerías puras** (`ridgun-libs`), compiladas como paquetes `.rglib` y bibliotecas dinámicas `.so` ELF64 listas para importar con `import <nombre>`:\n\n| Familia | Librerías (`import ...`) | Qué aportan a tus proyectos |\n|---|---|---|\n| **1. Texto, Formatos y Plantillas** | `str`, `fmt`, `regex`, `diff`, `template`, `json`, `csv`, `ini`, `codec` | Manipulación avanzada de cadenas, formateo tabular, expresiones regulares Thompson NFA, diferencias Myers (`diff`), plantillas sin lógica, parsers/serializadores `json`/`csv`/`ini` y codificación hexadecimal/Base64 (`codec`). |\n| **2. Colecciones, Matemáticas y Álgebra Tensorial** | `list`, `btree`, `math`, `tensor`, `linalg` | Operaciones funcionales sobre listas, árboles B ordenados en memoria (`btree`), aritmética avanzada (`math`), vistas y constructores de tensores tipados (`tensor`) y descomposición/álgebra lineal (`linalg`). |\n| **3. Sistema Operativo, Archivos y CLI** | `posix`, `io`, `path`, `glob`, `cli`, `time`, `log`, `uuid`, `test` | Descriptores y señales POSIX (`posix`), E/S con `with timeout` (`io`), rutas y patrones (`path`, `glob`), argumentos CLI tipados (`cli`), relojes monotónicos (`time`), logging estructurado (`log`), RFC 4122 `uuid` y aserciones (`test`). |\n| **4. Criptografía y Seguridad** | `crypto`, `argon2id`, `jwt` | SHA-256 binario directo (`sha256 raw` / `sha256 bytes` en 32 bytes crudos), HMAC-SHA-256, PBKDF2-HMAC-SHA-256, HKDF, ChaCha20-Poly1305 AEAD (`crypto`), hashing de contraseñas RFC 9106 `argon2id` sobre `Tensor[U64 * 8 * 16]` con permutación BlaMka sin alojar tuplas en el heap (`argon2id`), y tokens firmados (`jwt`). |\n| **5. Redes, Protocolos y Agentes** | `socket`, `http`, `ws`, `agent`, `pool`, `net_agent` | Sockets TCP/UDP POSIX (`socket`), cliente/servidor HTTP/1.1 (`http`), WebSockets RFC 6455 (`ws`), supervisores de agentes (`agent`), pools de trabajadores (`pool`) y servidores reactivos orientados a agentes (`net_agent`). |\n\nAhora que tienes la caja de herramientas y las librerías controladas, pasemos al **Capítulo 03** para dominar las funciones con espacios naturales, las firmas de tipos y los bloques.\n"}, {"num":"03","slug":"03-funciones-tipos-y-alineacion","file":"03-funciones-tipos-y-alineacion.md","title":"Capítulo 03: Funciones, firmas de tipos y el arte de escribir código como una tabla","short_title":"Funciones, firmas de tipos y el arte de escribir código como una tabla","markdown":"# Capítulo 03: Funciones, firmas de tipos y el arte de escribir código como una tabla\n\nHay una pregunta que todo programador se hace la primera vez que ve código funcional:\n\n> *\"Si aquí no hay clases, ni objetos, ni variables que cambian de valor... ¿entonces dónde meto mi lógica?\"*\n\nEn **funciones**. Pero no en esas funciones de 200 líneas llenas de banderas booleanas que todos hemos sufrido alguna vez, sino en funciones pequeñas, directas y tan fáciles de leer que parecen una tabla en una pizarra.\n\nVamos a ver cómo se construyen.\n\n---\n\n## 3.1. Dos formas de presentar una función (Tú eliges el traje)\n\nEn Ridgun toda función empieza con su **firma de tipos** (qué entra y qué sale tras `=>`) y debajo van sus **cláusulas de implementación** (con `=`).\n\nPuedes escribir el encabezado de dos maneras:\n\n### Opción A: Directa al grano (sin prefijo)\n```ridgun\naplicar descuento Int Int => Int\naplicar descuento precio porcentaje = precio - (precio * porcentaje / 100)\n```\n\n### Opción B: Con etiqueta explícita (`fn ::`)\n```ridgun\nfn :: aplicar descuento Int Int => Int\n aplicar descuento precio porcentaje = precio - (precio * porcentaje / 100)\n```\n\n*\"Oye, ¿y por qué usamos espacios en `aplicar descuento` en lugar de `aplicar_descuento`?\"*\nPorque en Ridgun todos los tipos empiezan por mayúscula (`Int`, `String`, `A`..`Z`), `[` o `(`, mientras que las palabras del nombre de la función van en minúscula. Así puedes escribir nombres de varias palabras con espacios naturales (`aplicar descuento`, `coste envio`, `str len`, `read file`) sin ninguna ambigüedad para el compilador.\n\n*\"¿Y por qué existen las dos opciones (con `fn ::` y sin él)?\"*\nPorque para gustos, los colores:\n- Hay quien prefiere que las funciones empiecen pegadas al margen izquierdo sin escribir `fn ::` cada vez (al fin y al cabo, la flecha `=>` ya grita a los cuatro vientos que esa línea es una firma de tipos).\n- Y hay quien prefiere tener una columna izquierda dedicada a las etiquetas (`fn ::`, `op ::`, `type`, `import`) para escanear el archivo visualmente.\n\nEl compilador y `stable format` entienden ambas a la perfección.\n\n---\n\n## 3.2. La magia de las columnas: por qué tus ojos descansan\n\nMira esta función que calcula el coste de un envío según el país y si es urgente:\n\n```ridgun\nfn :: coste envio String Bool Int => Int\n coste envio \"ES\" False _ = 5\n coste envio \"ES\" True _ = 12\n coste envio _ False peso = 10 + (peso * 2)\n coste envio _ True peso = 25 + (peso * 4)\n```\n\nHaz la prueba: tapa la mitad derecha de la pantalla y mira solo las tres columnas de entrada:\n- Primera columna (`String`): `\"ES\"`, `\"ES\"`, `_`, `_`.\n- Segunda columna (`Bool`): `False`, `True`, `False`, `True`.\n- Tercera columna (`Int`): `_`, `_`, `peso`, `peso`.\n\n¿Ves algún agujero lógico? Ninguno: están las 4 combinaciones posibles delante de tus narices, alineadas verticalmente debajo de `String`, `Bool` e `Int`. En un bloque de `if / else` tradicional tendrías que leer 18 líneas para estar seguro de que no te has dejado ningún caso suelto.\n\n---\n\n## 3.3. Los tipos básicos y un regalo para no contar ceros (`_KB`, `_MB`, `_GB`)\n\nLos tipos escalares de Ridgun son directos y sin sorpresas:\n\n| Tipo | Qué es en realidad | Ejemplos |\n|---|---|---|\n| `Int` | Entero de 64 bits con signo (`i64`) | `0`, `42`, `-100`, `0xFF00`, `1_000_000` |\n| `U64` | Entero de 64 bits sin signo (`u64`) para criptografía y sistemas | `0xdeadbeefcafe0001`, `a xor b`, `x rotr 24` |\n| `Byte` | Octeto sin signo (`0..255`) en tensores y buffers binarios | `Tensor[Byte * 64]` |\n| `Real` / `Float` | Decimal de doble precisión (`f64`) | `3.14159`, `0.001`, `-2.5` |\n| `String` | Cadena de texto o buffer binario inmutable con longitud explícita | `\"Hola\"`, `'ruta\\cruda'`, `sha256 raw \"abc\"` |\n| `Char` | Código de carácter | Devuelto por `str char at` |\n| `Bool` | Booleano de toda la vida | `True`, `False` |\n| `()` | Vacío (*Unit*, cuando solo imprimes o haces I/O) | `()` |\n\n### Operadores a nivel de bit de 1 ciclo y flexibilidad léxica (`fn`, `op`, `where`)\nPara criptografía, protocolos binarios y sistemas de alto rendimiento, `Int` y `U64` cuentan con operadores nativos a nivel de bit y rotación compilados a una sola instrucción máquina:\n- **Lógicos y bit a bit**: `and`, `or`, `xor`, `not`\n- **Desplazamientos y rotaciones de 64 bits**: `shl` (izquierda), `shr` (derecha lógica sin signo), `rotl` (rotación izquierda), `rotr` (rotación derecha)\n\nAdemás, el vocabulario de Ridgun es deliberadamente mínimo y ortogonal:\n- **`where` no es una palabra reservada**: los grafos paralelos se expresan exclusivamente con `||`, por lo que `where` es un identificador normal que puedes usar como nombre de variable o función.\n- **`fn` y `op` pueden formar parte de los nombres**: después de `fn ::` u `op ::`, las propias palabras `fn` y `op` pueden aparecer como parte del nombre de tu función u operador (`fn :: op Int => Int`, `op :: Int fn Int => Int`, `op :: A op B => A`).\n- **Retornos múltiples separados por espacios (`=> Int Int`)**: cuando una función devuelve varios valores, su salida se escribe separando los tipos con espacios (`fn :: dividir Int Int => Int Int`), y en la llamada puedes desestructurarla directamente sin alojar tuplas en el heap (`cociente, resto = dividir 17 5`).\n\n### ¿Cansado de escribir `65536` o `1048576` y rezar para no haberte comido un dígito?\nEn Ridgun los literales numéricos aceptan directamente los sufijos de memoria **`_KB`**, **`_MB`** y **`_GB`** y los sufijos de tiempo **`_ms`**, **`_s`** y **`_min`**, además de separadores numéricos:\n\n```ridgun\nfn :: tamano buffer => Int\n tamano buffer = 64_KB -- El compilador lo traduce a 65536\n\nfn :: limite archivo => Int\n limite archivo = 16_MB -- El compilador lo traduce a 16777216\n\nfn :: mascara octeto => Int\n mascara octeto = 0xFF_00 -- Hexadecimal limpio y legible\n```\n\n---\n\n## 3.4. Comillas dobles (`\"...\"`) vs. Comillas simples (`'...'`)\n\nEn muchos lenguajes las comillas simples y dobles hacen exactamente lo mismo (un desperdicio). En Ridgun cada una tiene una misión clarísima:\n\n### 1. Comillas dobles (`\"...\"`): Inteligentes e interpoladas\nInterpretan escapes (`\\n`, `\\t`, `\\\"`, `\\\\`) y **evalúan cualquier variable o expresión que metas entre llaves `{...}`**:\n\n```ridgun\nfn :: resumen pedido String Int Int => String\n resumen pedido item uds precio =\n \"Has pedido {uds}x {item}. Total a pagar: {uds * precio} euros.\"\n```\n\n*(Truco: si necesitas escribir una llave literal dentro de comillas dobles —por ejemplo al generar un script de shell `\"$\\{VAR:-default\\}\"` o un JSON—, puedes escaparla con barra invertida `\\{` y `\\}` o duplicarla `{{` y `}}`).*\n\n### 2. Comillas simples (`'...'`): Crudas y literales (*Raw Strings*)\nLo que escribes entre comillas simples sale **tal cual**, sin tocar ni una barra invertida `\\`, ni una llave `{}`. Perfecto para cuando generas código en otro lenguaje, escribes comandos de shell o trabajas con rutas llenas de barras:\n\n```ridgun\nfn :: comando sed => String\n comando sed = 's/\\([0-9]+\\)/[\\1]/g'\n```\n\n### Tu caja de herramientas para cadenas (`String`)\nUnir cadenas es tan simple como sumarlas con `+`: `\"Hola, \" + nombre`. Y cuando necesitas hacer cirugía fina sobre un texto, tienes estas funciones nativas con espacios naturales listas sin importar nada:\n\n- `str len s` $\\rightarrow$ longitud de `s`.\n- `str char at s i` $\\rightarrow$ código numérico (`Int`) del carácter en la posición `i` (`-1` si te sales).\n- `str slice s inicio fin` $\\rightarrow$ recorta desde `inicio` hasta `fin` (sin incluir `fin`).\n- `char from code c` $\\rightarrow$ convierte un código numérico (`Int`, p. ej. `10` para `\\n` o `34` para `\"`) en un `String` de 1 carácter.\n- `str contains s aguja` y `str starts with s prefijo` $\\rightarrow$ devuelven `True` o `False`.\n- `int to str n` y `str to int s` $\\rightarrow$ convierten de número a texto y viceversa (¡`str to int` entiende incluso `\"0xFF\"`!).\n\n---\n\n## 3.5. Bloques de varias líneas, la regla universal del `=` y variables `mut`\n\nCuando una función no cabe en una sola línea, haces un salto de línea después del `=` e indentas el bloque.\n\nMira cómo se lee un bloque paso a paso:\n\n```ridgun\nfn :: calcular factura Int Int => Int\n calcular factura base iva =\n impuestos = base * iva / 100\n gestion = 5\n echo \"Desglose -> Base:\" base \"Impuestos:\" impuestos\n base + impuestos + gestion\n```\n\nCuatro reglas de oro que hacen que los bloques en Ridgun sean limpios y uniformes:\n1. **Inmutable por defecto sin `let` ni `const`**: escribes `impuestos = ...` y ya tienes tu constante local inmutable. Si por descuido intentas sobrescribir `impuestos = 99` más abajo en el mismo bloque, el compilador te frena en seco.\n2. **Variables mutables explícitas con `mut`**: cuando de verdad quieres ir actualizando un acumulador local paso a paso dentro de una función, lo declaras con **`mut`** y luego lo reasignas libremente con `=`:\n ```ridgun\n fn :: aplicar recargos Int => Int\n aplicar recargos precio =\n mut total = precio\n total = total + 10\n total = total * 2\n total\n ```\n *(Y si `total` guarda una lista o estructura en memoria, la política **`RMM2`** de Ridgun suma `+1` al nuevo valor antes de descontar `-1` al antiguo, liberándolo al instante si nadie más lo usa).*\n3. **La regla universal del `=`**: detrás de **cualquier `=`** (en una función, en un operador, en una variable local `x =` o en una rama condicional) puedes poner siempre cualquiera de las tres formas de bloque de Ridgun:\n - **`(nada)`**: líneas secuenciales indentadas (como arriba).\n - **`|`**: un árbol de guardas condicionales.\n - **`||`**: un grafo de cálculos en paralelo con `->`.\n4. **La última expresión es siempre el resultado**: `base + impuestos + gestion` es lo que devuelve `calcular factura`. Sin `return`.\n\nEn el **Capítulo 04** vamos a ver cómo tomar decisiones complejas —incluyendo árboles de decisión de varios niveles con `|.`— sin escribir jamás un `if/else` ni un bucle `while`: bienvenido al mundo de las **guardas booleanas** y la **recursión de cola de coste cero**.\n"}, {"num":"04","slug":"04-pattern-matching-y-guardas","file":"04-pattern-matching-y-guardas.md","title":"Capítulo 04: Adiós al `if/else`, adiós al `while` (Pattern Matching, Guardas y TCO)","short_title":"Adiós al `if/else`, adiós al `while` (Pattern Matching, Guardas y TCO)","markdown":"# Capítulo 04: Adiós al `if/else`, adiós al `while` (Pattern Matching, Guardas y TCO)\n\nCuando le cuentas a alguien que en Ridgun **no existe la palabra clave `if`, ni `else`, ni `switch`, ni `for`, ni `while`**, suele mirarte como si le hubieras dicho que vas a construir una casa sin martillo ni clavos:\n\n> *\"¿Que no hay `if` ni `while`? ¿Y cómo narices tomo una decisión o recorro un millón de datos?\"*\n\nPues resulta que `if/else` y `while` son herramientas bastante toscas heredadas de cómo funcionaban los saltos (`GOTO`) en los años 50. En Ridgun los sustituimos por tres piezas que encajan como un guante:\n1. ***Pattern Matching*** (cuando decides según la **forma o valor exacto** del dato).\n2. **Guardas booleanas `|`** (cuando decides según una **condición o rango**).\n3. **Recursión de cola con TCO** (que el compilador convierte por debajo en un bucle `loop` nativo ultrarrápido).\n\nVamos a verlas en acción.\n\n---\n\n## 4.1. *Pattern Matching*: Cuando el argumento ya te da la respuesta\n\nEn el capítulo anterior ya vimos un adelanto. En lugar de recibir una variable genérica `x` y abrir dentro de la función cinco `if (x == ...)`, en Ridgun pones los casos directamente en la puerta de entrada de la función:\n\n```ridgun\nfn :: codigo http Int => String\n codigo http 200 = \"OK: Todo fue sobre ruedas\"\n codigo http 301 = \"Movido permanentemente\"\n codigo http 404 = \"No encontrado (¿has mirado debajo del sofá?)\"\n codigo http 500 = \"Fuego en el servidor\"\n codigo http n = \"Código desconocido: {n}\"\n```\n\nRidgun prueba las filas **de arriba abajo**:\n- Si entra `404`, salta directo a la tercera fila.\n- Si entra `418`, ninguna de las cuatro primeras filas coincide, así que cae en la última (`n`), que atrapa cualquier entero y lo nombra `n` para que puedas imprimirlo.\n- ¿Y si en el último caso ni siquiera te importa qué número era? Pones un guion bajo `_` (*comodín*) y te olvidas: `codigo http _ = \"Otro código\"`.\n\n---\n\n## 4.2. Guardas booleanas (`|`): Tu bisturí para rangos, variables condicionales y árboles de decisión\n\n*\"Vale, el pattern matching mola cuando busco el `200` o el `404`. ¿Pero qué hago si quiero comprobar si `nota >= 5` o si `edad > 18 and edad < 65`? ¡No voy a escribir 47 filas con todos los números del 18 al 65!\"*\n\n¡Claro que no! Para eso están las **guardas (`|`)**.\n\nCuando el resultado depende de una desigualdad o de una combinación lógica, después del `=` de la función (o de cualquier variable local) abres una escalera de barras verticales `|`:\n\n```ridgun\ncalificar Int => String\ncalificar nota =\n | nota < 0 = \"Nota imposible (¿puntos negativos?)\"\n | nota < 5 = \"Suspenso\"\n | nota < 7 = \"Aprobado\"\n | nota < 9 = \"Notable\"\n | nota <= 10 = \"Sobresaliente\"\n | otherwise = \"Te has pasado de la escala\"\n```\n\nMíralo bien:\n- El signo `=` del encabezado (`calificar nota =`) se alinea debajo de `=>` igual que en cualquier otra función.\n- Debajo se lee de arriba abajo igual que una tabla de tarifas: cada línea `| condicion = resultado` se comprueba en orden. En cuanto una da `True`, devuelve ese resultado y se acabó.\n- Al final pones **`| otherwise = ...`**, que significa literalmente *\"en cualquier otro caso\"* (tu red de seguridad para que nunca quede un camino sin retorno).\n\n### Lógica booleana en minúsculas claras (`and`, `or`, `xor`, `not`)\n¿Te has fijado en que muchos lenguajes usan `|` para bits, `||` para booleanos, `!` para negar... y cuando lees una condición compleja parece que un gato ha caminado sobre el teclado (`if (!a && (b || c))`)?\n\nEn Ridgun, como `|` ya es el símbolo sagrado de las guardas y de los tipos algebraicos, y `||` es el operador de paralelismo, **los operadores booleanos se escriben con palabras claras en minúsculas**:\n\n- **`and`**: se cumplen las dos cosas con evaluación en cortocircuito (`edad >= 18 and tiene_carnet`: si el lado izquierdo es `False`, el derecho ni siquiera se evalúa).\n- **`or`**: se cumple al menos una con evaluación en cortocircuito (`dia == \"sabado\" or dia == \"domingo\": si el lado izquierdo es `True`, el derecho no se evalúa).\n- **`xor`**: se cumple una u otra, pero no las dos a la vez.\n- **`not`**: invierte el booleano (`not (esta vacio texto)`).\n- **`<`, `>`, `<=`, `>=`**: comparan tanto números (`Int`, `Float`) como cadenas de texto **`String` en orden lexicográfico UTF-8** (`\"alfa\" < \"beta\"`).\n\nMira qué limpio queda el clásico problema de los años bisiestos:\n\n```ridgun\nfn :: es bisiesto Int => Bool\n es bisiesto a =\n | (a % 400) == 0 = True\n | (a % 4) == 0 and (a % 100) != 0 = True\n | otherwise = False\n```\n\nCero símbolos crípticos. Lo lee alguien que no sabe Ridgun y lo entiende a la primera.\n\n### Variables locales condicionales (`x =` + `|`)\nComo en Ridgun todo `=` sigue la misma regla, **también puedes usar `|` para asignar una variable local** sin tener que crear una función auxiliar aparte:\n\n```ridgun\nfn :: precio billete Int Bool => Int\n precio billete base vip =\n descuento =\n | vip = 20\n | base > 100 = 10\n | otherwise = 0\n base - (base * descuento / 100)\n```\n\n### Árboles de decisión anidados y el limitador compacto `|.`\n*\"¿Y si dentro de una rama `| cond =` o `| otherwise =` quiero abrir otro sub-árbol de guardas `|` de 3 o 4 niveles de profundidad y luego volver a un nivel superior?\"*\n\nPuedes anidar `|` dentro de `|` tantos niveles como quieras. Y cuando un bloque `|` anidado termina pero debajo siguen más ramas de un nivel padre (o más sentencias del bloque), colocas **`|.`** al final de su última línea (`| otherwise = valor |.`) para indicar sin ninguna ambigüedad que ese sub-árbol se cierra y vuelves al nivel anterior:\n\n```ridgun\nfn :: enrutar peticion Int Int Bool Bool Int Int => Int\n enrutar peticion metodo rol firmada admin kb lim =\n | metodo == 0 =\n | kb == 0 = 204\n | otherwise = 200 |.\n | metodo == 1 =\n | not firmada = 401\n | otherwise =\n -- Nivel 2: petición POST firmada\n | lim > 0 =\n -- Nivel 3: control de rate-limit\n | admin =\n -- Nivel 4: excepción para administradores\n | kb > 10000 = 413\n | otherwise = 202 |.\n | otherwise = 429 |.\n | kb > 1000 =\n -- Nivel 3: hemos vuelto desde el Nivel 4 gracias a `|.`\n | rol >= 2 and admin = 201\n | otherwise = 413 |.\n | otherwise = 201 |.\n | otherwise = 405\n```\n\nFíjate en lo limpio que es:\n- En un árbol simple de 1 nivel (o al final de toda la función) **nunca hace falta poner `|.`** (sería ruido innecesario).\n- Solo escribes `|.` al cerrar una rama anidada cuando debajo continúa una rama de un nivel superior.\n\n### Bloques de varias líneas dentro de una guarda (`| cond =` + bloque indentado)\nIgual que después del `=` de una función normal puedes abrir un bloque indentado de varias líneas, **después del `=` de cualquier guarda `| cond =` o `| otherwise =` también puedes hacer un salto de línea y abrir un bloque secuencial**:\n\n```ridgun\nfn :: ajustar ventana Int Int => Int\n ajustar ventana idx limite =\n | idx >= limite =\n tope = limite - 1\n escalado = tope * 2\n escalado + 10\n | otherwise =\n siguiente = idx + 1\n siguiente * 2\n```\n\nCada rama de la guarda tiene su propio bloque local limpio y su última línea es el valor que devuelve esa rama.\n\n---\n\n## 4.3. \"¿Sin `while`? ¿Y no va a reventar la pila si recorro un archivo grande?\"\n\nHablemos del elefante en la habitación.\n\nSi vienes de Python, Java o JavaScript, te han enseñado desde pequeñito que *\"la recursión es elegante en la pizarra de la universidad, pero en el mundo real desborda la pila (`StackOverflowError`) a las 1.000 vueltas; usa un bucle `while`\"*.\n\nEso es verdad en lenguajes que **no** optimizan las llamadas de cola. Pero en un lenguaje funcional de sistemas como Ridgun, **una recursión de cola ES un bucle `while` disfrazado de función pura**.\n\n### ¿Qué diferencia hay entre una recursión normal y una recursión de cola?\nFíjate en estas dos funciones que cuentan hacia atrás desde `n` hasta `0` sumando los números:\n\n```ridgun\n-- Versión A: Recursión NORMAL (deja trabajo pendiente para la vuelta)\nfn :: sumar lento Int => Int\n sumar lento 0 = 0\n sumar lento n = n + (sumar lento (n - 1))\n\n-- Versión B: Recursión DE COLA (lleva la cuenta al día en `acumulado`)\nfn :: sumar rapido bucle Int Int => Int\n sumar rapido bucle 0 acumulado = acumulado\n sumar rapido bucle n acumulado = sumar rapido bucle (n - 1) (acumulado + n)\n```\n\n- En la **Versión A**, antes de poder sumar `n + ...`, la función tiene que esperar a que termine `sumar lento (n - 1)`. Eso obliga al ordenador a guardar `n` en la pila en cada paso.\n- En la **Versión B**, la llamada a `sumar rapido bucle` **es lo último que ocurre en la línea**. No queda nada pendiente detrás de ella. Ya le pasas la suma actualizada en `acumulado + n`.\n\n### El truco de magia de `ridgunc` (TCO: *Tail-Call Optimization*)\nCuando `ridgunc` compila `sumar rapido bucle`, ve que la función se llama a sí misma al final de la cláusula y dice:\n\n> *\"¿Para qué voy a crear un marco de pila nuevo si el actual ya no sirve para nada? Voy a sobrescribir `n` con `n - 1`, sobrescribir `acumulado` con `acumulado + n` y saltar al principio con un `loop { ... continue; }` nativo.\"*\n\nResultado:\n1. Puedes llamar a `sumar rapido bucle 100_000_000 0` (cien millones de vueltas) y **gastará exactamente 0 bytes extra de pila**.\n2. Va igual de rápido que un bucle `while` escrito a mano en Rust o C, pero sin haber tenido que declarar ni una sola variable mutable en tu código Ridgun.\n\n---\n\n## 4.4. El patrón maestro que usarás en todo programa Ridgun\n\nCada vez que en otro lenguaje pensarías *\"voy a hacer un bucle `while` con un índice `i` y un acumulador\"*, en Ridgun escribes una pareja de funciones como esta (la función principal limpia y su ayudante de cola):\n\n```ridgun\nfn :: buscar caracter bucle String Int Int Int => Int\n buscar caracter bucle texto pos fin buscado =\n | pos >= fin = -1\n | (str char at texto pos) == buscado = pos\n | otherwise = buscar caracter bucle texto (pos + 1) fin buscado\n\nfn :: buscar caracter String Int => Int\n buscar caracter texto buscado = buscar caracter bucle texto 0 (str len texto) buscado\n```\n\nMira las tres guardas de `buscar caracter bucle`:\n1. ¿Llegué al final (`pos >= fin`)? Devuelvo `-1` (fin del bucle).\n2. ¿Encontré el carácter en `pos`? Devuelvo `pos` (fin del bucle).\n3. ¿No es este? Paso al siguiente (`pos + 1`) con una llamada de cola (que el compilador convierte en `continue;`).\n\nLimpio, imposible de tener *bugs* de variables globales pisadas y rápido como un rayo.\n\nEn el **Capítulo 05** vamos a meterle mano a las colecciones de datos: **listas, tuplas, funciones que aceptan infinitos argumentos (`...`) y las máscaras de secuencias estilo J/APL**.\n"}, {"num":"05","slug":"05-listas-tuplas-y-secuencias","file":"05-listas-tuplas-y-secuencias.md","title":"Capítulo 05: Listas, Tuplas y el truco ninja de las Secuencias","short_title":"Listas, Tuplas y el truco ninja de las Secuencias","markdown":"# Capítulo 05: Listas, Tuplas y el truco ninja de las Secuencias\n\nUn programa que solo mueve un número o un texto a la vez sirve de poco. En la vida real manejas listas de archivos, filas de una base de datos, paquetes de red o coordenadas.\n\nPara eso, Ridgun te da tres envases distintos, cada uno pensado para un trabajo muy concreto:\n1. **Listas (`[T]`)**: *\"Tengo muchos elementos del mismo tipo (o ninguno) y quiero recorrerlos\"*.\n2. **Tuplas (`(A, B)`)**: *\"Quiero devolver un par o un trío de cosas distintas juntas sin crear un tipo nuevo\"*.\n3. **Secuencias y máscaras (`s(_ + _)`, `(+ _ _ +)`)**: *\"Quiero filtrar o reordenar argumentos al vuelo como si tuviera un colador matemático\"*.\n\nVamos una por una.\n\n---\n\n## 5.1. Listas (`[T]`): El pan de cada día\n\nEn Ridgun, si pones corchetes alrededor de un tipo, tienes una lista de ese tipo:\n- `[Int]` es una lista de enteros: `[10, 20, 30]`.\n- `[String]` es una lista de textos: `[\"manzana\", \"pera\"]`.\n- `[]` es la lista vacía (el comodín que encaja con cualquier `[T]`).\n\n> *\"Oye, ¿puedo meter un número y un texto juntos en la misma lista tipo `[1, \"dos\"]`?\"*\n> Tal cual, **no** (y dale las gracias al compilador por impedírtelo, porque así nunca te explotará una suma en producción al encontrarse un texto sorpresa). En el Capítulo 06 veremos cómo resolver el caso de `[1, \"dos\"]` con elegancia total usando un Tipo Algebraico (igual que hace Haskell).\n\n### Cómo se trocea una lista sin usar índices `i`, `j`, `k`: `[]` y `(x:xs)`\nEn los lenguajes imperativos, recorrer una lista suele implicar `for (int i = 0; i < lista.size(); i++)`, con el clásico riesgo de pasarte por uno (*off-by-one*).\n\nEn Ridgun no cuentas índices; **desmontas la lista por la cabeza**:\n- `[]`: encaja si la lista está vacía.\n- `(x:xs)`: encaja si la lista tiene al menos un elemento. Mete el primer elemento en `x` (la **cabeza**) y el resto de la lista en `xs` (la **cola**).\n\nMira qué gusto da sumar una lista o contar cuántos elementos tiene:\n\n```ridgun\nfn :: sumar lista [Int] => Int\n sumar lista [] = 0\n sumar lista (x:xs) = x + (sumar lista xs)\n\nfn :: contar elementos [A] => Int\n contar elementos [] = 0\n contar elementos (_:xs) = 1 + (contar elementos xs)\n```\n\nY un detalle técnico que te alegrará el día: en Ridgun, sacar la cola `xs` en `(x:xs)` **no copia el resto de la lista en memoria**. Internamente es una vista sobre el mismo vector que solo avanza el puntero de inicio en `1` ($O(1)$ instantáneo).\n\n### Puedes destapar varios elementos de golpe\nEl patrón `:` da muchísimo juego cuando estás parseando comandos o protocolos:\n\n```ridgun\nfn :: interpretar comando [String] => String\n interpretar comando [] = \"No has escrito nada\"\n interpretar comando (\"ayuda\":_) = \"Mostrando la ayuda...\"\n interpretar comando (\"copiar\":origen:dest:[]) = \"Copiando de {origen} a {dest}\"\n interpretar comando (cmd:_) = \"Comando desconocido: {cmd}\"\n```\n\nMira la tercera fila: `(\"copiar\":origen:dest:[])` comprueba de un solo plumazo que la lista tenga **exactamente 3 elementos**, que el primero sea `\"copiar\"`, y te deja el segundo y el tercero servidos en bandeja en `origen` y `dest`. Intenta escribir eso en C o Java y verás cuántas líneas de `args.length == 3 && args[0].equals(\"copiar\")` te salen.\n\n### Unir listas con doble dos puntos (`::`)\nCuando estás en el lado derecho del `=` (en una expresión) y quieres pegar dos listas, usas **`::`**:\n\n```ridgun\nfn :: pegar listas [Int] [Int] => [Int]\n pegar listas a b = a :: b\n\nfn :: anadir detras [Int] Int => [Int]\n anadir detras xs x = xs :: [x]\n```\n\n*(Regla mnemotécnica rápida: un solo `:` en los patrones desmonta la lista; dos `::` en las expresiones pegan dos listas).*\n\n¿Y si de verdad quieres sacar el elemento número 5 directamente sin recorrer nada? Usa corchetes pegados al nombre: `mi_lista[5]`.\n\n---\n\n## 5.2. Funciones variádicas (`T...`): \"¡Pásame todos los que quieras!\"\n\nA veces quieres que quien llame a tu función pueda escribir `sumar varios 4 19 2 88 7` cómodamente, sin obligarle a envolver los números entre corchetes `[4, 19, 2, 88, 7]`.\n\nPon tres puntos **`...`** detrás del último tipo de tu firma, y dentro de tu función lo recibirás automáticamente empaquetado como una lista:\n\n```ridgun\nfn :: sumar varios Int... => Int\n sumar varios nums = sumar lista nums\n\nfn :: main => ()\n main =\n echo (sumar varios 10 20 30) -- Imprime 60\n echo (sumar varios 1 2 3 4 5 6 7) -- Imprime 28\n```\n\n---\n\n## 5.3. Tuplas y la Unificación de Firmas con Espacios (`Int Int [String String] String`)\n\nEn muchos lenguajes devolver dos o más cosas desde una función es un dolor: o creas una clase auxiliar tonta `ResultadoConPosicion`, o llenas la firma de paréntesis y comas redundantes.\n\nEn Ridgun, **una lista de argumentos `Int String Int` y una tupla `(Int, String, Int)` son conceptualmente lo mismo: una secuencia tipada (`seq`)**. Por eso, en las firmas de tipos puedes separar los elementos de una tupla simplemente con **espacios**, tanto después de `=>`, como dentro de listas `[...]` y dentro de paréntesis `(...)`:\n\n```ridgun\n-- Dos enteros de retorno separados por espacio:\nfn :: dividir y resto Int Int => Int Int\n dividir y resto a b = (a / b, a % b)\n\n-- Firma compleja sin ruido de comas ni paréntesis exteriores:\nfn :: parsear cabecera String => Int Int [String String] String\n parsear cabecera tag = (200, 2, [(\"Host\", \"ridgun.org\"), (\"X-Tag\", tag)], \"OK\")\n\n-- Lista de tuplas [String String] como parámetro de entrada:\nfn :: buscar cabecera [String String] String => String\n buscar cabecera [] _ = \"no encontrada\"\n buscar cabecera ((k, v):resto) clave\n | k == clave = v\n | otherwise = buscar cabecera resto clave\n```\n\nFíjate en la elegancia de `=> Int Int [String String] String`:\n- Después de `=>`, `Int Int [String String] String` significa exactamente lo mismo que `(Int, Int, [(String, String)], String)`.\n- Dentro de corchetes, `[String String]` significa una lista de pares de textos `[(String, String)]`.\n- Dentro de paréntesis (cuando pasas una tupla como un único parámetro de entrada), `(Int String)` significa exactamente lo mismo que `(Int, String)`:\n ```ridgun\n fn :: explicar division (Int Int) => String\n explicar division (cociente, resto) = \"Cabe a {cociente} y sobran {resto}\"\n ```\n- **¿Por qué esto no crea ninguna ambigüedad en Ridgun?** Porque a diferencia de Haskell (donde `Maybe Int` aplica un constructor de tipos por yuxtaposición), en Ridgun los tipos parametrizados usan paréntesis explícitos como `Tensor(Float, 3, 3)` y las listas usan `[T]`. Así, dos tipos separados por espacio (`T1 T2`) significan siempre y sin excepción una secuencia/tupla de tipos, manteniendo compatibilidad al 100% con la sintaxis con comas.\n\n### El detalle estrella: Desempaquetado automático entre funciones\nSupón que tienes `dividir y resto` (que devuelve `Int Int`) y otra función `sumar par Int Int => Int` (que espera 2 enteros). En Ridgun puedes enchufar una función dentro de la otra directamente:\n\n```ridgun\nfn :: sumar par Int Int => Int\n sumar par x y = x + y\n\nfn :: main => ()\n main =\n -- dividir y resto 17 5 devuelve (3, 2), y sumar par recibe 3 y 2 directamente:\n echo (sumar par (dividir y resto 17 5)) -- Imprime 5\n```\n\n---\n\n## 5.4. El truco ninja: Secuencias y Máscaras Posicionales (`s(_ + _)`, `(+ _ _ +)`)\n\nAquí viene una de las joyas más originales de Ridgun, heredada directamente de los lenguajes vectoriales como J y APL.\n\nImagina que recibes una ristra de argumentos con el patrón de secuencia **`(s)`** y quieres quedarte solo con algunos y descartar otros sin declarar cinco variables con nombres inventados (`_a`, `b`, `_c`, `d`...).\n\nLe aplicas a `s` una **máscara de tupla con `+` (quédatelo) y `_` (descártalo)**:\n\n- **`s(_ + _)`**: de 3 elementos, descarta el primero (`_`), quédate con el segundo (`+`) y descarta el tercero (`_`).\n- **`s(+ _ +)`**: quédate con el primero y el tercero; devuelve la secuencia con esos dos.\n- **`s(_ _ +...)`**: descarta los dos primeros (`_ _`) y aplica `+...` **a todos los demás que vengan detrás**, sean cuantos sean.\n\nMíralo funcionando:\n\n```ridgun\nfn :: el del medio Int Int Int => Int\n el del medio (s) = s(_ + _)\n\nfn :: descartar dos primeros Int... => [Int]\n descartar dos primeros (s) = s(_ _ +...)\n\nfn :: main => ()\n main =\n echo (el del medio 100 42 999) -- Imprime 42\n echo (descartar dos primeros 1 2 10 20 30) -- Imprime [10, 20, 30]\n```\n\nMejor aún: puedes colocar una máscara **`(+ _ _ +)`** como una etapa intermedia dentro de una tubería **`->`** (`FR-COMP-014`). Cuando una función u operador devuelve una tupla de varios valores, la máscara filtra qué posiciones conservas (`+`) y cuáles descartas (`_`), y los elementos seleccionados se desempaquetan automáticamente como argumentos de la siguiente función de la tubería:\n\n```ridgun\nfn :: parsear paquete String => Int Int String String\n parsear paquete ver = (200, 4, ver, \"OK\")\n\nfn :: combinar codigo Int String => String\n combinar codigo code msg = \"{code}:{msg}\"\n\nfn :: main => ()\n main =\n -- De (200, 4, \"v0.9.9\", \"OK\"), (+ _ _ +) selecciona el 1.º (200) y el 4.º (\"OK\")\n -- y los pasa como los 2 argumentos de `combinar codigo`:\n parsear paquete \"v0.9.9\" -> (+ _ _ +) -> combinar codigo -> resumen\n echo resumen -- Imprime 200:OK\n```\n\nAdemás, si usas el operador coma `,` fuera de paréntesis (`lista1 , lista2`), Ridgun aplana ambas colecciones en una única secuencia continua.\n\nEn el **Capítulo 06** vamos a dar el paso que convierte a un programador funcional en un arquitecto de primera: **crear tus propios Tipos de Datos Algebraicos (ADTs), escribir funciones polimórficas con `A`..`Z` y fabricar tus propios operadores a medida**.\n"}, {"num":"06","slug":"06-tipos-algebraicos-y-polimorfismo","file":"06-tipos-algebraicos-y-polimorfismo.md","title":"Capítulo 06: Tipos de Datos Algebraicos (ADTs), Polimorfismo y por qué aquí no queremos `null` ni `Any`","short_title":"Tipos de Datos Algebraicos (ADTs), Polimorfismo y por qué aquí no queremos `null` ni `Any`","markdown":"# Capítulo 06: Tipos de Datos Algebraicos (ADTs), Polimorfismo y por qué aquí no queremos `null` ni `Any`\n\nEn 1965, Tony Hoare inventó la referencia nula (`null` / `nil`). Cuarenta años después pidió perdón públicamente y la llamó *\"mi error de mil millones de dólares\"*.\n\n¿Por qué? Porque en los lenguajes con `null`, una función te promete en su firma que te va a devolver un `Usuario`... y cuando menos te lo esperas te devuelve `null` y te tira abajo el servidor a las tres de la mañana.\n\nY otro tanto ocurre con la herencia de objetos y el tipo `Any` (u `Object`): metes un número y un texto en una lista `[1, \"dos\"]`, el lenguaje se encoge de hombros y dice *\"vale, pues ahora esto es una `List[Any]`, buena suerte adivinando qué hay dentro cuando la recorras\"*.\n\nEn Ridgun cortamos esos dos problemas de raíz con **Tipos de Datos Algebraicos (ADTs)** y **Polimorfismo Paramétrico**. Suena a nombre de asignatura dura, pero en cinco minutos vas a ver que es la forma más natural del mundo de modelar datos.\n\n---\n\n## 6.1. Tipos Producto (Registros): Tus `struct` de toda la vida, pero con superpoderes\n\nCuando quieres agrupar varios datos que pertenecen a una misma entidad (por ejemplo, un servidor con su nombre, su IP y su puerto), defines un **`type`** con sus campos:\n\n```ridgun\ntype Servidor = host: String, puerto: Int, tls_activo: Bool\n```\n\nCon solo escribir esa línea, Ridgun te regala **cuatro cosas** automáticamente:\n\n### 1. El constructor `Servidor`\n```ridgun\nfn :: mi nodo => Servidor\n mi nodo = Servidor \"api.ejemplo.com\" 443 True\n```\n\n### 2. Una función *getter* pura para cada campo (`host`, `puerto`, `tls_activo`)\nNo tienes que escribir `s.getHost()` ni nada parecido; cada nombre de campo es directamente una función que extrae ese dato:\n\n```ridgun\nfn :: url conexion Servidor => String\n url conexion srv = \"Conectar a \" + (host srv) + \" en el puerto {puerto srv}\"\n```\n\n### 3. Actualización funcional de registros (`with campo: valor`)\nImagina un registro de configuración con 12 o 15 campos (como las opciones de un comando `ls` o de un servidor web) y quieres cambiar solo uno o dos campos sin tocar el resto. En vez de reconstruir el registro pasando los 15 campos a mano, usas **`with`** y dos puntos **`:`** (exactamente igual que en la definición del `type`):\n\n```ridgun\nfn :: activar modo dev Servidor => Servidor\n activar modo dev srv = srv with puerto: 8080, tls_activo: False\n```\n\nLo mejor de los dos mundos:\n- En tu código es **100% inmutable y seguro** (el compilador comprueba que `puerto` y `tls_activo` existan en `Servidor` y tengan el tipo correcto).\n- Por debajo, si `srv` no está compartido con nadie más, el ejecutable nativo reutiliza su memoria *in-place* (`Arc::try_unwrap`) sin copiar el resto de campos.\n\n### 4. *Pattern matching* completo por si quieres abrir el registro en canal\n```ridgun\nfn :: es puerto seguro Servidor => Bool\n es puerto seguro (Servidor _ 443 True) = True\n es puerto seguro _ = False\n```\n\n---\n\n## 6.2. Tipos Suma (Variantes con `|`): Cómo jubilar a `null`, a las excepciones y a la herencia\n\nAhora viene lo bueno.\nUn registro normal tiene `host` **Y** `puerto` **Y** `tls_activo`.\nPero en el mundo real muchas veces necesitas decir: *\"Un método de pago es **O BIEN** una tarjeta con su número, **O BIEN** una transferencia con su IBAN, **O BIEN** efectivo (que no lleva ningún dato extra)\"*.\n\nEn orientación a objetos tendrías que crear una interfaz `MetodoPago`, tres subclases en tres archivos distintos, constructores, `instanceof`... En Ridgun es **una línea**:\n\n```ridgun\ntype MetodoPago = Tarjeta String Int | Transferencia String | Efectivo\n```\n\nY mira qué pasa cuando procesas un `MetodoPago` en una función:\n\n```ridgun\nfn :: describir pago MetodoPago => String\n describir pago (Tarjeta num _) = \"Pago con tarjeta terminada en {num}\"\n describir pago (Transferencia iban) = \"Transferencia desde la cuenta {iban}\"\n describir pago Efectivo = \"Pago en metálico en ventanilla\"\n```\n\nCada variante (`Tarjeta`, `Transferencia`, `Efectivo`) lleva pegados únicamente los datos que tienen sentido para ella. Es **imposible** construir un pago en `Efectivo` que tenga un `iban` colgado por error.\n\n---\n\n## 6.3. \"¿Y cómo meto `[1, \"dos\"]` en una lista sin usar `Any`?\"\n\nVolvamos a la pregunta del millón. Imagina que estás leyendo un archivo CSV o JSON donde una celda puede ser un número entero, un texto o estar vacía.\n\nSi tuvieras un tipo `Any`, meterías `[1, \"dos\"]` y perderías toda la ayuda del compilador: a partir de ese momento cualquier operación sobre la lista sería caminar con los ojos vendados.\n\nEn Ridgun (igual que en Haskell o Rust), creas en dos segundos un tipo suma que explica exactamente qué cosas pueden vivir dentro de esa lista:\n\n```ridgun\ntype ValorCelda = Entero Int | Texto String | Vacio\n\nfn :: formatear celda ValorCelda => String\n formatear celda (Entero n) = \"Número: {n}\"\n formatear celda (Texto s) = \"Texto: {s}\"\n formatear celda Vacio = \"(vacío)\"\n\nfn :: mi fila => [ValorCelda]\n mi fila = [Entero 1, Texto \"dos\", Vacio]\n```\n\n¿Ves la diferencia?\n- Con `Any`, el compilador no sabe si en la lista hay enteros, cadenas o una cafetera conectada por USB.\n- Con `[ValorCelda]`, puedes mezclar números y textos en la misma lista, **pero el compilador sabe al 100% que solo existen esas tres posibilidades** y te obliga a desempaquetar cada caso (`Entero n`, `Texto s`, `Vacio`) antes de usarlo. Seguridad total sin perder ni un ápice de flexibilidad.\n\n---\n\n## 6.4. `Option` y `Result`: Cuando algo puede no estar o puede fallar\n\nComo en Ridgun no existe `null` ni existen excepciones que salten por los aires (`throw new Exception()`), el lenguaje trae integrados dos tipos suma universales:\n\n1. **`Option`**: o tienes **`Some valor`**, o tienes **`None`** (nada).\n2. **`Result`**: o la operación salió bien (**`Ok valor`**), o falló por un motivo concreto (**`Err error`**).\n\n```ridgun\nfn :: buscar division Int Int => Result\n buscar division _ 0 = Err \"¡Eh! No se puede dividir por cero\"\n buscar division a b = Ok (a / b)\n\nfn :: imprimir res Result => ()\n imprimir res (Ok n) = echo \"El resultado es {n}\"\n imprimir res (Err msg) = echo \"Fallo controlado: {msg}\"\n```\n\nNadie puede ignorar un error por descuido porque el propio tipo `Result` te obliga a mirar si dentro hay un `Ok` o un `Err`.\n\n---\n\n## 6.5. Polimorfismo Paramétrico: Funciones genéricas con `A`, `B`, `T`...\n\nSupón que escribes una función que invierte un par `(x, y)` o que saca el primer elemento de una lista. ¿De verdad importa si la lista es de `Int`, de `String` o de `Servidor`? ¡Para nada! El algoritmo es idéntico.\n\nEn Ridgun, **cualquier letra mayúscula individual (`A` a `Z`)** en una firma de tipos funciona automáticamente como un **tipo comodín polimórfico (variable de tipo)**:\n\n```ridgun\nfn :: cabeza segura [A] A => A\n cabeza segura [] defecto = defecto\n cabeza segura (x:_) _ = x\n\nfn :: girar par (A B) => B A\n girar par (x, y) = (y, x)\n```\n\nFíjate en la magia de `cabeza segura [A] A => A` y `girar par (A B) => B A`:\n- No es un `Any` descontrolado: dice que **el tipo de la lista `[A]`, el tipo del valor `defecto` (`A`) y el tipo devuelto (`A`) tienen que ser el mismo**.\n- Si llamas a `cabeza segura [10, 20] 0`, el compilador ata cabos (`A = Int`) y te devuelve un `Int`.\n- Si llamas a `cabeza segura [\"a\", \"b\"] \"ninguno\"`, ata cabos (`A = String`) y te devuelve un `String`.\n- Pero si intentas hacer trampa y llamas a `cabeza segura [10, 20] \"ninguno\"`, ¡zas! El compilador te frena en seco porque el primer argumento dice que `A` es `Int` y el segundo dice que `A` es `String`.\n\n---\n\n## 6.6. Crea tus propios operadores (`op ::`): Desde vectores hasta frases a medida\n\n¿Por qué solo los números nativos iban a tener derecho a usar `+` o `*`? ¿Y por qué conformarse con los operadores de siempre cuando puedes inventar la sintaxis perfecta para tu problema?\n\nCon **`op ::`** (o directamente sin prefijo si usas patrones de operador) puedes hacer dos virguerías:\n\n### 1. Enseñarle a `+`, `-`, `*`, `/`, `%` a operar con tus propios tipos\n```ridgun\ntype Complejo = real: Int, imag: Int\n\nop :: Complejo + Complejo => Complejo\n a + b = Complejo ((real a) + (real b)) ((imag a) + (imag b))\n```\n\n### 2. Crear operadores *Mixfix* (que se leen como frases naturales, incluidos `type` y `with`)\nEn una firma de operador, **las palabras con mayúscula inicial (`Int`, `String`, `A`..`Z`) o entre corchetes/paréntesis (`[Int]`, `(A, B)`) son los huecos para los argumentos**, y **las palabras en minúscula son parte de la frase del operador** (incluso palabras tan habituales como `type` o `with`, que en Ridgun son contextuales y nunca te bloquean):\n\n```ridgun\nop :: si Bool entonces A sino A => A\n si True entonces v sino _ = v\n si False entonces _ sino f = f\n\nop :: format String type String with Int => String\n format label type kind with w = \"{label} ({kind}, width={w})\"\n\nmain => ()\nmain =\n mensaje = si (10 > 5) entonces \"¡Diez gana!\" sino \"Imposible\"\n echo mensaje\n echo (format \"panel\" type \"bordered\" with 80)\n```\n\nAcabas de fabricarte tu propio operador ternario polimórfico y un DSL de formateo en **3 líneas** de Ridgun.\n\n---\n\n## 6.7. Constructores compartidos, variantes de tipo directo, `expand type` y espacios naturales en `fn ::`\n\nRidgun lleva los tipos algebraicos y las funciones un paso más allá en expresividad y extensibilidad:\n\n1. **Constructores compartidos entre distintos `type`**: Varios tipos suma pueden reutilizar el mismo nombre de constructor (`Error`, `Var`, `Lit`) con igual o distinta aridad; el verificador de tipos los distingue por la columna de la firma.\n2. **Variantes con nombre de tipo directo (`type Something = String | Int | MyStruct Float Int`)**: Cuando una variante es un tipo primitivo (`String`, `Int`, `Float`/`Real`, `Bool`, `Char`), no necesitas repetir `Int Int` ni `String String`. Puedes pasar valores directos (`\"hola\"`, `42`) o constructores explícitos (`String \"hola\"`, `Int 42`), y hacer pattern matching tanto con literales (`\"admin\"`, `0`) como con patrones tipados (`(String s)`, `(Int n)`).\n3. **Extensión abierta de tipos (`expand type`) y fusión automática de cláusulas `fn ::`**: Con `expand type` puedes añadir nuevas variantes a un tipo suma o nuevos campos a un registro. Al volver a declarar `fn ::` para la misma función, las nuevas cláusulas se fusionan automáticamente **antes** de cualquier cláusula comodín `_` (sin necesidad de escribir `expand fn`).\n4. **Espacios naturales `\" \"` en lugar de `_` en `fn ::`, funciones nativas y nombres de archivo**: Igual que en `op ::`, los nombres de función en `fn ::`, las llamadas a funciones nativas (`assert equals`, `str len`, `read file`) y los archivos `.rg` (`test core language.rg`) pueden escribirse con espacios naturales:\n\n```ridgun\ntype Something = String | Int | MyStruct Float Int\n\nfn :: describir algo Something => String\n describir algo \"admin\" = \"admin_especial\"\n describir algo 0 = \"cero\"\n describir algo (String s) = \"texto:{s}\"\n describir algo (Int n) = \"entero:{n}\"\n describir algo (MyStruct ratio cnt) = \"estructura:{ratio}:{cnt}\"\n describir algo _ = \"otro\"\n\nexpand type Something = Bool\n\nfn :: describir algo Something => String\n describir algo (Bool b) = \"booleano:{b}\"\n```\n\n### 5. Tipos genéricos propios (`type Result T E`, `type Page T`) y espacios de nombres de módulo (`import ... as`, `mod.Tipo`)\nAdemás de funciones polimórficas, puedes declarar **tus propios tipos suma y registros genéricos** con parámetros de tipo (`A`..`Z`), así como organizar tipos, constructores, campos y funciones con el mismo nombre en distintos módulos sin colisión (`module ::`, `import db as d`, `http.Config`, `d.Config`). Cada módulo tiene **visibilidad estricta por importación en 3 niveles**: resuelve los nombres sin prefijo buscando primero en **(1) sus propias declaraciones** (incluyendo varios archivos que compartan `module :: nombre`), después en **(2) sus `import` directos** y solo en último lugar en **(3) sus `import` transitivos**, de modo que otros módulos hermanos o la aplicación principal nunca contaminan una librería importada:\n\n```ridgun\ntype Resultado T E = Exito T | Fallo E\ntype Pagina T = elementos: [T], total: Int\n\nfn :: desempaquetar exito (Resultado A String) A => A\n desempaquetar exito (Exito v) _ = v\n desempaquetar exito (Fallo _) d = d\n```\n\nEn el **Capítulo 07** vamos a poner todos los núcleos de tu procesador a trabajar a la vez sin derramar una gota de sudor: **funciones de orden superior (`map`, `||map`, `filter`, `||filter`, `fold`), clausuras léxicas (`(x => expr)`), grafos paralelos `||` con `->` y canales concurrentes**.\n"}, {"num":"07","slug":"07-orden-superior-y-paralelismo-dag","file":"07-orden-superior-y-paralelismo-dag.md","title":"Capítulo 07: Funciones de Orden Superior y Paralelismo sin Sudor (Ni `Mutex`, ni `Deadlocks`)","short_title":"Funciones de Orden Superior y Paralelismo sin Sudor (Ni `Mutex`, ni `Deadlocks`)","markdown":"# Capítulo 07: Funciones de Orden Superior y Paralelismo sin Sudor (Ni `Mutex`, ni `Deadlocks`)\n\nEn casi todos los lenguajes de programación hay un momento dramático en la vida de un proyecto: el día en que alguien dice en una reunión:\n\n> *\"Esto va lento. Hay que paralelizarlo con hilos.\"*\n\nSe hace el silencio en la sala. Todo el mundo sabe lo que viene después: semanas cazando *race conditions* fantasma que solo ocurren los martes con luna llena, bloqueos mutuos (*deadlocks*) y errores irreproducibles.\n\nEn Ridgun, cuando alguien dice *\"hay que paralelizarlo\"*, basta con anteponer `||` a tus operaciones de lista (**`||map`** y **`||filter`**) o poner tus cálculos en un bloque paralelo **`||`** con flechas `->` y te vas a tomar un café.\n\n¿Por qué? Porque **cuando tus funciones son puras y tus datos son inmutables por defecto, dos núcleos de CPU trabajando a la vez jamás pueden pisarse la manguera**.\n\nVamos a verlo paso a paso.\n\n---\n\n## 7.1. Funciones que reciben funciones: `map`, `||map`, `filter`, `||filter` y `fold`\n\nEn Ridgun una función es un valor más. Puedes pasarla por parámetro igual que pasas el número `7`.\n\nEl lenguaje trae de serie los combinadores de colecciones para transformar listas sin escribir ni una sola recursión manual, tanto en modo secuencial (`map`, `filter`, `fold`) como en modo paralelo multicore con el prefijo `||` (**`||map`**, **`||filter`**):\n\n### 1. `map` (secuencial) y `||map` (paralelo multicore)\nSupón que tienes una función `costoso Int => Int` y una lista con números:\n\n```ridgun\ncostoso Int => Int\ncostoso x = (x * x) + (x * 3)\n\nmain => ()\nmain =\n datos = [1, 2, 3, 4, 5, 6, 7, 8]\n -- Secuencial estricto de izquierda a derecha:\n r1 = map costoso datos\n -- Paralelo multicore con el prefijo `||`:\n r2 = ||map costoso datos\n echo r1\n echo r2\n```\n\n- Con **`map`**, Ridgun aplica la función elemento a elemento de izquierda a derecha en el hilo actual (ideal para listas pequeñas o funciones con efectos de entrada/salida ordenados).\n- Con **`||map`**, el runtime de Ridgun reparte los elementos de `datos` entre los hilos trabajadores de tu procesador y reconstruye la lista final **exactamente en el mismo orden**, garantizando que `||map f xs == map f xs`.\n\n### 2. `filter` (secuencial) y `||filter` (paralelo): Quedarte solo con los que cumplan una condición\nLe pasas una función que devuelva `Bool` (un *predicado*) y una lista:\n\n```ridgun\nfn :: es positivo Int => Bool\n es positivo n = n > 0\n\nfn :: main => ()\n main =\n limpios = filter es positivo [-5, 10, 0, -2, 42]\n limpios_par = ||filter es positivo [-5, 10, 0, -2, 42]\n echo limpios -- Imprime [10, 42]\n echo limpios_par -- Imprime [10, 42]\n```\n\n### 3. `fold`: Reducir toda una lista a un solo resultado\nLe pasas una función combinadora de dos argumentos `(Acumulado Elemento => Acumulado)`, el valor inicial y la lista:\n\n```ridgun\nfn :: acumular producto Int Int => Int\n acumular producto acc x = acc * x\n\nfn :: main => ()\n main =\n echo (fold acumular producto 1 [2, 3, 4, 5]) -- 1 * 2 * 3 * 4 * 5 = 120\n```\n\n### ¿Cómo escribo yo mi propia función que reciba otra función?\nMuy fácil: en la firma de tipos pones la firma de la función invitada entre paréntesis `(Entrada => Salida)`:\n\n```ridgun\nfn :: ejecutar dos veces (A => A) A => A\n ejecutar dos veces f x = f (f x)\n```\n\n### 4. Clausuras léxicas y funciones anónimas (`(x => expr)`, `(acc x => expr)`)\nCuando quieres pasar una función corta al vuelo o capturar variables locales de tu ámbito actual (como un desplazamiento, un prefijo o una sesión activa) sin declarar una función global auxiliar, usas una **clausura léxica** entre paréntesis con **`=>`**:\n\n```ridgun\nfn :: procesar con sesion => [Int]\n procesar con sesion =\n base = 10\n bono = 3\n sumados = map (x => x + base) [1, 2, 3]\n filtrados = filter (x => x >= bono) [1, 2, 3, 4]\n total = fold (acc x => acc + x + bono) 0 [1, 2, 3]\n pares = map ((a b) => a + b + base) [(1, 2), (3, 4)]\n sumados :: filtrados :: [total] :: pares\n```\n\nCinco reglas mantienen las lambdas limpias y sin ambigüedad:\n- **Paréntesis obligatorios**: toda lambda va siempre entre paréntesis `(... => ...)`.\n- **Lambda sin parámetros (`(=> expr)`)**: igual que `fn :: main => ()` no lleva `()` antes de `=>`, una lambda sin parámetros se escribe `(=> expr)` (y su tipo es `(=> Tipo)`), nunca `(() => expr)`.\n- **Varios parámetros frente a tupla (`(a b => ...)` vs. `((a b) => ...)`)**: `(acc x => ...)` recibe dos parámetros separados; `((a b) => ...)` recibe una sola tupla y la desestructura.\n- **Cuerpo de varias líneas**: si pones un salto de línea tras `=>`, puedes escribir un bloque sangrado de varios pasos antes del `)` de cierre:\n ```ridgun\n dobles = map (x =>\n paso = x * 2\n paso + base\n ) [1, 2, 3]\n ```\n- **Mayúsculas frente a minúsculas**: `(Conn => Conn)` es un tipo de función (inicial mayúscula) y `(conn => conn)` es un valor lambda (inicial minúscula).\n\nLas variables capturadas (`base`, `bono`) se empaquetan automáticamente con gestión determinista de memoria (`RMM1`/`RMM2`), sin fugas y sin pausas de recolector de basura.\n\n### 5. El estilo más Ridgunero (recomendado): Tuberías fluidas con `->`, invocación por la izquierda y bloques sin `_ =` (`FR-COMP-014`)\nEscribir `map f xs` o asignar cada paso intermedio a una variable (`r1 = ...`, `r2 = ...`) está muy bien, pero donde Ridgun se disfruta al 100% es encadenando transformaciones de izquierda a derecha con **`->`** y ligando el resultado final directamente con **`-> variable`**:\n\n```ridgun\nop :: [Int] compact limit Int => [Int]\n [] compact limit _ = []\n (x:xs) compact limit lim\n | x <= lim = [x] :: (xs compact limit lim)\n | otherwise = xs compact limit lim\n\nfn :: main => ()\n main =\n xs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n\n -- Estilo recomendado (2b): arranque infijo natural + `->` entre etapas + `-> results`\n xs map (x => x * 2) -> filter (x => x > 6) -> compact limit 14 -> results\n echo results -- Imprime [8, 10, 12, 14]\n```\n\nRidgun unifica las cuatro formas de escribir la cadena en el mismo árbol sintáctico (AST), para que elijas la que mejor se lea en cada caso:\n1. **Estilo `2b` (recomendado)**: `xs map f -> filter g -> compact limit 14 -> results` (arranque infijo natural con el sujeto a la izquierda, `->` separando cada etapa y `-> results` al final).\n2. **Estilo `2` (`->` desde el valor inicial)**: `xs -> map f -> filter g -> compact limit 14 -> results`.\n3. **Estilo `1` (infijo continuo sin `->` intermedios)**: `xs map f filter g compact limit 14 -> results`.\n4. **Estilo `3` (arranque prefijo clásico + `->`)**: `map f xs -> filter g -> compact limit 14 -> results`.\n\nY si la tubería es larga, puedes partirla en varias líneas sangradas que empiezan por `->`:\n\n```ridgun\n xs map (x => x * 3)\n -> filter (x => x >= 12)\n -> fold 0 (acc x => acc + x)\n -> total\n```\n\nLa regla universal de `->` es sencilla y consistente en todo el lenguaje:\n- **El valor que viene por la izquierda entra como PRIMER argumento** de la función u operador de la derecha (`x -> f a b` $\\equiv$ `x f a b` $\\equiv$ `f x a b`), salvo en `map`/`filter`/`fold` (donde entra como la colección sobre la que iterar) o si colocas un comodín explícito **`_`** (`payload -> write file ruta_salida _`).\n- **Envío directo a agentes**: `[5, 10, 15] -> sumar lista -> send to ag` y `[1, 2, 3] -> sumar lista -> ask to ag -> total`.\n- **Máscaras de secuencia en tránsito (`-> (+ _ _ +) ->`)**: `parsear paquete \"v0.9.9\" -> (+ _ _ +) -> combinar codigo -> resumen`.\n- **Bloques secuenciales limpios sin `_ =` y descarte explícito `-> ()`**: las llamadas con efectos laterales (`echo`, `write file`, `shell exec`, `log`, etc.) se escriben directamente sin `_ =`. Si la última línea de una función `=> ()` llama a una función que devuelve un valor (como `Int`), basta con terminar esa línea con **`-> ()`** para descartar el resultado explícitamente y devolver `()`.\n\n---\n\n## 7.2. La joya de la corona: Bloques `||` con `->` como Grafos Paralelos (DAGs)\n\nAquí es donde Ridgun cambia tu forma de pensar.\n\nEn un lenguaje imperativo normal, cuando escribes tres líneas seguidas:\n1. Primero se ejecuta la línea 1.\n2. Luego la línea 2 (aunque no tenga nada que ver con la línea 1).\n3. Luego la línea 3.\n\nEso es obligar al ordenador a trabajar con orejeras. En Ridgun, cuando usas barras paralelas **`||`** con flechas **`->`** (sin necesidad de escribir ninguna palabra clave como `where`), no estás dando órdenes secuenciales: estás dibujando un **Grafo Acíclico Dirigido (DAG)** que dice qué cálculo alimenta a qué variable:\n\n```ridgun\nfn :: fib Int => Int\n fib 0 = 0\n fib 1 = 1\n fib n = (fib (n - 1)) + (fib (n - 2))\n\nfn :: fact Int => Int\n fact 0 = 1\n fact n = n * (fact (n - 1))\n\nfn :: super calculo => Int\n super calculo = rama_izquierda + rama_derecha\n || fib 10 -> rama_izquierda\n || fact 6 -> rama_derecha\n```\n\nLéelo en voz alta:\n- *\"Quiero calcular `rama_izquierda + rama_derecha`, mientras en paralelo (`||`) `fib 10` va a `rama_izquierda` (`->`) y `fact 6` va a `rama_derecha` (`->`).\"*\n\n### Lo que hace `ridgunc` entre bambalinas con tus líneas `||`:\n1. Mira las flechas `->` y arma el árbol de dependencias (DAG).\n2. **Caza ciclos estáticamente**: si por error escribes `|| a + 1 -> b` y `|| b + 1 -> a`, el compilador te dice en el acto *\"¡Quietos! Hay una dependencia circular\"* y no te deja compilar un cuelgue infinito.\n3. Ve que `fib 10` y `fact 6` no dependen el uno del otro, así que tienen luz verde para correr en paralelo antes de sumarse en `rama_izquierda + rama_derecha`.\n\nIncluso puedes montar un pipeline de varias plantas dentro del mismo bloque `||` —ya sea al pie de la expresión o justo después del `=`— y el compilador deducirá solito qué plantas pueden ir a la vez y cuáles tienen que esperar:\n\n```ridgun\nfn :: balance trimestral Int => Int\n balance trimestral base =\n || base * 100 -> ventas_brutas\n || base * 40 -> costes_fijos\n || ventas_brutas - costes_fijos -> beneficio_bruto ||.\n beneficio_bruto * 80 / 100\n```\n\nFíjate en el detalle **`||.`** al final de la última línea `||`: igual que `|.` cierra un árbol de guardas anidado, **`||.`** cierra el bloque paralelo `||` cuando debajo siguen sentencias secuenciales normales. Aquí `ventas_brutas` y `costes_fijos` arrancan en paralelo; cuando ambos están listos se calcula `beneficio_bruto`, y finalmente se devuelve `beneficio_bruto * 80 / 100`. Todo sin un solo `async`, `Future`, `Promise` ni `Mutex`.\n\n---\n\n## 7.3. El atajo `||` sin nombres: Tuberías paralelas que devuelven una tupla\n\n¿Y si solo quieres lanzar dos o tres cálculos en paralelo y recogerlos juntos en una tupla sin siquiera inventar nombres de variables con `->`?\n\nUsas `||` directamente:\n\n```ridgun\nfn :: par paralelo Int => Int Int\n par paralelo n =\n || fib n\n || fact n\n```\n\nDirecto, visual y devuelve la tupla `(fib n, fact n)`.\n\n---\n\n## 7.4. Cuando quieres el volante manual: el Modelo Nativo de Agentes (`agent`, `send ... to`, `receive from`) y Canales CSP\n\nEl paralelismo automático de `||` y `||map` cubre el 90% de los casos. Pero cuando programas un servidor persistente, sesiones de usuario con estado aislado, colas de trabajo o un pipeline productor-consumidor, quieres **agentes concurrentes con memoria aislada y paso de mensajes tipado**.\n\nEn Ridgun no hace falta inventar una sintaxis extraña como `actor ::`: **cualquier función normal (`fn ::` u `op ::`) se convierte en un agente concurrente en una línea con `agent`**:\n\n### 1. Agentes sin estado y con estado aislado (`agent`, `send ... to`, `receive from`, `stop`)\n- `ag = agent (mi funcion)`: convierte una función `M => R` en un agente sin estado de tipo `Agent M R`.\n- `ag = agent (mi funcion con estado) estado_inicial`: convierte una función `S M => S` (o `S M => S R`) en un agente con estado aislado que recuerda su estado mensaje tras mensaje sin un solo `Mutex`.\n- `send msg to ag` (o con varios argumentos: `send a b to ag`): envía un mensaje tipado al buzón del agente.\n- `res = receive from ag` (o `try receive from ag` sin bloquear): recoge la siguiente respuesta del buzón de salida del agente.\n- `stop ag`: detiene el agente y libera su estado automáticamente mediante `RMM1`/`RMM2`.\n\n```ridgun\nfn :: sumar visita Int Int => Int\n sumar visita acum n = acum + n\n\nfn :: procesar lote Int Int => Int\n procesar lote id t = (id * 100) + t\n\nfn :: main => ()\n main =\n contador = agent (sumar visita) 10\n send 5 to contador\n send 7 to contador\n v1 = receive from contador\n v2 = receive from contador\n stop contador\n\n trabajador = agent (procesar lote)\n send 3 25 to trabajador\n lote = receive from trabajador\n stop trabajador\n echo \"Visitas:\" v1 \"->\" v2 \"| Lote:\" lote\n```\n\n### 2. Elección inteligente en tiempo de compilación (*Green Threads* vs. Hilos Reales del Kernel)\nUna pregunta habitual es: *\"¿Los bloques `||`, `||map` y los agentes `agent` crean siempre hilos del sistema operativo o corren como tareas ligeras?\"*\n\nEn Ridgun, **el compilador toma esa decisión por ti analizando estáticamente cada función**:\n- **Funciones ligeras (`TierTrivial` / `StratInlineSequential`)**: Si la función que pasas a `||`, `||map`, `||filter` o `agent` tiene poca profundidad (aritmética, formateo de cadenas, transición de estado rápida de una sesión HTTP), calentar hilos del sistema operativo costaría más que el propio cálculo. El compilador la ejecuta como un paso *green-thread* ultraligero sin sobrecarga de `SYS_clone`.\n- **Cálculos pesados o recursivos profundos (`TierDynamicRecursive` / `TierHeavyCPU`)**: Si la función tiene recursión profunda (como `fib` o `fact`) o álgebra de tensores (`einsum`, `hadamard`, `@`), el compilador genera automáticamente el despacho multinúcleo sobre **hilos reales del kernel (`SYS_clone`)**, procesando las ramas `||` o la cola de mensajes del `agent` en paralelo real sobre todos tus núcleos de CPU.\n\n### 3. Pasar mensajes por canales acotados (`new bounded channel`)\nIgual que en Go o Erlang, además de agentes también puedes crear tubos explícitos para comunicar etapas:\n\n- `new bounded channel capacidad`: crea un canal con espacio para `capacidad` mensajes.\n- `send through valor canal` (o `chan send valor canal`): mete un mensaje en el tubo.\n- `receive from canal` (o `chan recv canal`): saca el siguiente mensaje del tubo.\n- `try receive from canal`: mira si hay mensaje sin quedarse esperando; devuelve `Some valor` o `None`.\n- `receive from timeout canal ticks`: espera un límite de tiempo y devuelve `Ok valor` o `Err Timeout`.\n\n```ridgun\nfn :: main => ()\n main =\n tubo = new bounded channel 4\n send through \"Tarea A completada\" tubo\n send through \"Tarea B completada\" tubo\n primero = receive from tubo\n segundo = receive from tubo\n echo primero \"|\" segundo\n```\n\n### 4. Agentes sumidero (*fire-and-forget* `agent sink`), buzones globales (`agent ::`) y pruebas deterministas (`with agent`)\nCuando necesitas auditoría asíncrona, métricas, trazas o *rate limiting* transversal sin ensuciar todas las firmas con un parámetro extra ni acumular respuestas sin leer en una cola de salida, Ridgun ofrece **agentes sumidero (`agent sink`)** y **buzones globales de nivel superior (`agent ::`)**:\n- `rec = agent sink (paso) [estado_inicial], mailbox N drop|block, drain 2_s`: procesa cada mensaje en orden FIFO (`S M => S` o `M => ()`), libera el estado anterior con `RMM1`/`RMM2` y **no acumula cola de salida** (`receive from`, `try receive from` y `ask` sobre un `SinkAgent` son errores de compilación).\n- `agent :: audit_log = sink (paso) [], mailbox 10_000 drop, drain 2_s`: declara un agente sumidero global inicializado antes de `main` y vaciado ordenadamente al salir del programa.\n- `agent dropped ag`, `agent flush ag`, `agent state ag` y `with agent audit_log = espia do ...`: permiten observar mensajes descartados por contrapresión (`drop`), drenar el buzón, inspeccionar el estado y redirigir temporalmente un buzón global a un agente espía aislado en los tests.\n- Además, los descriptores de `Channel`, `Agent` y `SinkAgent` pueden compartirse libremente entre múltiples trabajadores (`send ch 1 to w1`, `send ch 2 to w2`), mientras que los datos enviados por el canal mantienen su propiedad afín (`ErrUseAfterSend`).\n\nY ahora, abróchate el cinturón para el **Capítulo 08**, el gran final: **tensores con dimensiones verificadas en compilación, variables de ámbito (`context`/`shared`), regiones de memoria deterministas, acceso directo a hardware MMIO y programación de sistemas UNIX/POSIX**.\n"}, {"num":"08","slug":"08-tensores-regiones-y-sistemas","file":"08-tensores-regiones-y-sistemas.md","title":"Capítulo 08: Tensores, Memoria Compilada RMM (`RMM1`/`RMM2`), Regiones y Sistemas","short_title":"Tensores, Memoria Compilada RMM (`RMM1`/`RMM2`), Regiones y Sistemas","markdown":"# Capítulo 08: Tensores, Memoria Compilada RMM (`RMM1`/`RMM2`), Regiones y Sistemas\n\nHasta 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`.\n\nLlegados a este punto, el programador veterano de sistemas suele levantar la mano al fondo del aula con media sonrisa y preguntar:\n\n> *\"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?\"*\n\nExcelentes 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.\n\n---\n\n## 8.1. El dilema de la memoria: ni Garbage Collector ni peleas con el Borrow Checker (`RMM1` y `RMM2`)\n\nEn el mundo de los lenguajes modernos suele haber dos bandos irreconciliables:\n\n1. **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.\n2. **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`.\n\nRidgun 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`)**:\n\n### Política `RMM1` — Gestión de Memoria Compilada (CMM Lineal y Condicional)\nCuando el compilador conoce estáticamente el flujo de las variables locales dentro de una función:\n- **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.\n- **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.\n\n### Política `RMM2` — Conteo por Asignación y Cascada Estructural\n¿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:\n\n1. **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.\n2. **Toda asignación a una variable local interna o celda de contenedor suma `+1`**:\n - `a = [10, 20]` $\\rightarrow$ el bloque `M1` nace con contador `1`.\n - `xs = [a, b]` $\\rightarrow$ la lista `xs` nace con `1`, y como guarda referencias a `a` y `b`, ambos suben a `2`.\n - Si reasignas una variable mutable (`mut x = ...` y luego `x = nuevo`), `RMM2` aplica primero **`+1`** al valor nuevo antes de aplicar **`-1`** al antiguo.\n3. **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**.\n4. **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`.\n\n```ridgun\nelegir Bool [Int] [Int] => [Int]\nelegir cond a b =\n | cond = a\n | otherwise = b\n\nejemplo rmm2 Bool => Int\nejemplo rmm2 cond =\n a = [10, 20, 30] -- M1 (rc = 1)\n b = [40, 50, 60] -- M2 (rc = 1)\n c = elegir cond a b -- `c` recibe +1; el no elegido muere aquí mismo (rc -> 0)\n (contar a) + (contar c) -- Al terminar, `a` y `c` reciben -1 y todo queda en 0 bloques vivos\n```\n\n---\n\n## 8.2. Regiones Léxicas con Cuota (`with region`): Cortafuegos de memoria en $O(1)$\n\nAdemá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`**:\n\n```ridgun\nprocesar trama String => Result\nprocesar trama crudo =\n with region 64_KB do\n limpias = map limpiar linea (dividir lineas crudo)\n contar validos limpias\n```\n\n1. **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.\n2. **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.\n\n---\n\n## 8.3. Tensores Nativos, Contracción de Einstein (`@`) yDimensiones Simbólicas\n\nSi 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:\n\n```text\nRuntimeError: mat1 and mat2 shapes cannot be multiplied (128x64 and 32x128)\n```\n\nEn Ridgun, los **`Tensor`** son ciudadanos nativos de primera clase y sus dimensiones forman parte de la firma de tipos:\n\n```ridgun\n-- Si entra (N × K) y (K × M), ¡por narices sale (N × M)!\nmultiplicar matrices Tensor[N, K] Tensor[K, M] => Tensor[N, M]\nmultiplicar matrices a b = a_ik @ b_kj\n```\n\nFíjate en dos detalles espectaculares:\n1. **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.\n2. **Notación de subíndices de Einstein (`a_ik @ b_kj`) y Hadamard (`a_ij * b_j`)**:\n - Con **`@`**, los subíndices repetidos en todos los operandos (`k`) se contraen (se suman) y los únicos (`i`, `j`) sobreviven en el tensor resultado.\n - 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`).\n3. **Tensores tipados (`Tensor[T * d1 * d2]`), subíndices matemáticos `t_(i, j)`, vistas `t_(i,)` y mapeo *in-place* (`in`)**:\n 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`:\n ```ridgun\n fn :: blamka p16 Tensor[U64 * 16] => ()\n blamka p16 v =\n v_(0), v_(4), v_(8), v_(12) = blamka g v_(0) v_(4) v_(8) v_(12)\n\n fn :: comprimir bloque Tensor[U64 * 8 * 16] Tensor[U64 * 8 * 16] => Tensor[U64 * 8 * 16]\n comprimir bloque x y =\n r_ij = x_ij xor y_ij\n z_ij = r_ij\n ||map blamka p16 in z_(i,)\n ||map blamka p16 in z_(,j)\n z_ij xor r_ij\n ```\n\n---\n\n## 8.4. Acceso a Hardware (`hardware ::`) y Primitivas POSIX de Sistemas\n\nEl 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:\n\n```ridgun\nhardware :: UART0 @ 0x1000_0000\n tx_data : Int @ 0x00\n status : Int @ 0x04\n```\n\nY para trabajar con el sistema operativo:\n\n| Primitiva | Firma | Qué hace |\n|---|---|---|\n| `cli args` | `=> [String]` | Argumentos de línea de comandos (`argv`). |\n| `read file` / `write file` | `String => String` / `String String => ()` | Lectura y escritura íntegra de archivos en disco. |\n| `read bytes` / `write bytes` | `String => [Int]` / `String [Int] => ()` | Lectura y escritura binaria exacta byte a byte (`0..255`). |\n| `list dir` / `file lstat` | `String => [String]` / `String => ...` | Listado de directorios e inspección de metadatos POSIX (`lstat`). |\n| `sha256 file` / `sha256 str` | `String => String` | Hash criptográfico SHA-256 nativo en hexadecimal (64 caracteres). |\n| `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). |\n| `sys exec` / `shell exec` | `... => Int` | Ejecución de procesos del sistema operativo. |\n| `sys getcwd` / `sys chdir` | `=> String` / `String => Int` | Consultar o cambiar el directorio de trabajo. |\n| `sys getenv` / `sys setenv` | `String => String` / `String String => Int` | Variables de entorno del proceso. |\n| `sys getuid` / `sys getgid` | `=> Int` | UID y GID reales del proceso (`doas`, herramientas de seguridad). |\n| `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`. |\n| `sys exit` | `Int => ()` | Terminar el proceso con código de salida. |\n\n---\n\n## 8.5. Caso de Estudio Real: Construyendo `rg-wc`, una utilidad Unix paralela\n\nVamos 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:\n\n```ridgun\n-- 1. Modelamos las métricas de un archivo con un registro ADT (Capítulo 06)\ntype Metricas = archivo: String, lineas: Int, bytes: Int\n\n-- 2. Contamos saltos de línea (código ASCII 10) con recursión de cola TCO (Capítulo 04)\nfn :: contar saltos String Int Int Int => Int\n contar saltos texto pos fin acum =\n | pos >= fin = acum\n | (str char at texto pos) == 10 = contar saltos texto (pos + 1) fin (acum + 1)\n | otherwise = contar saltos texto (pos + 1) fin acum\n\nfn :: numero de lineas String => Int\n numero de lineas texto = contar saltos texto 0 (str len texto) 0\n\n-- 3. Analizamos un archivo y devolvemos su estructura Metricas\nfn :: analizar archivo String => Metricas\n analizar archivo ruta =\n contenido = read file ruta\n || numero de lineas contenido -> n_lineas\n || str len contenido -> n_bytes ||.\n Metricas ruta n_lineas n_bytes\n\n-- 4. Formateamos cada fila y sumamos totales con `fold` y `||` (Capítulo 07)\nfn :: formatear metrica Metricas => String\n formatear metrica m = \"{lineas m}\\t{bytes m}\\t{archivo m}\"\n\nfn :: sumar lineas Int Metricas => Int\n sumar lineas acc m = acc + (lineas m)\n\nfn :: sumar bytes Int Metricas => Int\n sumar bytes acc m = acc + (bytes m)\n\nfn :: generar informe [String] => String\n generar informe rutas =\n metricas = ||map analizar archivo rutas\n || map formatear metrica metricas -> filas\n || fold sumar lineas 0 metricas -> total_l\n || fold sumar bytes 0 metricas -> total_b ||.\n \"{filas}\\nTotal: {total_l} líneas, {total_b} bytes\"\n\nfn :: main => ()\n main =\n echo (generar informe [\"README.md\", \"AGENTS.md\"])\n```\n\nPárate un segundo a mirar esas ~40 líneas de código:\n- No hay ni un solo `null`, ni una excepción descontrolada, ni un `Mutex`.\n- `contar saltos` se compila a un `loop` nativo con 0 bytes de pila gracias al TCO automático.\n- `||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.\n- La memoria temporal se libera determinísticamente mediante **`RMM1`/`RMM2`**.\n- 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`.\n\n---\n\n## 8.6. Hoja de Ruta Final: Las 10 Reglas de Oro del Programador Ridgun\n\n1. **Escribe siempre primero el contrato (`=>`) y después las ecuaciones (`=`):** pensar en los tipos primero hace que el compilador trabaje para ti.\n2. **Deja que `stable format` maquete tus tablas:** escribe con un espacio y deja que la herramienta alinee columnas, `=>`, `=` y `->`.\n3. **Recuerda la regla universal del `=`:** detrás de cualquier `=` puedes abrir un bloque secuencial, un árbol de guardas `|` o un grafo paralelo `||`.\n4. **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.\n5. **Inmutable por defecto (`x = ...`), mutable explícito (`mut x = ...`):** usa `mut` únicamente cuando un acumulador local lo pida de verdad.\n6. **Usa operadores booleanos claros en minúsculas (`and`, `or`, `xor`, `not`) y cierra siempre tus guardas con `| otherwise =`.**\n7. **Modela tu dominio con ADTs (`type`):** usa registros con `with campo: valor` y tipos suma (`Option`, `Result`) en lugar de `null`.\n8. **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.\n9. **Ponle dimensiones simbólicas a tus Tensores (`Tensor[N, K]`) y subíndices a tus contracciones (`a_ik @ b_kj`).**\n10. **Confía en `RMM` (`RMM1`/`RMM2`), acota entradas externas con `with region` y verifica siempre con `stable check` y `stable test`.**\n\n¡Feliz viaje programando en Ridgun!\n"}]; |
| 416 | const UI_STRINGS = {"copy_label":"Copiar","copied_label":"¡Copiado!","prev_label":"← Capítulo anterior","next_label":"Siguiente capítulo →","search_hits_label":"Resultados para","search_empty_label":"No se encontraron coincidencias para"}; |
| 417 | let currentIdx = 0; |
| 418 | |
| 419 | function escapeHtml(s) { |
| 420 | return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); |
| 421 | } |
| 422 | |
| 423 | function highlightRidgun(code) { |
| 424 | return code.split("\n").map(line => { |
| 425 | const comIdx = (() => { |
| 426 | let inD = false, inS = false; |
| 427 | for (let i = 0; i < line.length - 1; i++) { |
| 428 | if (line[i] === "\\") { i++; continue; } |
| 429 | if (line[i] === '"' && !inS) inD = !inD; |
| 430 | else if (line[i] === "'" && !inD) inS = !inS; |
| 431 | else if (!inD && !inS && line[i] === "-" && line[i+1] === "-") return i; |
| 432 | } |
| 433 | return -1; |
| 434 | })(); |
| 435 | const codePart = comIdx >= 0 ? line.slice(0, comIdx) : line; |
| 436 | const comPart = comIdx >= 0 ? line.slice(comIdx) : ""; |
| 437 | let html = escapeHtml(codePart); |
| 438 | // Strings |
| 439 | html = html.replace(/("([^"\\]|\\.)*"|'[^']*')/g, '<span class="rg-str">$1</span>'); |
| 440 | // Keywords |
| 441 | html = html.replace(/\b(fn|op|type|import|mut|otherwise|with|region|do|global|context|shared|atomic|hardware|echo|and|or|xor|not)\b/g, '<span class="rg-kw">$1</span>'); |
| 442 | // Types & constructors |
| 443 | html = html.replace(/\b(Int|Real|String|Char|Bool|Tensor|Option|Result|Some|None|Ok|Err|OutOfMemory|Timeout|True|False)\b/g, '<span class="rg-ty">$1</span>'); |
| 444 | // Operators |
| 445 | html = html.replace(/(::|=>|->|<-|\|\|\.|\|\||\|\.)/g, '<span class="rg-op">$1</span>'); |
| 446 | // Numbers & byte units |
| 447 | html = html.replace(/\b(0x[0-9a-fA-F_]+|[0-9][0-9_]*(_KB|_MB|_GB)?(\.[0-9]+)?)\b/g, '<span class="rg-num">$1</span>'); |
| 448 | if (comPart) { |
| 449 | html += '<span class="rg-com">' + escapeHtml(comPart) + '</span>'; |
| 450 | } |
| 451 | return html; |
| 452 | }).join("\n"); |
| 453 | } |
| 454 | |
| 455 | function formatInline(text) { |
| 456 | let out = escapeHtml(text); |
| 457 | out = out.replace(/`([^`]+)`/g, "<code>$1</code>"); |
| 458 | out = out.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>"); |
| 459 | out = out.replace(/\*([^*]+)\*/g, "<em>$1</em>"); |
| 460 | out = out.replace(/\$O\(([^)]+)\)\$/g, "<code>O($1)</code>"); |
| 461 | out = out.replace(/\$\\rightarrow\$/g, "→"); |
| 462 | out = out.replace(/\$1024\^2\$/g, "1024<sup>2</sup>"); |
| 463 | out = out.replace(/\$1024\^3\$/g, "1024<sup>3</sup>"); |
| 464 | out = out.replace(/\$1024\$/g, "1024"); |
| 465 | return out; |
| 466 | } |
| 467 | |
| 468 | function renderMarkdown(md) { |
| 469 | const lines = md.split("\n"); |
| 470 | let html = ""; |
| 471 | let i = 0; |
| 472 | while (i < lines.length) { |
| 473 | const line = lines[i]; |
| 474 | // Fenced code block |
| 475 | if (line.startsWith("```")) { |
| 476 | const lang = line.slice(3).trim() || "ridgun"; |
| 477 | i++; |
| 478 | const buf = []; |
| 479 | while (i < lines.length && !lines[i].startsWith("```")) { |
| 480 | buf.push(lines[i]); |
| 481 | i++; |
| 482 | } |
| 483 | i++; // skip closing ``` |
| 484 | const rawCode = buf.join("\n"); |
| 485 | const bodyHtml = (lang === "ridgun" || lang === "") ? highlightRidgun(rawCode) : escapeHtml(rawCode); |
| 486 | html += `<div class="code-wrapper"><div class="code-header"><span>${escapeHtml(lang)}</span><button class="copy-btn" onclick="copyCode(this)">${escapeHtml(UI_STRINGS.copy_label)}</button></div><pre data-raw="${ encodeURIComponent(rawCode) }"><code>${bodyHtml}</code></pre></div>`; |
| 487 | continue; |
| 488 | } |
| 489 | // Horizontal rule |
| 490 | if (/^---+$/.test(line.trim())) { |
| 491 | html += "<hr />"; |
| 492 | i++; |
| 493 | continue; |
| 494 | } |
| 495 | // Headings |
| 496 | if (line.startsWith("# ")) { |
| 497 | html += `<h1>${formatInline(line.slice(2).trim())}</h1>`; |
| 498 | i++; |
| 499 | continue; |
| 500 | } |
| 501 | if (line.startsWith("## ")) { |
| 502 | html += `<h2>${formatInline(line.slice(3).trim())}</h2>`; |
| 503 | i++; |
| 504 | continue; |
| 505 | } |
| 506 | if (line.startsWith("### ")) { |
| 507 | html += `<h3>${formatInline(line.slice(4).trim())}</h3>`; |
| 508 | i++; |
| 509 | continue; |
| 510 | } |
| 511 | // Table |
| 512 | if (line.trim().startsWith("|") && i + 1 < lines.length && /^\|[\s:-]+\|/.test(lines[i+1].trim())) { |
| 513 | const headers = line.trim().slice(1, -1).split("|").map(c => formatInline(c.trim())); |
| 514 | i += 2; |
| 515 | const rows = []; |
| 516 | while (i < lines.length && lines[i].trim().startsWith("|")) { |
| 517 | rows.push(lines[i].trim().slice(1, -1).split("|").map(c => formatInline(c.trim()))); |
| 518 | i++; |
| 519 | } |
| 520 | html += '<div class="table-wrap"><table><thead><tr>' + headers.map(h => `<th>${h}</th>`).join("") + '</tr></thead><tbody>' + |
| 521 | rows.map(r => '<tr>' + r.map(c => `<td>${c}</td>`).join("") + '</tr>').join("") + '</tbody></table></div>'; |
| 522 | continue; |
| 523 | } |
| 524 | // Blockquote |
| 525 | if (line.startsWith(">")) { |
| 526 | const qLines = []; |
| 527 | while (i < lines.length && lines[i].startsWith(">")) { |
| 528 | qLines.push(lines[i].replace(/^>\s?/, "")); |
| 529 | i++; |
| 530 | } |
| 531 | html += `<blockquote><p>${qLines.map(formatInline).join("<br/>")}</p></blockquote>`; |
| 532 | continue; |
| 533 | } |
| 534 | // Unordered list |
| 535 | if (/^\s*-\s+/.test(line)) { |
| 536 | const items = []; |
| 537 | while (i < lines.length && /^\s*-\s+/.test(lines[i])) { |
| 538 | items.push(formatInline(lines[i].replace(/^\s*-\s+/, ""))); |
| 539 | i++; |
| 540 | } |
| 541 | html += "<ul>" + items.map(it => `<li>${it}</li>`).join("") + "</ul>"; |
| 542 | continue; |
| 543 | } |
| 544 | // Ordered list |
| 545 | if (/^\s*\d+\.\s+/.test(line)) { |
| 546 | const items = []; |
| 547 | while (i < lines.length && /^\s*\d+\.\s+/.test(lines[i])) { |
| 548 | items.push(formatInline(lines[i].replace(/^\s*\d+\.\s+/, ""))); |
| 549 | i++; |
| 550 | } |
| 551 | html += "<ol>" + items.map(it => `<li>${it}</li>`).join("") + "</ol>"; |
| 552 | continue; |
| 553 | } |
| 554 | // Empty line |
| 555 | if (!line.trim()) { |
| 556 | i++; |
| 557 | continue; |
| 558 | } |
| 559 | // Paragraph |
| 560 | const pLines = []; |
| 561 | while (i < lines.length && lines[i].trim() && !lines[i].startsWith("#") && !lines[i].startsWith("```") && !lines[i].startsWith(">") && !lines[i].trim().startsWith("|") && !/^---+$/.test(lines[i].trim()) && !/^\s*(-\s+|\d+\.\s+)/.test(lines[i])) { |
| 562 | pLines.push(formatInline(lines[i])); |
| 563 | i++; |
| 564 | } |
| 565 | html += `<p>${pLines.join(" ")}</p>`; |
| 566 | } |
| 567 | return html; |
| 568 | } |
| 569 | |
| 570 | window.copyCode = function(btn) { |
| 571 | const pre = btn.closest(".code-wrapper").querySelector("pre"); |
| 572 | const raw = decodeURIComponent(pre.getAttribute("data-raw")); |
| 573 | navigator.clipboard.writeText(raw); |
| 574 | const old = btn.textContent; |
| 575 | btn.textContent = UI_STRINGS.copied_label; |
| 576 | setTimeout(() => { btn.textContent = old; }, 1400); |
| 577 | }; |
| 578 | |
| 579 | function renderSidebar() { |
| 580 | const nav = document.getElementById("navList"); |
| 581 | nav.innerHTML = CHAPTERS.map((ch, idx) => ` |
| 582 | <li class="nav-item"> |
| 583 | <button class="nav-btn ${idx === currentIdx ? 'active' : ''}" onclick="selectChapter(${idx})"> |
| 584 | <span class="nav-num">${ch.num}</span> |
| 585 | <span>${escapeHtml(ch.short_title)}</span> |
| 586 | </button> |
| 587 | </li> |
| 588 | `).join(""); |
| 589 | } |
| 590 | |
| 591 | window.selectChapter = function(idx) { |
| 592 | currentIdx = idx; |
| 593 | const ch = CHAPTERS[idx]; |
| 594 | location.hash = ch.slug; |
| 595 | renderSidebar(); |
| 596 | let pagerHtml = '<div class="pager">'; |
| 597 | if (idx > 0) { |
| 598 | pagerHtml += `<button class="pager-btn prev" onclick="selectChapter(${idx - 1})"><span class="pager-label">${UI_STRINGS.prev_label}</span><span class="pager-title">${escapeHtml(CHAPTERS[idx - 1].title)}</span></button>`; |
| 599 | } else { |
| 600 | pagerHtml += '<div></div>'; |
| 601 | } |
| 602 | if (idx + 1 < CHAPTERS.length) { |
| 603 | pagerHtml += `<button class="pager-btn next" onclick="selectChapter(${idx + 1})"><span class="pager-label">${UI_STRINGS.next_label}</span><span class="pager-title">${escapeHtml(CHAPTERS[idx + 1].title)}</span></button>`; |
| 604 | } |
| 605 | pagerHtml += '</div>'; |
| 606 | document.getElementById("chapterContent").innerHTML = renderMarkdown(ch.markdown) + pagerHtml; |
| 607 | window.scrollTo({ top: 0, behavior: "smooth" }); |
| 608 | }; |
| 609 | |
| 610 | document.getElementById("searchInput").addEventListener("input", (e) => { |
| 611 | const q = e.target.value.trim().toLowerCase(); |
| 612 | if (q.length < 2) { |
| 613 | selectChapter(currentIdx); |
| 614 | return; |
| 615 | } |
| 616 | const hits = []; |
| 617 | CHAPTERS.forEach((ch, idx) => { |
| 618 | const lines = ch.markdown.split("\n"); |
| 619 | for (const l of lines) { |
| 620 | if (l.toLowerCase().includes(q)) { |
| 621 | hits.push({ idx, title: ch.title, snippet: l.replace(/^[#>*-]+\s*/, "").slice(0, 140) }); |
| 622 | break; |
| 623 | } |
| 624 | } |
| 625 | }); |
| 626 | const box = hits.length |
| 627 | ? `<div class="search-results"><strong>${escapeHtml(UI_STRINGS.search_hits_label)} "${escapeHtml(q)}" (${hits.length}):</strong>` + |
| 628 | hits.map(h => `<div class="search-hit" onclick="document.getElementById('searchInput').value=''; selectChapter(${h.idx})"><div class="search-hit-title">${escapeHtml(h.title)}</div><div class="search-hit-snippet">${escapeHtml(h.snippet)}</div></div>`).join("") + |
| 629 | `</div>` |
| 630 | : `<div class="search-results">${escapeHtml(UI_STRINGS.search_empty_label)} "${escapeHtml(q)}".</div>`; |
| 631 | document.getElementById("chapterContent").innerHTML = box + renderMarkdown(CHAPTERS[currentIdx].markdown); |
| 632 | }); |
| 633 | |
| 634 | document.getElementById("themeBtn").addEventListener("click", () => { |
| 635 | const root = document.documentElement; |
| 636 | root.setAttribute("data-theme", root.getAttribute("data-theme") === "dark" ? "light" : "dark"); |
| 637 | }); |
| 638 | |
| 639 | // Initial load from URL hash if present |
| 640 | const initialSlug = location.hash.replace(/^#/, ""); |
| 641 | const foundIdx = CHAPTERS.findIndex(c => c.slug === initialSlug); |
| 642 | selectChapter(foundIdx >= 0 ? foundIdx : 0); |
| 643 | </script> |
| 644 | </body> |
| 645 | </html> |