🧬 El mismo programa en las familias de lenguajes — Clase 061

⬅️ Volver a la clase 061 · 🌐 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 —el nombre del día a partir de su número— 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.json que 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.

El contrato, igual para todos

stdin esperado
1 dia=lunes
6 dia=sabado
8 dia=invalido

Scripting dinámico

Representantes del núcleo: Python · PHP. Aquí se ve algo que la clase no puede enseñar con un solo lenguaje: la mitad de esta familia no tiene switch y resuelve la selección por valor con una tabla asociativa.

Ruby

d = STDIN.gets.to_i
nombre = case d
         when 1 then "lunes"
         when 2 then "martes"
         when 3 then "miercoles"
         when 4 then "jueves"
         when 5 then "viernes"
         when 6 then "sabado"
         when 7 then "domingo"
         else "invalido"
         end
puts "dia=#{nombre}"

Perl

# Perl no tiene switch: given/when fue experimental y acabó retirado.
# El idioma de la comunidad para seleccionar por valor es la tabla hash.
my $d = <STDIN>;
chomp $d;
my %dias = (1 => 'lunes',   2 => 'martes',  3 => 'miercoles', 4 => 'jueves',
            5 => 'viernes', 6 => 'sabado',  7 => 'domingo');
printf "dia=%s\n", $dias{$d} // 'invalido';

Lua

-- Lua tampoco tiene switch: la tabla indexada por posición hace el trabajo.
local d = tonumber(io.read("l"))
local dias = { "lunes", "martes", "miercoles", "jueves", "viernes", "sabado", "domingo" }
print("dia=" .. (dias[d] or "invalido"))

Tcl

gets stdin d
switch -- $d {
    1 { set nombre lunes }
    2 { set nombre martes }
    3 { set nombre miercoles }
    4 { set nombre jueves }
    5 { set nombre viernes }
    6 { set nombre sabado }
    7 { set nombre domingo }
    default { set nombre invalido }
}
puts "dia=$nombre"

R

d <- as.integer(readLines("stdin", n = 1))
# switch() con un entero selecciona por POSICIÓN y no admite rama por defecto:
# cuando el índice se sale del rango devuelve NULL, y el default se pone a mano.
nombre <- switch(d, "lunes", "martes", "miercoles", "jueves",
                 "viernes", "sabado", "domingo")
if (is.null(nombre)) nombre <- "invalido"
cat(sprintf("dia=%s\n", nombre))

Qué reconocer: solo Ruby y Tcl traen una construcción de selección múltiple de verdad, y son expresiones o comandos, no la sentencia de C: el case de Ruby devuelve un valor y no hay break ni caída en cascada. Perl y Lua declaran la ausencia con toda claridad —la tabla asociativa es su switch— y de paso muestran por qué el fallthrough nunca les hizo falta. R es el caso más extraño: su switch es una función y, con índice numérico, elige por posición y devuelve NULL cuando no encaja.


JavaScript / web

Representantes del núcleo: JavaScript · TypeScript.

Dart

import 'dart:io';

void main() {
  final d = int.parse(stdin.readLineSync()!.trim());
  final nombre = switch (d) {
    1 => 'lunes',
    2 => 'martes',
    3 => 'miercoles',
    4 => 'jueves',
    5 => 'viernes',
    6 => 'sabado',
    7 => 'domingo',
    _ => 'invalido',
  };
  print('dia=$nombre');
}

ActionScript 3

// ActionScript corre en el reproductor Flash, sin stdin: se ilustra la selección.
// Su switch es el de C, con case/break y caída en cascada si se omite el break.
package {
    public class Dia {
        public static function nombre(d:int):String {
            switch (d) {
                case 1: return "dia=lunes";
                case 2: return "dia=martes";
                case 3: return "dia=miercoles";
                case 4: return "dia=jueves";
                case 5: return "dia=viernes";
                case 6: return "dia=sabado";
                case 7: return "dia=domingo";
                default: return "dia=invalido";
            }
        }
    }
}

Qué reconocer: ActionScript conserva intacto el switch heredado de C —el mismo que sigue vivo en JavaScript, con break obligatorio y fallthrough implícito—. Dart parte de ahí y en su versión 3 añade la expresión switch: sin break, con _ como comodín y devolviendo un valor. Es el mismo movimiento que hicieron C# y Java con sus switch de flecha.


JVM

Representante del núcleo: Java. Todos compilan al mismo bytecode; lo que cambia es cuánta ceremonia exigen y si la selección es sentencia o expresión.

Kotlin

