⬅️ Volver a la clase 060 · 🧬 Primos del Atlas · 🧟 Índice de lenguajes vivos · 📚 Índice
El mayor de dos números. El ejercicio elegido porque la diferencia entre resolverlo con una
sentencia y resolverlo con una expresión se ve en una línea: if decide qué se ejecuta,
mientras que una expresión condicional decide qué valor tiene esto. Y de esa distinción depende que
puedas escribir const en la variable que recibe el resultado.
🎯 Estos lenguajes no están aquí por ser antiguos
El criterio es doble y se declara en la ficha de cada uno: que se ejecute hoy —banca, sanidad, aviónica, ERP, diseño de chips, CAD— y que deje a la vista un concepto que los diez del núcleo esconden.
Aquí el concepto es la diferencia entre sentencia y expresión, y estos lenguajes lo enseñan porque están en los dos bandos. En Lisp y Smalltalk la distinción no existe: todo es una expresión y todo devuelve un valor, así que el condicional siempre ha sido asignable. En COBOL, PL/I y RPG no hay ternario en absoluto: hay que declarar la variable y asignarla dentro de cada rama.
Y en medio, Ada y Fortran ilustran la vía alternativa: en vez de un operador ternario, funciones y atributos —
Integer'Max,max(),merge()— que resuelven el caso concreto sin necesidad de sintaxis condicional.Y ninguno es una foto fija: casi todos han incorporado en los últimos años JSON, REST, GPU, Unicode o Git. Cada ficha lo detalla en su sección
🔄 Lo que se ha modernizado.
a b (dos enteros) → stdout: max=<el mayor>max = (a > b) ? a : b| stdin | esperado |
|---|---|
3 7 |
max=7 |
9 2 |
max=9 |
5 5 |
max=5 |
Qué está verificado en esta página. Los lenguajes de la sección 🟢 se ejecutan en CI contra este mismo
casos.json, igual que las diez implementaciones del núcleo (workflow Labs). Los de la sección 🟡 no pueden cumplir este contrato sin falsear el lenguaje, y se explica por qué. Los de la sección ⚪ sí podrían, pero su cadena de herramientas no está en los runners: son correctos, sin sello de máquina.
Ficha completa · Banca, seguros, gobierno, medios de pago · cobc -x -free prog.cob
IDENTIFICATION DIVISION.
PROGRAM-ID. MAXIMO.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 LINEA PIC X(80).
01 TXT-A PIC X(20).
01 TXT-B PIC X(20).
01 A PIC S9(9) COMP-3.
01 B PIC S9(9) COMP-3.
01 MAYOR PIC S9(9) COMP-3.
01 ED-M PIC -(9)9.
PROCEDURE DIVISION.
ACCEPT LINEA
UNSTRING LINEA DELIMITED BY ALL SPACES INTO TXT-A TXT-B
MOVE FUNCTION NUMVAL(TXT-A) TO A
MOVE FUNCTION NUMVAL(TXT-B) TO B
COMPUTE MAYOR = FUNCTION MAX(A, B)
MOVE MAYOR TO ED-M
DISPLAY "max=" FUNCTION TRIM(ED-M)
STOP RUN.
Lo que esta clase enseña en COBOL. COBOL no tiene operador ternario ni if como expresión.
Un IF es una sentencia que ejecuta cosas; no devuelve nada. Sin las funciones intrínsecas habría
que escribir:
IF A > B
MOVE A TO MAYOR
ELSE
MOVE B TO MAYOR
END-IF
Cuatro líneas, una variable declarada antes sin valor, y ninguna garantía de que todas las ramas la asignen.
Lo que COBOL ofrece en su lugar son las funciones intrínsecas, incorporadas en COBOL-85, y son más de las que la gente recuerda:
FUNCTION MAX(A, B, C) *> acepta CUALQUIER número de argumentos
FUNCTION MIN(TABLA(ALL)) *> ¡y una tabla entera con ALL!
FUNCTION SUM(VENTAS(ALL))
FUNCTION MEAN(NOTAS(ALL))
FUNCTION ORD-MAX(A, B, C) *> la POSICIÓN del mayor, no su valor
FUNCTION MAX(TABLA(ALL)) sobre un array completo es notable: es una operación sobre una colección
entera, sin bucle, en un lenguaje de 1985. Es la misma idea que max() de Fortran sobre arrays y que
inject:into: de Smalltalk, y la razón es la misma — el dominio del lenguaje está lleno de
totalizar, promediar y buscar máximos sobre tablas.
Ficha completa · HPC, clima, física, BLAS/LAPACK · gfortran -O2 prog.f90
program maximo
implicit none
integer :: a, b
read(*, *) a, b
write(*, '(A,I0)') 'max=', max(a, b)
end program maximo
Lo que esta clase enseña en Fortran. Fortran tampoco tiene ternario, y su respuesta es la más característica del lenguaje: funciones elementales.
max(a, b) no es una función normal. Es elemental, lo que significa que se aplica igual a
escalares y a arrays, elemento a elemento:
max(3, 7) ! 7
max(a, b, c, d) ! cualquier número de argumentos
max(vector1, vector2) ! un ARRAY con el mayor de cada posición
max(matriz, 0.0) ! pone a cero todos los negativos de una matriz
Esa última línea es el idioma que hace innecesario el condicional: en vez de recorrer y comparar, aplicas la operación a la estructura entera. Es la mentalidad vectorizada de la clase 043, aplicada al control de flujo.
Y para el caso general, Fortran tiene merge, que es lo más parecido a un ternario que ofrece:
merge(a, b, a > b) ! el ternario de Fortran
merge(v, 0.0, v > 0.0) ! sobre un array: pone a cero los no positivos
merge es también elemental, así que la condición puede ser un array de lógicos y la selección se
hace posición a posición. Es una operación sin ramas, que el compilador puede vectorizar — que es
exactamente por lo que existe. En un bucle de mil millones de vueltas, un if rompe la
segmentación del procesador y merge no.
Ficha completa · Aviónica, espacio, ferrocarril, defensa · gnatmake prog.adb
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Maximo is
A, B : Integer;
begin
Get (A);
Get (B);
-- Integer'Max es un ATRIBUTO del tipo. También valdría la expresión
-- condicional de Ada 2012: (if A > B then A else B)
Put ("max=");
Put (Integer'Max (A, B), Width => 1);
New_Line;
end Maximo;
Lo que esta clase enseña en Ada. Ada tiene las dos respuestas, y compararlas es instructivo.
La primera es Integer'Max (A, B): un atributo del tipo, no una función de biblioteca. Cada tipo
escalar trae 'Max, 'Min, 'Succ, 'Pred, 'First, 'Last, 'Image, 'Value, 'Range… Si
defines type Metros is new Float, Metros'Max existe automáticamente y devuelve un Metros, no
un Float. Los atributos se heredan con el tipo, cosa que una función genérica tendría que
instanciar.
La segunda llegó con Ada 2012: las expresiones condicionales, que exigen paréntesis obligatorios.
M : constant Integer := (if A > B then A else B);
X : constant String := (case Dia is when Sabado | Domingo => "finde",
when others => "laborable");
Los paréntesis no son opcionales, y esa exigencia es muy propia de Ada: hacen imposible confundir la expresión con la sentencia al leer, y evitan cualquier ambigüedad de precedencia.
Lo importante es lo que habilitó esa incorporación: poder declarar constant. Antes de 2012, una
variable cuyo valor dependía de una condición tenía que declararse sin valor y asignarse en un if,
así que no podía ser constante. La expresión condicional no se añadió por brevedad — se añadió para
que más cosas pudieran ser inmutables.
Ficha completa · Escritorio empresarial, TPV, industria (vía Delphi y Free Pascal) · fpc -Mobjfpc prog.pas
program Maximo;
{$MODE OBJFPC}{$H+}
uses SysUtils, Math;
var
A, B: Integer;
begin
Read(A, B);
WriteLn('max=', IntToStr(Max(A, B)));
end.
Lo que esta clase enseña en Pascal. El Pascal ISO no tiene ternario, y Free Pascal y Delphi
añadieron Max y Min en la unidad Math, además de una función IfThen que parece un ternario y
no lo es:
uses Math, StrUtils;
X := IfThen(A > B, A, B); { versión de Math, para enteros }
S := IfThen(Cond, 'sí', 'no'); { versión de StrUtils, para cadenas }
Y aquí está la trampa que esta clase debe señalar: IfThen es una función normal, así que evalúa
sus tres argumentos siempre. Un ternario de verdad no evalúa la rama que no se toma.
X := IfThen(Divisor <> 0, Dividendo div Divisor, 0); { ¡DIVISIÓN POR CERO! }
Esa línea falla cuando Divisor es cero, porque la división se evalúa antes de llamar a
IfThen. En C, d != 0 ? n / d : 0 es correcto. Es exactamente la diferencia entre una función y
una construcción de control, y es la misma razón por la que and: de Smalltalk necesita un bloque y
por la que and de Lisp es una macro.
Delphi 10.4 añadió por fin la expresión condicional real —var m := if a > b then a else b—, y Free
Pascal la ofrece con el modificador de modo correspondiente. Pero el IfThen de la biblioteca sigue
ahí, y sigue siendo una trampa para quien lo confunde con un ternario.
Ficha completa · IA simbólica, CAD, investigación · sbcl --script prog.lisp
(let* ((a (read))
(b (read)))
(format t "max=~D~%" (max a b)))
Lo que esta clase enseña en Common Lisp. En Lisp la distinción entre sentencia y expresión no
existe. Todo es una expresión, todo devuelve un valor, y por tanto if siempre ha sido
asignable:
(let ((m (if (> a b) a b))) ...) ; el if devuelve un valor
(setf x (cond (c1 v1) (c2 v2) (t v3))) ; cond también
(setf y (case k (1 'uno) (t 'otro))) ; y case
(setf z (progn (log "hola") 42)) ; incluso un bloque: vale su última forma
No hay un if que ejecute y otro que devuelva: hay uno solo. Y when y unless, que son if sin
la rama contraria, devuelven nil cuando no se cumplen — así que también son expresiones.
Esa uniformidad es lo que Rust, Kotlin, Scala y Ruby adoptaron después, y viene de aquí. La razón por la que casi todos los lenguajes de los 70 y 80 separaron sentencia de expresión es la máquina: una sentencia se compilaba a un salto y una expresión a un valor en un registro, y unificarlas costaba. Lisp lo hizo desde el principio porque su modelo era el cálculo lambda, no la máquina.
max en Lisp acepta cualquier número de argumentos y funciona sobre toda la torre numérica de la
clase 043: (max 1/2 0.3 7) compara una fracción, un real y un entero y devuelve el 7.
Ficha completa · Diseño de chips (EDA), redes, testing · tclsh prog.tcl
gets stdin linea
lassign [split [string trim $linea]] a b
puts "max=[expr {max($a, $b)}]"
Lo que esta clase enseña en Tcl. Dentro de expr, Tcl sí tiene el ternario de C —c ? a : b—
y desde la versión 8.5 también las funciones max() y min() con cualquier número de argumentos.
Fuera de expr no hay ninguna de las dos cosas, porque fuera de expr no hay operadores. Y eso
produce una asimetría que conviene tener clara:
set m [expr {$a > $b ? $a : $b}] ;# ternario: dentro de expr
set m [expr {max($a, $b)}] ;# función: dentro de expr
if {$a > $b} { set m $a } else { set m $b } ;# comando: fuera
Las tres formas hacen lo mismo. La primera y la segunda son expresiones y se pueden usar donde se
espera un valor; la tercera es un comando que además devuelve un valor —el de la última sentencia
del cuerpo ejecutado—, así que en Tcl incluso el if es asignable:
set m [if {$a > $b} { set _ $a } else { set _ $b }]
Funciona, aunque nadie lo escribe así. La razón de que funcione es la misma de siempre: en Tcl todo
comando devuelve una cadena, incluido if, while y proc. La distinción sentencia/expresión no
existe porque no hay más que comandos, cada uno con su resultado.
Ficha completa · Sysadmin, texto, bioinformática · perl prog.pl
use strict;
use warnings;
my $linea = <STDIN>;
chomp $linea;
my ($x, $y) = split ' ', $linea;
my $mayor = $x > $y ? $x : $y;
print "max=$mayor\n";
Lo que esta clase enseña en Perl. Perl tiene el ternario de C y, además, algo que casi ningún lenguaje de esta página ofrece: el ternario es asignable.
($x > $y ? $x : $y) = 0; # pone a cero la MAYOR de las dos variables
Eso funciona porque en Perl el ternario devuelve un lvalue —una referencia al sitio, no una
copia— cuando ambas ramas lo son. Es la misma capacidad que tiene substr en la clase 048, y muestra
una idea de fondo de Perl: las construcciones devuelven lugares, no solo valores.
Perl también tiene una versión de esta clase que es más idiomática para más de dos elementos, y viene de la biblioteca estándar:
use List::Util qw(max min sum first reduce any all none);
my $mayor = max @numeros;
my $total = sum @numeros;
my $primero = first { $_ > 100 } @numeros;
my $hay = any { $_ < 0 } @numeros;
List::Util está en el núcleo desde 2001 y sus funciones están escritas en C, así que son rápidas.
first, any, all y none cortocircuitan: paran en cuanto tienen la respuesta. Es la
biblioteca de orden superior que la clase 068 estudiará a fondo, disponible aquí para resolver el
caso concreto sin escribir un condicional.
Ficha completa · Videojuegos, navegadores, finanzas, HPC · g++ -std=c++17 prog.cpp
#include <algorithm>
#include <iostream>
int main() {
int a{}, b{};
if (!(std::cin >> a >> b)) return 1;
const int mayor = std::max(a, b);
std::cout << "max=" << mayor << '\n';
return 0;
}
Lo que esta clase enseña en C++. La palabra clave de este programa es const. const int
mayor = std::max(a, b); declara, calcula y sella en una sola línea. Con un if habría que
escribir int mayor; sin valor y asignarlo en las ramas, perdiendo la constancia y abriendo la
posibilidad de que alguna rama no asigne.
Esa es la razón práctica por la que las expresiones condicionales importan, y explica por qué el C++ moderno prefiere el ternario o una lambda inmediatamente invocada cuando la lógica es más compleja:
const int mayor = a > b ? a : b;
const auto categoria = [&] { // lambda invocada al vuelo
if (score >= 90) return "A";
if (score >= 80) return "B";
return "F";
}(); // <- se llama aquí mismo
Ese patrón —conocido como IIFE— convierte una cadena de if en una expresión, permitiendo
const. Es el sustituto del if como expresión en un lenguaje que no lo tiene.
Y un aviso sobre std::max: devuelve una referencia constante, así que
const auto& m = std::max(f(), g()); deja una referencia colgante si los argumentos son temporales.
Con auto a secas —copiando— no hay problema. Es un caso clásico y sutil que aparece en cuanto se
mezcla auto& con funciones que devuelven referencias.
Estos lenguajes no pueden leer de stdin y escribir en stdout sin dejar de ser ellos
mismos. No es una limitación del material: es su naturaleza. El cálculo es el mismo y la forma
de entrar y salir es la de su anfitrión. No pasan por el verificador, y se dice.
Ficha completa · IBM i: ERP, retail, logística, manufactura · CRTBNDRPG sobre IBM i
En IBM i un programa recibe sus datos por parámetros, por un fichero o por una pantalla, nunca por la entrada estándar.
**free
ctl-opt dftactgrp(*no) actgrp(*caller);
dcl-pi MAXIMO;
a int(10) const;
b int(10) const;
end-pi;
dcl-s mayor int(10);
dcl-s salida char(30);
if a > b;
mayor = a;
else;
mayor = b;
endif;
salida = 'max=' + %char(mayor);
dsply salida;
*inlr = *on;
return;
Lo que esta clase enseña en RPG. RPG no tiene ternario ni if como expresión, así que hay
que declarar la variable y asignarla en las ramas. Es la misma situación que COBOL y PL/I, y por el
mismo motivo: los tres nacieron cuando la sentencia y la expresión eran cosas distintas también en la
máquina.
Lo que RPG sí tiene, y viene al caso, son funciones incorporadas sobre matrices:
dcl-s ventas packed(11:2) dim(12);
total = %sum(ventas); // suma de todos los elementos
mayor = %max(ventas); // el mayor
posicion = %lookup(buscado : ventas); // búsqueda, devuelve el índice
%sum, %max, %min y %lookup sobre matrices completas son la misma idea que
FUNCTION SUM(TABLA(ALL)) de COBOL y max(array) de Fortran: operar sobre la colección entera sin
escribir el bucle. Los tres lenguajes de negocio y cálculo de esta página llegaron a la misma
solución, porque totalizar tablas es lo que hacen todo el día.
Y para elegir entre dos valores, el idioma de RPG es %max(a : b) con la forma de dos argumentos,
que evita el if de este programa — aunque sigue sin ser una expresión condicional general.
Sí podrían cumplir el contrato, pero su cadena de herramientas no está en los runners de CI.
Ficha completa · Mainframe z/OS: banca, seguros · IBM Enterprise PL/I for z/OS
maximo: procedure options(main);
declare (a, b) fixed binary(31);
get list (a, b);
put skip list ('max=' || trim(char(max(a, b))));
end maximo;
Lo que esta clase enseña en PL/I. PL/I no tiene ternario, pero su catálogo de funciones
incorporadas es enorme y cubre este caso y muchos más: max, min, abs, sign, sum, prod,
any, all, poly…
Dos de ellas merecen atención en esta clase porque operan sobre cadenas de bits, que es la forma que PL/I tiene de trabajar con varias condiciones a la vez:
declare condiciones bit(8);
if any(condiciones) then ... /* ¿alguno de los 8 bits está a 1? */
if all(condiciones) then ... /* ¿todos? */
any y all sobre una cadena de bits son el equivalente de los cuantificadores for all y
for some de Ada de la clase 057, y de any/all de List::Util en Perl. Con 32 condiciones
empaquetadas en un bit(32), comprobarlas todas es una sola instrucción.
Es un ejemplo del patrón que recorre toda esta sección: PL/I no tenía la construcción de control,
tenía la operación sobre datos. Y en muchos casos la operación sobre datos es mejor, porque
paraleliza; en otros, obliga a evaluarlo todo cuando bastaría con lo primero. Esa es exactamente la
frontera entre any de PL/I y first de Perl, que cortocircuita.
Ficha completa · Sanidad: historia clínica, VistA, Epic · YottaDB
MAXIMO ; Maximo -- clase 060
read linea
set a = $piece(linea, " ", 1)
set b = $piece(linea, " ", 2)
set mayor = $select(a > b : a, 1 : b)
write "max=", mayor, !
quit
Lo que esta clase enseña en M. $select es el ternario de M, y es más general que el ?: de
C porque admite cualquier número de pares:
set nivel = $select(x>90 : "alto", x>50 : "medio", x>0 : "bajo", 1 : "nulo")
Es una expresión, devuelve un valor, evalúa perezosamente y encadena tantos casos como quieras. En un
lenguaje sin ternario ni if asignable, $select cubre las dos necesidades.
Y M tiene una segunda pieza que resuelve esta clase de otra manera, y que es muy suya: $order y
la ordenación de las claves. Como los subíndices de un array se guardan en orden, el máximo de un
conjunto no se calcula, se consulta:
set ^TMP(3)="", ^TMP(7)="", ^TMP(5)=""
write $order(^TMP(""), -1) ; 7 -- el ÚLTIMO subíndice: el máximo
write $order(^TMP("")) ; 3 -- el PRIMERO: el mínimo
El -1 recorre en orden inverso. En una base de datos con un millón de nodos, obtener el máximo es
una operación de índice, no un recorrido. Es la misma ventaja que da un índice B-tree en SQL, con la
diferencia de que aquí no hay que declararlo: el orden es una propiedad del almacenamiento.
Ficha completa · Banca, seguros, trading · Pharo
| partes a b |
partes := stdin nextLine substrings collect: [ :cada | cada asNumber ].
a := partes first.
b := partes second.
Transcript show: 'max=', (a max: b) printString; cr.
Lo que esta clase enseña en Smalltalk. a max: b es un mensaje enviado al número, y como todo
mensaje devuelve un valor, la distinción de esta clase sencillamente no se plantea: en Smalltalk
todo es una expresión.
ifTrue:ifFalse: devuelve el valor del bloque que se evaluó, así que es asignable sin ninguna
sintaxis especial:
mayor := a > b ifTrue: [ a ] ifFalse: [ b ].
etiqueta := score > 50 ifTrue: [ 'apto' ] ifFalse: [ 'no apto' ].
Y lo mismo vale para el resto de estructuras de control: whileTrue: devuelve nil, to:do:
devuelve el receptor, detect:ifNone: devuelve el elemento encontrado. No hay ninguna construcción
del lenguaje que no devuelva algo, porque todas son envíos de mensajes y un mensaje siempre
responde.
La implementación de max: es, como cabe esperar, una línea que puedes abrir y leer:
Magnitude >> max: unaMagnitud
^ self > unaMagnitud ifTrue: [ self ] ifFalse: [ unaMagnitud ]
Está en Magnitude, la superclase abstracta de todo lo que se puede comparar —números, caracteres,
fechas, cadenas—. Basta con implementar < en una clase nueva para heredar max:, min:,
between:and: y el resto del protocolo de comparación. Es la misma economía que el Comparable de
Java, veinte años antes.
Lo transferible es que una expresión condicional permite inicializar y sellar en una sola
sentencia. const int m = a > b ? a : b; declara, calcula y prohíbe futuras modificaciones a la
vez; la versión con if obliga a declarar sin valor y confiar en que todas las ramas asignen. Por
eso Rust, Kotlin y Scala convirtieron if en expresión, y por eso Python añadió a if c else b. Los
lenguajes de esta página muestran de dónde venía la necesidad.
⏮️ Volver a la clase 060 · 🧬 Los primos del Atlas · 🧟 Índice de lenguajes vivos