04 — Numbers: cómo se implementa en cada familia
04 — Numbers: how it is implemented in each family
ES: Guía de implementación del módulo. Explica cómo y por qué el pseudocódigo de la especificación se escribe de forma distinta en cada familia de lenguajes. No añade requisitos: los criterios están en
04_Numbers.mdy, si esta guía y la especificación discrepan, manda la especificación.EN: Implementation guide for the module. It explains how and why the specification’s pseudocode is written differently in each language family. It adds no requirements: the criteria live in
04_Numbers.mdand, should this guide and the specification disagree, the specification wins.
1. La solución canónica, en llano / The canonical solution, in plain words
ES: El módulo pide cinco algoritmos —suma de los primeros n, factorial, Fibonacci, MCD y MCM— resueltos tres veces, con el mismo resultado y distinto reparto del trabajo:
_rec(recursión directa) es la definición matemática escrita tal cual:sum(n) = n + sum(n - 1). Al volver de la llamada queda trabajo pendiente (esa suma que se hace al final), y por eso cada llamada ocupa un marco de pila hasta que la recursión termina._acc(recursión con acumulador) mueve ese trabajo pendiente a un parámetro:sum(n, acc) = sum(n - 1, acc + n). Ya no hay nada que hacer al volver, así que la llamada puede ser la última instrucción. Eso es tail-call: si el lenguaje lo garantiza, la pila no crece y la recursión es un bucle disfrazado._ite(iterativo) hace lo mismo que_accpero con un bucle: memoria constante por construcción, sin depender de ninguna optimización.
Lo que la especificación fija de verdad: los cinco algoritmos, los 11 casos por variante, la separación código/pruebas, solo biblioteca estándar y el techo de complejidad. Lo que es solo forma: cuántas de las tres variantes puede probar tu lenguaje. Eso lo decide el lenguaje, no el módulo.
EN: The module asks for five algorithms —sum of the first n, factorial, Fibonacci, GCD and LCM— solved three times, with the same result and a different distribution of work: _rec is the mathematical definition as is (work is left after the call, so each call holds a stack frame), _acc moves that work into a parameter (nothing left to do on return, so the call is the last instruction — a tail call), and _ite does the same with a loop (constant memory by construction, with no optimisation needed).
What the specification actually fixes: the five algorithms, the 11 cases per variant, the source/test split, standard library only and the complexity ceiling. What is just form: how many of the three variants your language can test. The language decides that, not the module.
2. Los ejes que cambian en este módulo / The axes that change here
| Eje / Axis | Pregunta / Question | Si sí / If yes | Si no / If no |
|---|---|---|---|
| Recursión de cola (TCO) | ¿El runtime garantiza que una auto-llamada de cola no crece la pila? | _acc es un bucle real y lleva su suite (11 casos más) |
_acc se queda como puente didáctico hacia _ite, sin suite propia |
| Iteración nativa | ¿Hay bucles for/while (o combinadores usados de forma iterativa)? |
_ite lleva su suite |
La suite iterativa se omite |
| Memoria y llamadas | ¿Quién paga la recursión? | Pila + GC, o TCO con pila constante, o bucle sin recursión | — |
ES: Los dos primeros ejes son independientes: de ahí salen las combinaciones de 33 (rec+acc+ite), 22 (rec+acc o rec+ite) y 11 (rec sola) pruebas. Cada README del módulo declara la suya y por qué.
EN: The first two axes are independent, which is where the 33 / 22 / 11 test combinations come from. Each module README declares which one applies and why.
3. Las variantes, familia a familia / The variants, family by family
ES: El mismo caso —sum_of_first_n(3) = 6— escrito en el código real del repo:
EN: The same case —sum_of_first_n(3) = 6— written in the repo’s real code:
| Familia (ejemplos) / Family | Se escribe así / It is written like this | Por qué / Why | Qué gana y qué pierde / Gains and loses |
|---|---|---|---|
F1 OO con null (java, csharp, kotlin, scala…) |
int result = 0;for (int i = 1; i <= n; i++) { result += i;}return result; |
el objeto agrupa el estado y el bucle es explícito | legible y sin sorpresas; el recorrido es manual y verboso |
F2 Scripting dinámico (python, ruby, javascript…) |
result = 0for i in range(1, n + 1): result += ireturn result |
misma forma que F1, sin tipos declarados | lo más corto de escribir; sin TCO, así que _acc no lleva suite |
F3 C-lineage (c, ada, cobol, forth, assembly) |
int sum = 0;for (int i = 1; i <= n; i++) { sum += i;}return sum; |
control total y memoria manual | control y cero GC; mucha ceremonia (ada 196 líneas, cobol 440) |
F5 Funcional ML/Haskell (haskell, ocaml, elm, fsharp…) |
sumFirstNRec n \| n <= 0 = 0 \| otherwise = n + sumFirstNRec (n - 1) |
no hay mutación: la recursión es el recorrido, y el acumulador se pasa como parámetro | una expresión por caso; el coste es que la pila depende de TCO |
F7 BEAM (erlang, elixir, gleam) |
sum_first_n_rec(0) -> 0;sum_first_n_rec(N) -> N + sum_first_n_rec(N - 1). |
TCO garantizada: la recursión es el bucle, pero sin crecer la pila | pila constante e idiomático; no hay bucles, así que no hay _ite |
ES: En F5 y F7 la variante _acc sí importa: es la diferencia entre crecer la pila y no crecerla. En F1–F3 la misma _acc no aporta nada medible sin TCO, y el módulo lo dice en voz alta: se queda como puente didáctico, sin suite.
EN: In F5 and F7 the _acc variant matters: it is the difference between growing the stack and not. In F1–F3 the same _acc buys nothing measurable without TCO, and the module says so out loud: it stays as a didactic bridge, with no suite.
4. Consecuencias: coste, pila y memoria / Consequences: cost, stack and memory
ES: La complejidad algorítmica ya la fija la especificación ($O(n)$ en las sumatorias y el factorial, $O(\log n)$ en el MCD) y fibonacci_rec es $O(2^n)$ por diseño educativo. Lo que distingue a los tres enfoques no es el tiempo, es la memoria:
_recdeja trabajo pendiente al volver, así que la pila crece conn(un marco por llamada). Con los 11 casos del módulo no hay problema; con unngrande, sí._accdeja la pila constante solo si el runtime garantiza TCO. Si no lo garantiza, es exactamente_reccon otra forma. Esa es la razón de la regla condicional de las suites, no una comodidad._itegasta memoria constante en todos los lenguajes: es el criterio «memoria constante $O(1)$» de la especificación.
Coste de expresión medido (líneas de la fuente del módulo por lenguaje, 2026-10-16): de 43 en elixir y 46 en erlang a 196 en ada, 358 en assembly y 440 en cobol. La diferencia es la forma del lenguaje, no el algoritmo: los cinco algoritmos y sus 11 casos son los mismos en los 50.
EN: Algorithmic complexity is fixed by the specification and fibonacci_rec is $O(2^n)$ on purpose. What distinguishes the three approaches is not time but memory: _rec grows the stack with n, _acc keeps it constant only when TCO is guaranteed (that is why the suite rule is conditional, not a convenience) and _ite spends constant memory in every language, which is the specification’s $O(1)$ criterion. Measured expression cost ranges from 43 lines in Elixir to 440 in COBOL, and that is the language’s shape, not the algorithm’s.
5. Trampas para quien llega nuevo / Traps for newcomers
_accsin TCO no compra nada. Si tu lenguaje no garantiza la optimización, la variante con acumulador no es «la rápida»: es un puente hacia_ite. No le escribas suite; la especificación lo pide así.- En Haskell el
_iteno es un bucle.foldl'yuntilson la forma idiomática de recorrer de forma iterativa; el módulo acepta combinadores usados explícitamente como iteración. - En Elixir no hay una versión iterativa explícita, y no es un defecto: la forma idiomática de iterar es la recursión con acumulador.
fibonacci_reces exponencial a propósito. Es el coste que el módulo quiere que se vea; no lo «optimices».- El tamaño del entero importa menos de lo que parece:
intde 32 bits alcanza para los 11 casos en todos los lenguajes, aunque Python, Haskell o Raku trabajen con precisión arbitraria. - La combinación de suites la declara tu README, no esta guía. Si dudas de si tu lenguaje tiene TCO, la respuesta está en la especificación del lenguaje, no en el compilador que tengas instalado.
6. Dónde seguir / Where next
| Capa / Layer | Documento / Document |
|---|---|
| Qué se exige / What is required | 04_Numbers.md |
| Las familias en general / Families in general | PARADIGM_FAMILIES.md |
| Qué obliga tu lenguaje / What your language mandates | idioms/{lang}.md |
| Qué se verificó en los 50 / What was verified across the 50 | 04_Audited_Numbers.md |