fun main() {
    val nombre = when (readLine()!!.trim().toInt()) {
        1 -> "lunes"
        2 -> "martes"
        3 -> "miercoles"
        4 -> "jueves"
        5 -> "viernes"
        6 -> "sabado"
        7 -> "domingo"
        else -> "invalido"
    }
    println("dia=$nombre")
}

Scala

object Dia extends App {
  val nombre = scala.io.StdIn.readInt() match {
    case 1 => "lunes"
    case 2 => "martes"
    case 3 => "miercoles"
    case 4 => "jueves"
    case 5 => "viernes"
    case 6 => "sabado"
    case 7 => "domingo"
    case _ => "invalido"
  }
  println(s"dia=$nombre")
}

Groovy

def d = System.in.newReader().readLine().trim() as int
def nombre
switch (d) {
    case 1: nombre = 'lunes'; break
    case 2: nombre = 'martes'; break
    case 3: nombre = 'miercoles'; break
    case 4: nombre = 'jueves'; break
    case 5: nombre = 'viernes'; break
    case 6: nombre = 'sabado'; break
    case 7: nombre = 'domingo'; break
    default: nombre = 'invalido'
}
println "dia=$nombre"

Clojure

(let [d (Integer/parseInt (.trim (read-line)))
      nombre (case d
               1 "lunes"
               2 "martes"
               3 "miercoles"
               4 "jueves"
               5 "viernes"
               6 "sabado"
               7 "domingo"
               "invalido")]
  (println (str "dia=" nombre)))

Qué reconocer: Groovy mantiene la forma de Java —case, break, caída si lo olvidas— pero compara con isCase, no con ==, así que un case puede ser una clase, una expresión regular o un rango. Kotlin (when), Scala (match) y Clojure (case) eliminan el break de raíz: sin sentencia no hay caída posible, y el resultado es un valor que se asigna directamente. En Clojure la última forma suelta es la rama por defecto, y case exige constantes en tiempo de compilación: es un salto por tabla, no una cadena de comparaciones.


.NET

Representante del núcleo: C#.

F#

let nombre =
    match int (stdin.ReadLine().Trim()) with
    | 1 -> "lunes"
    | 2 -> "martes"
    | 3 -> "miercoles"
    | 4 -> "jueves"
    | 5 -> "viernes"
    | 6 -> "sabado"
    | 7 -> "domingo"
    | _ -> "invalido"
printfn "dia=%s" nombre

VB.NET

Module Dia
    Sub Main()
        Dim d = Integer.Parse(Console.ReadLine().Trim())
        Dim nombre As String
        Select Case d
            Case 1 : nombre = "lunes"
            Case 2 : nombre = "martes"
            Case 3 : nombre = "miercoles"
            Case 4 : nombre = "jueves"
            Case 5 : nombre = "viernes"
            Case 6 : nombre = "sabado"
            Case 7 : nombre = "domingo"
            Case Else : nombre = "invalido"
        End Select
        Console.WriteLine("dia=" & nombre)
    End Sub
End Module

Qué reconocer: los tres corren sobre el CLR, pero ninguno de los dos primos tiene fallthrough. VB.NET nunca lo tuvo: cada Case termina solo, y por eso no existe break en su Select Case —precisamente el error que C obliga a recordar—. F# va más lejos y hace del match la construcción central del lenguaje: el compilador avisa si dejas casos sin cubrir, algo que un switch de C jamás comprueba.


C / llaves

Representante del núcleo: C. Aquí nació el switch con case, break y caída en cascada que el resto de familias heredó o corrigió.

C++

#include <iostream>
#include <string_view>

int main() {
    int d;
    std::cin >> d;
    std::string_view nombre;
    switch (d) {
        case 1: nombre = "lunes"; break;
        case 2: nombre = "martes"; break;
        case 3: nombre = "miercoles"; break;
        case 4: nombre = "jueves"; break;
        case 5: nombre = "viernes"; break;
        case 6: nombre = "sabado"; break;
        case 7: nombre = "domingo"; break;
        default: nombre = "invalido"; break;
    }
    std::cout << "dia=" << nombre << '\n';
}

Objective-C

#import <Foundation/Foundation.h>

int main(void) {
    @autoreleasepool {
        int d;
        scanf("%d", &d);
        NSString *nombre;
        switch (d) {
            case 1: nombre = @"lunes"; break;
            case 2: nombre = @"martes"; break;
            case 3: nombre = @"miercoles"; break;
            case 4: nombre = @"jueves"; break;
            case 5: nombre = @"viernes"; break;
            case 6: nombre = @"sabado"; break;
            case 7: nombre = @"domingo"; break;
            default: nombre = @"invalido"; break;
        }
        printf("dia=%s\n", nombre.UTF8String);
    }
    return 0;
}

