Esta página lleva la tesis del programa hasta el final: aprende el representante, reconoce la familia entera. El mismo problema de la clase —clasificar el signo de un entero— resuelto por los primos de cada familia del Atlas, no solo por los diez lenguajes del núcleo.
Si entendiste la versión de Rust, la de Scala 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.
nsigno=<positivo|negativo|cero>n > 0 → positivo; n < 0 → negativo; n == 0 → cero| stdin | esperado |
|---|---|
5 |
signo=positivo |
-3 |
signo=negativo |
0 |
signo=cero |
Representantes del núcleo: Python · PHP. Es la familia donde más se nota la diferencia: la coincidencia de patrones llegó tarde (o no llegó), y cada lenguaje improvisó su propia solución con las piezas que ya tenía.
n = STDIN.gets.to_i
signo = case n
when 1.. then "positivo"
when ..-1 then "negativo"
else "cero"
end
puts "signo=#{signo}"
# Perl no tiene coincidencia de patrones sobre valores.
# El idioma de la comunidad es el operador de comparación numérica <=>,
# que devuelve exactamente -1, 0 o 1: el signo, ya clasificado.
my $n = <STDIN>;
chomp $n;
my %signo = (1 => 'positivo', -1 => 'negativo', 0 => 'cero');
printf "signo=%s\n", $signo{$n <=> 0};
-- En Lua "patrón" significa patrón de texto (string.match): no existe
-- coincidencia estructural sobre valores. La cadena de if es lo idiomático.
local n = tonumber(io.read("l"))
local signo
if n > 0 then
signo = "positivo"
elseif n < 0 then
signo = "negativo"
else
signo = "cero"
end
print("signo=" .. signo)
# El switch de Tcl compara CADENAS (exacto, glob o regexp): no sabe de rangos
# numéricos, así que la clasificación se hace con expr.
gets stdin n
set signo [expr {$n > 0 ? "positivo" : ($n < 0 ? "negativo" : "cero")}]
puts "signo=$signo"
n <- as.integer(readLines("stdin", n = 1))
# sign() reduce el valor a -1, 0 o 1, y switch() sobre una cadena
# sí admite ramas con nombre.
signo <- switch(as.character(sign(n)),
"1" = "positivo",
"-1" = "negativo",
"0" = "cero")
cat(sprintf("signo=%s\n", signo))
Qué reconocer: de los cinco, solo Ruby distingue por forma y no por comparación: sus when
son rangos (1.., ..-1) y el case los prueba con el operador ===, el mismo mecanismo que
permitiría poner ahí una clase o una expresión regular. Los otros cuatro no tienen coincidencia de
patrones y lo confiesan con distintos disfraces: Perl y R normalizan primero el valor a −1/0/1 y
luego consultan una tabla; Lua encadena if; Tcl usa el operador ternario porque su switch solo
sabe de cadenas. Cuando un lenguaje carece del concepto, el problema no desaparece: se desplaza.
Representantes del núcleo: JavaScript · TypeScript.
import 'dart:io';
void main() {
final n = int.parse(stdin.readLineSync()!.trim());
final signo = switch (n) {
> 0 => 'positivo',
< 0 => 'negativo',
_ => 'cero',
};
print('signo=$signo');
}
// ActionScript no tiene stdin ni coincidencia de patrones.
// El truco clásico de la familia es switch(true): cada case es una condición.
package {
public class Signo {
public static function de(n:int):String {
switch (true) {
case n > 0: return "signo=positivo";
case n < 0: return "signo=negativo";
default: return "signo=cero";
}
}
}
}
Qué reconocer: ActionScript enseña el estado en el que sigue JavaScript: sin patrones, y con el
apaño de switch (true) para meter condiciones donde el lenguaje solo espera valores. Dart 3 es la
otra mitad de la historia —añadió patrones relacionales (> 0, < 0) y comodín _ dentro de
una expresión switch—, y es exactamente la propuesta que JavaScript lleva años discutiendo.
Representante del núcleo: Java. Java añadió patrones tarde; sus primos llevaban una década con ellos.
fun main() {
val n = readLine()!!.trim().toInt()
val signo = when {
n > 0 -> "positivo"
n < 0 -> "negativo"
else -> "cero"
}
println("signo=$signo")
}
object Signo extends App {
val signo = scala.io.StdIn.readInt() match {
case n if n > 0 => "positivo"
case n if n < 0 => "negativo"
case _ => "cero"
}
println(s"signo=$signo")
}
def n = System.in.newReader().readLine().trim() as int
def signo
switch (n) {
case { it > 0 }: signo = 'positivo'; break
case { it < 0 }: signo = 'negativo'; break
default: signo = 'cero'
}
println "signo=$signo"
(let [n (Integer/parseInt (.trim (read-line)))
signo (cond
(pos? n) "positivo"
(neg? n) "negativo"
:else "cero")]
(println (str "signo=" signo)))
Qué reconocer: Scala es el que tiene coincidencia de patrones de verdad: el case n if n > 0
liga el valor a un nombre y le aplica una guarda, y el mismo match sabría descomponer una tupla,
una lista o una case class por su estructura. Kotlin recorta: su when sin sujeto es una cadena de
condiciones bien vestida, no un desestructurador. Groovy aprovecha que su case usa isCase, así
que una clausura funciona como patrón. Clojure elige cond en vez de case porque case
compara constantes por igualdad y aquí hacen falta comparaciones; su coincidencia estructural vive
aparte, en core.match y en la desestructuración de let.
Representante del núcleo: C#.
let signo =
match int (stdin.ReadLine().Trim()) with
| n when n > 0 -> "positivo"
| n when n < 0 -> "negativo"
| _ -> "cero"
printfn "signo=%s" signo
Module Signo
Sub Main()
Dim n = Integer.Parse(Console.ReadLine().Trim())
Dim signo As String
Select Case n
Case Is > 0 : signo = "positivo"
Case Is < 0 : signo = "negativo"
Case Else : signo = "cero"
End Select
Console.WriteLine("signo=" & signo)
End Sub
End Module
Qué reconocer: el match de F# es el original del que C# copió sus pattern matching
expressions: mismo when para las guardas, mismo _ como comodín, y un compilador que avisa si el
conjunto de casos no es exhaustivo. VB.NET no tiene patrones, pero su Select Case sí acepta
relaciones y rangos —Case Is > 0, Case 1 To 10—, algo que el switch de C nunca permitió.
Es un punto intermedio interesante: más expresivo que un switch clásico, menos que un match.
Representante del núcleo: C. Aquí no hay patrones, y el switch solo compara
enteros con constantes: para clasificar por rango hay que bajar a los condicionales.
#include <iostream>
int main() {
int n;
std::cin >> n;
const char* signo = (n > 0) ? "positivo"
: (n < 0) ? "negativo"
: "cero";
std::cout << "signo=" << signo << '\n';
}
#import <Foundation/Foundation.h>
int main(void) {
@autoreleasepool {
int n;
scanf("%d", &n);
NSString *signo = n > 0 ? @"positivo" : (n < 0 ? @"negativo" : @"cero");
printf("signo=%s\n", signo.UTF8String);
}
return 0;
}
Qué reconocer: ninguno de los dos puede escribir case > 0, porque el switch de C exige
constantes enteras conocidas en compilación. La respuesta idiomática de la familia es el operador
ternario encadenado, que al menos conserva lo importante: sigue siendo una expresión que produce
un valor. C++ está incorporando la idea por partes —std::visit sobre std::variant, if con
inicializador, propuestas de inspect—, pero a día de hoy la coincidencia de patrones no es parte
del lenguaje.
Representantes del núcleo: Go · Rust. Rust hizo del match
exhaustivo su sello de identidad; sus vecinos toman decisiones distintas.
const std = @import("std");
pub fn main() !void {
var buf: [32]u8 = undefined;
const linea = (try std.io.getStdIn().reader().readUntilDelimiterOrEof(&buf, '\n')).?;
const n = try std.fmt.parseInt(i64, std.mem.trim(u8, linea, " \r"), 10);
// std.math.order devuelve un enum de tres valores: el switch queda
// exhaustivo sin necesidad de else, y el compilador lo comprueba.
const signo = switch (std.math.order(n, 0)) {
.gt => "positivo",
.lt => "negativo",
.eq => "cero",
};
try std.io.getStdOut().writer().print("signo={s}\n", .{signo});
}
import std/strutils
let n = stdin.readLine().strip().parseInt()
let signo =
case n
of low(int) .. -1: "negativo"
of 0: "cero"
else: "positivo"
echo "signo=", signo
import std.stdio, std.conv, std.string, std.math;
void main() {
const n = readln().strip().to!int;
string signo;
// sgn() reduce el valor a -1, 0 o 1 para poder usar case con constantes.
switch (sgn(n)) {
case 1: signo = "positivo"; break;
case -1: signo = "negativo"; break;
default: signo = "cero"; break;
}
writefln("signo=%s", signo);
}
Qué reconocer: Zig y Nim sí distinguen por forma, cada uno a su manera. Zig no usa guardas:
convierte la comparación en un enum de tres valores y deja que el switch sea exhaustivo por
construcción —muy en su estilo de hacer visible lo que otros ocultan—. Nim admite rangos como
patrón (low(int) .. -1), que es más de lo que puede C pero menos que el match de Rust. D vuelve
al truco de normalizar con sgn() porque su switch, heredado de C, quiere constantes.
Representante del núcleo: SQL. Aquí la coincidencia de patrones no es una construcción del lenguaje: es el motor de ejecución entero.
:- initialization(main, main).
signo(N, "positivo") :- N > 0, !.
signo(N, "negativo") :- N < 0, !.
signo(_, "cero").
main :-
read_line_to_string(user_input, Linea),
number_string(N, Linea),
signo(N, S),
format("signo=~w~n", [S]).
% Datalog no tiene E/S: la entrada se declara como un hecho.
% Las comparaciones en el cuerpo son extensión de dialectos prácticos
% (Soufflé, LogiQL); el Datalog puro solo une variables entre relaciones.
entrada(5).
signo("positivo") :- entrada(N), N > 0.
signo("negativo") :- entrada(N), N < 0.
signo("cero") :- entrada(N), N = 0.
Qué reconocer: Prolog es, junto con Scala y F#, el que hace coincidencia de patrones real —y
la hace en la cabeza de la cláusula, antes de entrar en el cuerpo—. Las tres cláusulas de signo/2
se prueban en orden y la unificación decide cuál encaja; los > y < del cuerpo son las guardas, y
el ! evita que el backtracking siga probando las siguientes tras haber encontrado respuesta. Sin
esos cortes, un 5 daría dos soluciones: "positivo" y también "cero". Datalog conserva las tres
reglas pero pierde el orden: las evalúa todas a la vez hasta el punto fijo, así que la
exclusividad tiene que estar en las condiciones, no en la secuencia.
Veinte lenguajes, un solo problema, y una línea divisoria muy nítida: los que distinguen por forma —Scala, F#, Prolog, Dart 3, Rust— y los que solo saben comparar valores y tienen que normalizar antes (Perl, R, D) o encadenar condiciones (Lua, C++, Tcl). Saber de qué lado está el lenguaje que tienes delante es lo transferible.