Parte 11 — Proyecto integrador políglota · ⏱️ Duración estimada: 90 min · Nivel: Intermedio ✅ Clase construida — 10 implementaciones del núcleo verificadas contra
casos.json.
Construimos el componente de datos: la capa que almacena la información y responde consultas. Es la
fuente de verdad del sistema, y su lenguaje natural es SQL. Hoy implementamos la operación que mejor lo
caracteriza: la agregación, resumir muchos valores en uno (aquí, sumarlos), como hace un SELECT
SUM(...) sobre miles de filas.
Este componente encierra la diferencia paradigmática más profunda de todo el curso, y por eso merece atención. Los otros nueve lenguajes del núcleo son imperativos: le dices a la máquina cómo hacer las cosas, paso a paso. SQL es declarativo: describes qué quieres y el motor decide cómo obtenerlo. C. J. Date, en SQL and Relational Theory, insiste en que esta no es una comodidad sintáctica sino un cambio de modelo mental heredado del álgebra relacional: piensas en conjuntos, no en bucles. Entender la capa de datos es entender que a veces el mejor código es el que no escribes, porque delegas el "cómo" a un motor que optimiza mejor que tú.
Al finalizar, podrás:
| # | Tema | Por qué importa |
|---|---|---|
| 1 | Capa de datos | Persiste y consulta: fuente de verdad |
| 2 | Agregación | Resume muchas filas en un valor |
| 3 | Consulta declarativa | Describes el qué; el motor resuelve el cómo |
El componente de datos es la capa que almacena y consulta la información; es la fuente de verdad del
sistema, donde vive el estado que debe sobrevivir a todo lo demás. La agregación combina muchas filas en
un solo valor (SUM, AVG, COUNT): es el resumen de datos por excelencia. Una consulta declarativa
describe qué datos se quieren, no cómo obtenerlos, y es el rasgo distintivo de SQL.
La consecuencia práctica del modelo declarativo es enorme. Cuando escribes SELECT SUM(x) FROM ventas, no
dices "abre un cursor, recorre las filas, acumula": dices "quiero la suma", y el motor elige el plan de
ejecución —usar un índice, paralelizar, ordenar por lotes— según los datos que tenga. Newman, al hablar de
la propiedad de los datos en Building Microservices, subraya que cada servicio debe ser dueño de su
almacén y exponerlo solo por su API; la capa de datos no es un cajón compartido sino un componente con
fronteras, y SQL es cómo se le pregunta. Delegar la agregación a la base de datos —en vez de traer todas
las filas al backend y sumarlas allí— es una de las decisiones de rendimiento más rentables que existen.
El backend necesita "el total de ventas del mes". Tiene dos caminos. El malo: pedir a la base de datos las
cien mil filas, traerlas por la red al servicio y sumarlas en un bucle. El bueno: enviar SELECT SUM(monto)
FROM ventas WHERE mes = ? y recibir un solo número. El segundo mueve el cálculo a donde están los datos,
aprovecha los índices y transfiere un entero en vez de un torrente de filas. SQL es el lenguaje natural de
este componente precisamente porque fue diseñado para expresar ese "resume allí donde viven los datos" en
una línea declarativa.
total=<suma de los valores>Especificación y verificación en casos.json:
| stdin | esperado |
|---|---|
10 20 30 |
total=60 |
5 |
total=5 |
1 2 3 4 |
total=10 |
LEER valores ; total <- suma ; ESCRIBIR total
Mismo algoritmo, forma idiomática en cada lenguaje. Todas producen la salida de casos.json.
Cada bloque es el archivo real de implementaciones/: el enlace de cada lenguaje abre su fuente, y el comando de al lado lo ejecuta.
python/main.py · python main.pyimport sys
nums = [int(x) for x in sys.stdin.read().split()]
print(f"total={sum(nums)}")
🧬 El mismo programa en la familia Scripting dinámico: Ruby · Perl · Lua · Tcl · R
javascript/main.mjs · node main.mjsimport { readFileSync } from "node:fs";
const nums = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);
console.log(`total=${nums.reduce((a, b) => a + b, 0)}`);
🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript
typescript/main.ts · pnpm exec tsx main.tsimport { readFileSync } from "node:fs";
const nums: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);
console.log(`total=${nums.reduce((a, b) => a + b, 0)}`);
🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript
java/Main.java · java Main.javaimport java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] p = br.readLine().trim().split("\\s+");
long total = 0;
for (String s : p) total += Integer.parseInt(s);
System.out.println("total=" + total);
}
}
🧬 El mismo programa en la familia JVM: Kotlin · Scala · Groovy · Clojure
csharp/Program.cs · dotnet runusing System;
using System.Linq;
long total = Console.In.ReadToEnd()
.Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)
.Sum(x => (long) int.Parse(x));
Console.WriteLine($"total={total}");
🧬 El mismo programa en la familia .NET: F# · VB.NET
go/main.go · go run main.gopackage main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
line, _ := bufio.NewReader(os.Stdin).ReadString('\n')
total := 0
for _, s := range strings.Fields(line) {
n, _ := strconv.Atoi(s)
total += n
}
fmt.Printf("total=%d\n", total)
}
🧬 El mismo programa en la familia Sistemas: Zig · Nim · D
rust/main.rs · rustc main.rs -o main && ./mainuse std::io::Read;
fn main() {
let mut s = String::new();
std::io::stdin().read_to_string(&mut s).unwrap();
let total: i64 = s.split_whitespace().map(|x| x.parse::<i64>().unwrap()).sum();
println!("total={total}");
}
🧬 El mismo programa en la familia Sistemas: Zig · Nim · D
c/main.c · cc main.c -o main && ./main#include <stdio.h>
int main(void) {
long total = 0, x;
while (scanf("%ld", &x) == 1) total += x;
printf("total=%ld\n", total);
return 0;
}
🧬 El mismo programa en la familia C / llaves: C++ · Objective-C
sql/main.sql · sqlite3 :memory: < main.sql-- SQL: agregacion declarativa con SUM.
WITH datos(x) AS (VALUES (10), (20), (30))
SELECT printf('total=%d', sum(x)) AS resultado FROM datos;
🧬 El mismo programa en la familia Lógica y declarativa: Prolog · Datalog
php/main.php · php main.php<?php
$nums = array_map('intval', preg_split('/\s+/', trim(fgets(STDIN))));
echo "total=" . array_sum($nums) . "\n";
🧬 El mismo programa en la familia Scripting dinámico: Ruby · Perl · Lua · Tcl · R
SQL es declarativo: no lee de stdin como los demás; su implementación muestra la misma idea sobre una tabla de casos, y el verificador la marca como ilustrativa.
El contrato (casos.json) es una suma: de 10 20 30 sale total=60; de 5, total=5.
Trivial en aritmética, pero es el caso perfecto para ver la frontera imperativo/declarativo, porque un
lenguaje la expresa de forma radicalmente distinta a los demás.
Los nueve imperativos recorren y acumulan, con más o menos azúcar. Python lo hace casi declarativo:
nums = [int(x) for x in sys.stdin.read().split()]
print(f"total={sum(nums)}")
La comprensión de lista convierte los tokens en enteros y sum() los reduce. No ves el bucle, pero está
ahí: sum itera. C enseña ese bucle sin adornos —while (scanf("%ld", &x) == 1) total += x;—: lee un
número, lo suma, repite. Entre ambos, Go y Java escriben el for explícito acumulando en una
variable. Todos comparten el mismo modelo mental: "empieza en cero, recorre, acumula".
SQL rompe ese molde:
WITH datos(x) AS (VALUES (10), (20), (30))
SELECT printf('total=%d', sum(x)) AS resultado FROM datos;
Aquí no hay variable acumuladora, ni bucle, ni orden. sum(x) es una función de agregación que opera sobre
todo el conjunto datos a la vez; tú declaras "quiero la suma de la columna x" y el motor decide cómo
recorrerla. No lee de stdin como los otros —su entrada es la tabla—, por eso el verificador la marca como
ilustrativa. Esa es la lección de Date hecha código: nueve lenguajes te dan el "cómo" de la suma; SQL te
da solo el "qué", y ese salto es la diferencia paradigmática que este componente encarna.
Sumar un conjunto es donde la distancia entre "decir cómo" y "decir qué" se ve con más claridad.
| Clase de diferencia | Observación entre lenguajes |
|---|---|
| Sintáctica | sum() (Python), reduce (JS), .Sum() (C#/LINQ), for (Go/Java), while (C): variaciones de acumular. |
| Semántica | Los imperativos mantienen una variable acumuladora y un orden de recorrido; SQL opera sobre el conjunto sin orden explícito. |
| Paradigmática | La diferencia central del curso: nueve lenguajes describen el cómo (recorrer y acumular); SQL declara el qué (SELECT SUM(x)) y delega el cómo al motor. |
Nota que C# con LINQ (.Sum(x => ...)) es un puente: trae el estilo declarativo al mundo imperativo. Ese
préstamo de ideas entre familias —tomar el "describe el qué" de SQL/funcional y llevarlo a C#— es la razón
por la que comparar lenguajes acelera el aprendizaje en vez de dispersarlo.
Las bases de datos relacionales (PostgreSQL, SQLite, MySQL) y su SQL dominan este componente, pero la idea
de "agregación declarativa sobre un conjunto" reaparece en las streams de Java, LINQ en C#, reduce en
JS y las comprensiones de Python. Reconocer que todas son la misma operación —resumir una colección— es
transferir el concepto entre familias imperativa y declarativa.
Los mismos casos para todas las implementaciones: casos.json. Verifica la equivalencia:
python scripts/verificar_equivalencia.py 170
Detalle en reto.md.
SUM/AVG/GROUP BY.reduce (JS) y las comprensiones (Python) traen su espíritu al mundo imperativo.Libros de la parte:
Libros de los lenguajes del núcleo:
⏮️ Clase 169 · 📂 Parte · 📚 Índice · 🌐 Atlas · Clase 171 ⏭️