Qué reconocer: ambos son superconjuntos de C y su switch es literalmente el mismo: solo acepta enteros y enumerados, exige break en cada rama y cae a la siguiente si lo omites. Lo único que cambia es el tipo del valor guardado —std::string_view frente a NSString *— y cómo se imprime. C++ moderno añade [[fallthrough]] para declarar que la caída es intencionada; sin esa marca, el compilador puede avisarte de que probablemente sea un olvido.


Sistemas

Representantes del núcleo: Go · Rust. Compilados, sin máquina virtual, y con una postura muy explícita sobre el fallthrough: lo consideran un error de diseño.

Zig

const std = @import("std");

pub fn main() !void {
    var buf: [32]u8 = undefined;
    const linea = (try std.io.getStdIn().reader().readUntilDelimiterOrEof(&buf, '\n')).?;
    const d = try std.fmt.parseInt(i32, std.mem.trim(u8, linea, " \r"), 10);
    const nombre = switch (d) {
        1 => "lunes",
        2 => "martes",
        3 => "miercoles",
        4 => "jueves",
        5 => "viernes",
        6 => "sabado",
        7 => "domingo",
        else => "invalido",
    };
    try std.io.getStdOut().writer().print("dia={s}\n", .{nombre});
}

Nim

import std/strutils

let d = stdin.readLine().strip().parseInt()
let nombre =
  case d
  of 1: "lunes"
  of 2: "martes"
  of 3: "miercoles"
  of 4: "jueves"
  of 5: "viernes"
  of 6: "sabado"
  of 7: "domingo"
  else: "invalido"
echo "dia=", nombre

D

import std.stdio, std.conv, std.string;

void main() {
    const d = readln().strip().to!int;
    string nombre;
    switch (d) {
        case 1: nombre = "lunes"; break;
        case 2: nombre = "martes"; break;
        case 3: nombre = "miercoles"; break;
        case 4: nombre = "jueves"; break;
        case 5: nombre = "viernes"; break;
        case 6: nombre = "sabado"; break;
        case 7: nombre = "domingo"; break;
        default: nombre = "invalido"; break;
    }
    writefln("dia=%s", nombre);
}

Qué reconocer: Zig y Nim convierten la selección en expresión exhaustiva: no hay break, cada rama produce un valor y el compilador exige que cubras todos los casos o escribas else. D conserva la sintaxis de C pero corrige su trampa: la caída implícita entre case es un error de compilación, y si de verdad la quieres tienes que escribir goto case. Es exactamente la misma decisión que tomó Go al hacer el fallthrough explícito.


Lógica y declarativa

Representante del núcleo: SQL. No hay switch porque no hay flujo de control: se declara la correspondencia y el motor la resuelve.

Prolog

:- initialization(main, main).

dia(1, "lunes").
dia(2, "martes").
dia(3, "miercoles").
dia(4, "jueves").
dia(5, "viernes").
dia(6, "sabado").
dia(7, "domingo").

nombre(D, N) :- dia(D, N), !.
nombre(_, "invalido").

main :-
    read_line_to_string(user_input, Linea),
    number_string(D, Linea),
    nombre(D, N),
    format("dia=~w~n", [N]).

Datalog

% Datalog no tiene E/S: la entrada se declara como un hecho.
% La rama por defecto necesita negación estratificada (Datalog¬),
% porque en Datalog puro no se puede decir "si NO existe correspondencia".
dia(1, "lunes").
dia(2, "martes").
dia(3, "miercoles").
dia(4, "jueves").
dia(5, "viernes").
dia(6, "sabado").
dia(7, "domingo").

entrada(8).

conocido(D) :- dia(D, _).
resultado(N) :- entrada(D), dia(D, N).
resultado("invalido") :- entrada(D), not conocido(D).

Qué reconocer: en Prolog las siete ramas del switch son siete hechos, y la selección la hace la unificación de la cabeza de cláusula, no una sentencia. El ! (corte) cumple el papel del break: impide que el motor pruebe también la cláusula por defecto tras haber encontrado un día válido; sin él, nombre/2 daría dos soluciones por backtracking. Datalog lleva la renuncia al extremo —solo hechos y reglas— y deja al descubierto que un "caso por defecto" es, en lógica, una negación: algo que el Datalog puro ni siquiera puede expresar.


Y de vuelta a la clase

Veinte lenguajes, un solo problema, y tres respuestas distintas a la misma pregunta: la caída en cascada de C, la expresión exhaustiva de Kotlin, Zig o F#, y la ausencia total de switch en Perl, Lua o Prolog, donde una tabla o unos hechos hacen el mismo trabajo. Reconocer cuál de las tres tienes delante es lo transferible.

⏮️ Volver a la clase 061 · 🌐 Ver las familias en el Atlas