âŹ ïž Volver a la clase 161 · đ Atlas de familias · đ Ăndice
Esta pĂĄgina lleva la tesis del programa hasta el final: aprende el representante, reconoce la familia entera. El mismo problema de la clase âconsumir los mensajes de una cola y sumarlosâ resuelto por los primos de cada familia del Atlas, no solo por los diez lenguajes del nĂșcleo.
Si entendiste la versiĂłn de Python, la de Ruby te resultarĂĄ familiar aunque no la hayas visto nunca. Ese reconocimiento es exactamente lo que este curso quiere producir.
â ïž QuĂ© estĂĄ verificado y quĂ© no. Ruby, Perl y Lua se ejecutan en CI contra el mismo
casos.jsonque el nĂșcleo, igual que las diez implementaciones de la clase (workflow Labs). Los otros 17 primos son material de lectura: su toolchain no estĂĄ en el workflow, asĂ que estĂĄn escritos para ser correctos pero sin el sello de la mĂĄquina. Verificar tres de veinte no es verificarlos todos.
recibido=<suma de los mensajes>| stdin | esperado |
|---|---|
1 2 3 |
recibido=6 |
5 |
recibido=5 |
10 20 30 40 |
recibido=100 |
Representantes del nĂșcleo: Python · PHP. Los lenguajes del pegamento entre procesos: nacidos para leer de una tuberĂa y escribir en otra.
cola = STDIN.read.split.map(&:to_i)
puts "recibido=#{cola.sum}"
use List::Util qw(sum0);
my @cola = split ' ', <STDIN>;
printf "recibido=%d\n", sum0(@cola);
-- Lua no trae sockets en su biblioteca estĂĄndar (harĂa falta LuaSocket),
-- pero io.read sobre stdin sĂ es parte del nĂșcleo.
local recibido = 0
for m in io.read("l"):gmatch("%-?%d+") do
recibido = recibido + tonumber(m)
end
print("recibido=" .. recibido)
# Tcl trae `socket` en el nĂșcleo del lenguaje; aquĂ basta el canal stdin,
# que es un canal mĂĄs, indistinguible de un socket para el resto del cĂłdigo.
gets stdin linea
set recibido 0
foreach m [split [string trim $linea]] {
incr recibido $m
}
puts "recibido=$recibido"
cola <- scan("stdin", what = integer(), quiet = TRUE)
cat(sprintf("recibido=%d\n", sum(cola)))
QuĂ© reconocer: los cinco leen la cola igual âuna lĂnea de stdin, partir, sumarâ y ahĂ acaba el
parecido. En sockets la familia se rompe: Ruby trae TCPServer y Perl IO::Socket en su
distribuciĂłn, Lua no trae ninguno y necesita LuaSocket, y R los abre con socketConnection pero
de forma bloqueante y sin bucle de eventos. Tcl es el mĂĄs elegante de la tanda: socket estĂĄ en
el nĂșcleo del lenguaje, devuelve un canal que se lee con el mismo gets que stdin, y fileevent
mĂĄs vwait dan un servidor concurrente sin hilos ni bibliotecas. Un programa Tcl puede cambiar de
tuberĂa a red sustituyendo una lĂnea.
Representantes del nĂșcleo: JavaScript · TypeScript. Familia del bucle de eventos: la cola de mensajes no es una biblioteca, es el modelo de ejecuciĂłn.
import 'dart:io';
void main() {
final cola = stdin.readLineSync()!.trim().split(RegExp(r'\s+')).map(int.parse);
final recibido = cola.fold<int>(0, (a, b) => a + b);
print('recibido=$recibido');
}
// ActionScript corre en el reproductor Flash: no tiene stdin ni procesos hijos,
// solo flash.net.Socket. Se ilustra el consumo de la cola ya recibida.
package {
public class Cola {
public static function recibir(mensajes:Array):String {
var recibido:int = 0;
for each (var m:int in mensajes) {
recibido += m;
}
return "recibido=" + recibido;
}
}
}
QuĂ© reconocer: ActionScript marca el lĂmite duro de esta clase: no tiene stdin ni puede lanzar
procesos, porque el reproductor Flash era una caja aislada; lo Ășnico que le queda es
flash.net.Socket, y aun asĂ solo hacia servidores que publiquen una polĂtica de seguridad. Dart es
lo contrario: dart:io trae stdin, Process.start y ServerSocket, y añade los isolates, que
se comunican por paso de mensajes con SendPort âuna cola real dentro del proceso, sin memoria
compartida, igual que si fueran procesos separados. Es el mismo modelo de los Web Workers de
JavaScript, con la misma consecuencia: lo que cruza la frontera se copia.
Representante del nĂșcleo: Java. Hilos con memoria compartida, y sobre ellos todas las abstracciones de cola que existen.
fun main() {
val recibido = readLine()!!.trim().split(Regex("\\s+")).sumOf { it.toInt() }
println("recibido=$recibido")
}
object Cola extends App {
val recibido = scala.io.StdIn.readLine().trim.split("\\s+").map(_.toInt).sum
println(s"recibido=$recibido")
}
def recibido = System.in.newReader().readLine().trim().split(/\s+/)*.toInteger().sum()
println "recibido=$recibido"
(require '[clojure.string :as str])
(let [cola (map parse-long (str/split (str/trim (read-line)) #"\s+"))]
(println (str "recibido=" (reduce + cola))))
Qué reconocer: los cuatro heredan de Java el mismo System.in, el mismo ProcessBuilder para
lanzar procesos hijos y la misma BlockingQueue para las colas dentro del proceso. Lo que cambia es
la abstracciĂłn que cada comunidad pone encima: Kotlin usa corrutinas y Channel, Scala el modelo de
actores de Akka âdonde cada actor tiene su propio buzĂłn y nada se comparteâ, y Clojure core.async,
que copia los canales de Go dentro de la JVM. Cuatro vocabularios distintos para la misma idea de
esta clase: un productor deposita, un consumidor retira, y la cola desacopla su velocidad.
Groovy es el que menos ceremonia pone para lo bĂĄsico, y por eso vive en los scripts de despliegue.
Representante del nĂșcleo: C#.
let recibido =
stdin.ReadLine().Split(' ', System.StringSplitOptions.RemoveEmptyEntries)
|> Array.sumBy int
printfn "recibido=%d" recibido
Imports System
Module Cola
Sub Main()
Dim cola = Console.ReadLine().Split(New Char() {" "c}, StringSplitOptions.RemoveEmptyEntries)
Dim recibido = 0
For Each m In cola
recibido += Integer.Parse(m)
Next
Console.WriteLine($"recibido={recibido}")
End Sub
End Module
Qué reconocer: los tres comparten Console.In, Process con
RedirectStandardOutput = True para hablar con un hijo por tuberĂas, y
System.Threading.Channels para la cola en memoria. F# añade lo mås parecido a Erlang que hay en el
CLR: el MailboxProcessor, un agente con buzĂłn propio que se lee con Receive() en un bucle
asĂncrono, sin bloqueo alguno ni cerrojos. VB.NET no tiene nada equivalente en su sintaxis y usa las
mismas clases con For Each, lo que deja claro que en esta plataforma la concurrencia vive en la
biblioteca, no en el lenguaje.
Representante del nĂșcleo: C. La capa donde stdin, socket y tuberĂa son lo mismo: un descriptor de fichero.
#include <iostream>
int main() {
long long recibido = 0, m;
while (std::cin >> m) {
recibido += m;
}
std::cout << "recibido=" << recibido << '\n';
}
#import <Foundation/Foundation.h>
int main(void) {
@autoreleasepool {
long long recibido = 0, m;
while (scanf("%lld", &m) == 1) {
recibido += m;
}
printf("recibido=%lld\n", recibido);
}
return 0;
}
Qué reconocer: en esta familia se ve el mecanismo desnudo: fork duplica el proceso, pipe crea
el par de descriptores y exec reemplaza el programa, que es literalmente lo que hacen por dentro
subprocess de Python y ProcessBuilder de Java. C++ envuelve el descriptor en un flujo âstd::cin
es el mismo stdin de C con otra caraâ y desde C++11 añade std::thread y colas propias, pero sin
socket en la biblioteca estĂĄndar. Objective-C sĂ lo trae todo en Foundation: NSTask para el proceso
hijo, NSPipe para el canal y NSRunLoop para atenderlos sin bloquear, un bucle de eventos muy
parecido al de Tcl. Es la misma idea, empaquetada por Apple.
Representantes del nĂșcleo: Go · Rust. Nativos, sin runtime pesado, con acceso directo a las llamadas al sistema.
const std = @import("std");
pub fn main() !void {
var buf: [256]u8 = undefined;
const linea = (try std.io.getStdIn().reader().readUntilDelimiterOrEof(&buf, '\n')).?;
var it = std.mem.tokenizeAny(u8, linea, " \r\t");
var recibido: i64 = 0;
while (it.next()) |m| {
recibido += try std.fmt.parseInt(i64, m, 10);
}
try std.io.getStdOut().writer().print("recibido={d}\n", .{recibido});
}
import std/[strutils, sequtils, math]
let cola = stdin.readLine().splitWhitespace().map(parseInt)
echo "recibido=", cola.sum()
import std.stdio, std.array, std.string, std.conv, std.algorithm;
void main() {
auto cola = readln().strip().split().map!(to!long);
writefln("recibido=%d", cola.sum());
}
Qué reconocer: los tres traen en la biblioteca eståndar lo que C deja al sistema operativo:
std.process.Child en Zig, std/osproc en Nim y std.process en D lanzan un hijo y capturan su
salida en tres lĂneas, sin tocar fork. Y los tres traen serializador JSON incluido, asĂ que pueden
poner un mensaje estructurado en la cola sin dependencias. En sockets se separan de Go y Rust: Zig
expone std.net a bajo nivel y sin abstracciĂłn de concurrencia, mientras D ofrece paso de mensajes
entre hilos con std.concurrency âsend y receive sobre buzones, con los datos aislados por el
sistema de tipos, la misma renuncia a la memoria compartida que hace Rust.
Representante del nĂșcleo: SQL. Describir el resultado, no el recorrido: sumar la cola es una agregaciĂłn, no un bucle.
:- initialization(main, main).
main :-
read_line_to_string(user_input, Linea),
split_string(Linea, " ", " ", Partes),
maplist([S, N]>>number_string(N, S), Partes, Cola),
sum_list(Cola, Recibido),
format("recibido=~w~n", [Recibido]).
% Datalog puro no tiene E/S, procesos ni agregaciĂłn: los mensajes se declaran
% como hechos. Sumarlos exige un dialecto con agregados, como Soufflé:
% recibido(t) :- t = sum v : { mensaje(_, v) }.
mensaje(1, 1).
mensaje(2, 2).
mensaje(3, 3).
QuĂ© reconocer: Prolog sĂ puede hablar con el mundo âread_line_to_string/2 sobre user_input,
y SWI-Prolog trae process_create/3 y socketsâ, pero la suma se expresa como sum_list/2 sobre la
lista completa, no como un acumulador que se reasigna en un bucle. Datalog no puede ni eso: sin E/S,
sin efectos y sin agregaciĂłn en el nĂșcleo, los mensajes solo existen como hechos declarados, y la
suma requiere salirse a un dialecto como Soufflé. Es la misma renuncia de SQL, que agrega con sum()
y no te deja decidir en qué orden se recorren las filas.
Veinte lenguajes, un solo problema, y una conclusiĂłn que vale mĂĄs que el cĂłdigo: los mecanismos de comunicaciĂłn difieren enormemente âTcl tiene sockets en el nĂșcleo, Lua no tiene ninguno, ActionScript ni siquiera puede lanzar un procesoâ, pero stdin/stdout es el Ășnico mecanismo que todos comparten. Por eso el verificador de equivalencia de este curso estĂĄ construido sobre Ă©l: es el mĂnimo comĂșn denominador de la interoperabilidad, y el Ășnico contrato que ningĂșn lenguaje se niega a cumplir. Eso es lo transferible.
âźïž Volver a la clase 161 · đ Ver las familias en el Atlas