⬅️ Volver a la clase 108 · 🧬 Primos del Atlas · 🧟 Índice de lenguajes vivos · 📚 Índice
Un acumulador y un bucle. El paradigma imperativo en su forma más pura, y el sitio exacto donde estos
lenguajes tienen algo que contar que ninguno moderno puede: la programación estructurada no fue un
descubrimiento académico, fue una guerra de veinte años, y se libró en COBOL, en Fortran y en PL/I.
Los tres tuvieron que añadir el while a un lenguaje que ya llevaba décadas en producción.
🎯 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 secuencia, la selección y la iteración —las tres estructuras del teorema de Böhm-Jacopini (1966)—, y estos lenguajes lo enseñan porque son los que estaban ahí cuando se demostró. FORTRAN no tuvo
IF/THEN/ELSEhasta 1977 niDO WHILEhasta 1990. COBOL no tuvoEND-IFhasta 1985. Antes de eso, la estructura se construía conGO TOy etiquetas.Y RPG y M aportan las dos rarezas: RPG tuvo un bucle principal implícito hasta que dejó de tenerlo, y M no tiene bloques: la condición va pegada al comando.
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.
suma=<suma de todos>acumular la suma recorriendo la lista| stdin | esperado |
|---|---|
1 2 3 |
suma=6 |
5 |
suma=5 |
10 20 |
suma=30 |
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. IMPER.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 LINEA PIC X(200).
01 TOKEN PIC X(20).
01 TLEN PIC 9(2) COMP VALUE 0.
01 I PIC 9(4) COMP.
01 SUMA PIC S9(18) COMP-3 VALUE 0.
01 ED-S PIC -(17)9.
PROCEDURE DIVISION.
ACCEPT LINEA
MOVE SPACES TO TOKEN
PERFORM VARYING I FROM 1 BY 1 UNTIL I > 200
IF LINEA(I:1) = SPACE
PERFORM ACUMULAR
ELSE
ADD 1 TO TLEN
MOVE LINEA(I:1) TO TOKEN(TLEN:1)
END-IF
END-PERFORM
PERFORM ACUMULAR
MOVE SUMA TO ED-S
DISPLAY "suma=" FUNCTION TRIM(ED-S)
STOP RUN.
ACUMULAR.
IF TLEN > 0
COMPUTE SUMA = SUMA + FUNCTION NUMVAL(TOKEN)
MOVE SPACES TO TOKEN
MOVE 0 TO TLEN
END-IF.
Lo que esta clase enseña en COBOL. COBOL es el caso mejor documentado de un lenguaje que tuvo que aprender a ser estructurado, y las fechas lo cuentan:
| Año | Qué llegó |
|---|---|
| 1959 | IF, GO TO, PERFORM, ALTER |
| 1968 | PERFORM VARYING |
| 1974 | poco: el debate estaba en marcha |
| 1985 | END-IF, END-PERFORM, EVALUATE, PERFORM UNTIL en línea |
| 2002 | EXIT PARAGRAPH, EXIT PERFORM, CONTINUE |
El punto de inflexión es 1985, y el problema que resolvió era grave. Antes, un IF se cerraba con
un punto:
IF SALDO > 0
DISPLAY "positivo"
MOVE 1 TO X.
DISPLAY "siempre"
Ese punto después de MOVE 1 TO X cierra el IF. Un punto de más, y la mitad del cuerpo se sale del
condicional; uno de menos, y el resto del párrafo entra dentro. Y el compilador no se queja,
porque las dos formas son legales.
Con END-IF, el problema desaparece: el bloque tiene un delimitador explícito, y hoy la práctica
universal es no usar puntos salvo al final del párrafo.
Y hay una sentencia que merece nombrarse porque es célebre por lo dañina: ALTER.
ALTER PARRAFO-X TO PROCEED TO PARRAFO-Z.
ALTER cambia en ejecución el destino de un GO TO. Es decir: leyendo el código no se puede
saber a dónde salta, porque depende de qué ALTER se haya ejecutado antes. Es el GO TO calculado
elevado a su peor forma, y produjo código literalmente imposible de seguir.
Está obsoleta desde COBOL-85 y eliminada del estándar de 2002, y sigue apareciendo en programas antiguos que nadie se atreve a tocar. Cuando se habla del "código COBOL heredado ininteligible", una parte concreta de esa reputación tiene nombre, y es este.
Ficha completa · HPC, clima, física, BLAS/LAPACK · gfortran -O2 prog.f90
program imper
implicit none
integer :: v(100), n, ios, i
integer :: suma
character(len=400) :: linea
read(*, '(A)') linea
n = 0
do i = 1, 100
read(linea, *, iostat=ios) v(1:i)
if (ios /= 0) exit
n = i
end do
! Estilo IMPERATIVO: acumulador y bucle explícito
suma = 0
do i = 1, n
suma = suma + v(i)
end do
write(*, '(A,I0)') 'suma=', suma
end program imper
Lo que esta clase enseña en Fortran. Fortran es el otro gran caso de esta clase, y su historia es
aún más llamativa que la de COBOL: el FORTRAN de 1957 no tenía IF/THEN/ELSE.
Lo que tenía era el IF aritmético, una de las construcciones más extrañas que han existido:
IF (X - Y) 10, 20, 30
Tres etiquetas: salta a la primera si la expresión es negativa, a la segunda si es cero y a la tercera si es positiva. Es una bifurcación a tres bandas, pensada para el hardware del IBM 704, y era la única forma de decidir.
La cronología de la estructuración de Fortran:
| Versión | Qué añadió | Qué faltaba |
|---|---|---|
| 1957 | IF aritmético, DO, GO TO |
todo lo demás |
| 1966 | IF lógico de una sentencia |
bloques |
| 1977 | IF/THEN/ELSE/END IF |
no hay WHILE |
| 1990 | DO WHILE, EXIT, CYCLE, SELECT CASE |
— |
| 1995 | se borran del estándar ASSIGN y el GO TO asignado |
— |
Fortran no tuvo while hasta 1990. Treinta y tres años escribiendo bucles condicionales así:
10 IF (.NOT. COND) GO TO 20
...
GO TO 10
20 CONTINUE
Ese idioma está en todo el código Fortran clásico, y explica por qué las bibliotecas numéricas históricas —LINPACK, EISPACK, las rutinas de Numerical Recipes— se leen como se leen. No es mala programación: es que no había otra forma.
Y hay un fósil que merece verse porque hoy sería inconcebible: el DO original ejecutaba el cuerpo
al menos una vez, aunque el límite fuera menor que el inicio. DO 10 I = 1, 0 daba una vuelta. Se
corrigió en Fortran 77, y hubo que revisar código.
Fortran 90 completó la estructuración con nombres de bucle, que son mejores que el break con
etiqueta de otros lenguajes:
externo: do i = 1, n
interno: do j = 1, m
if (...) exit externo ! salir del bucle EXTERNO por su nombre
if (...) cycle interno
end do interno
end do externo
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 Imper is
Linea : String (1 .. 400);
Ultimo : Natural;
Pos : Integer := 1;
Valor : Integer;
Fin : Positive;
Suma : Integer := 0;
begin
Get_Line (Linea, Ultimo);
loop
while Pos <= Ultimo and then Linea (Pos) = ' ' loop
Pos := Pos + 1;
end loop;
exit when Pos > Ultimo;
Get (Linea (Pos .. Ultimo), Valor, Fin);
Suma := Suma + Valor;
Pos := Fin + 1;
end loop;
Put ("suma=");
Put (Suma, Width => 1);
New_Line;
end Imper;
Lo que esta clase enseña en Ada. Ada nació después de que la batalla estructurada terminara, y
se nota: es de los pocos lenguajes de esta página donde el goto casi no se usa, y donde el diseño de
los bucles está pensado con cuidado.
Ada tiene una sola construcción de bucle con tres formas:
loop ... end loop; -- infinito
while Cond loop ... end loop; -- con condición al principio
for I in 1 .. N loop ... end loop; -- contado
for I in reverse 1 .. N loop ... end loop; -- descendente
for E of Coleccion loop ... end loop; -- Ada 2012: sobre los ELEMENTOS
Y exit when, que es la aportación de diseño:
loop
Leer (X);
exit when X = 0;
Procesar (X);
end loop;
Eso resuelve el bucle "y medio" —el que necesita salir por el medio— que Dijkstra señaló como el
caso que las tres estructuras clásicas no cubren bien. En C hay que escribir while (true) con un
break; en Ada, exit when es la forma normal.
Y exit acepta el nombre del bucle, como en Fortran:
Externo:
for I in 1 .. N loop
for J in 1 .. M loop
exit Externo when Encontrado;
end loop;
end loop Externo;
Fíjate en que end loop Externo repite el nombre. Ada lo permite —y lo exige si el bucle está
nombrado— para todos los bloques: end if, end loop Nombre, end Procedimiento. En un
procedimiento de trescientas líneas, saber qué se está cerrando sin contar sangrías es más útil de lo
que parece.
Ada sí tiene goto, con la sintaxis goto <<Etiqueta>>, y las guías lo permiten para un caso
concreto: salir de bucles anidados profundos en código generado. Que exista, esté acotado y casi no se
use es la señal de que el debate ya estaba resuelto cuando el lenguaje se diseñó.
Ficha completa · Escritorio empresarial, TPV, industria (vía Delphi y Free Pascal) · fpc -Mobjfpc prog.pas
program Imper;
{$MODE OBJFPC}{$H+}
uses SysUtils;
var
Linea, Tok: string;
I, Suma: Integer;
C: Char;
begin
ReadLn(Linea);
Suma := 0;
Tok := '';
for I := 1 to Length(Linea) + 1 do
begin
if I <= Length(Linea) then C := Linea[I] else C := ' ';
if C = ' ' then
begin
if Tok <> '' then
begin
Suma := Suma + StrToInt(Tok);
Tok := '';
end;
end
else
Tok := Tok + C;
end;
WriteLn('suma=', IntToStr(Suma));
end.
Lo que esta clase enseña en Pascal. Pascal es esta clase: se diseñó en 1970 para enseñar exactamente esto, y su sintaxis es la programación estructurada convertida en gramática.
Las tres estructuras están completas y sin excepciones desde el primer día:
begin ... end { secuencia }
if ... then ... else { selección }
case ... of ... end { selección múltiple, CON rangos (clase 092) }
while ... do { iteración con condición al principio }
repeat ... until { con condición al final }
for ... to / downto ... do { iteración contada }
Y lo que deliberadamente NO tiene es lo más instructivo:
break ni continue en el Pascal original. Wirth los consideraba goto disfrazados.return anticipado: el valor se asigna al nombre de la función y el procedimiento termina
donde termina.case, al contrario que el switch de C.for no permite modificar la variable de control dentro del bucle — el estándar lo prohíbe,
y la variable queda indefinida al salir.Esa última regla suele sorprender y tiene un motivo técnico: permite al compilador guardar el índice en un registro y no escribirlo en memoria.
Pascal sí tiene goto, y Wirth lo incluyó a regañadientes con una restricción severa: solo
puede saltar a una etiqueta del mismo bloque o de uno que lo contenga, nunca hacia dentro de otro
bloque. Es goto que no puede romper la estructura.
Y hay un detalle histórico que cierra el argumento de esta clase: Pascal apareció en 1970, dos años
después de la carta de Dijkstra sobre el GO TO. No es una coincidencia — Wirth y Dijkstra
trabajaron juntos en el comité de Algol 68 y compartían el diagnóstico. Pascal es la respuesta de
Wirth escrita en forma de compilador, después de que la propuesta conjunta que firmaron contra Algol
68 fuera rechazada.
Ficha completa · IA simbólica, CAD, investigación · sbcl --script prog.lisp
(let ((suma 0))
(loop for x = (read *standard-input* nil nil)
while x
do (incf suma x)) ; imperativo: mutar un acumulador
(format t "suma=~D~%" suma))
Lo que esta clase enseña en Common Lisp. Que un programa Lisp aparezca en la clase de programación imperativa no es una contradicción: Common Lisp es tan imperativo como funcional, y esa es una de sus diferencias más importantes con Scheme y con Haskell.
El lenguaje tiene, sin ningún reparo:
(setf x 10) ; asignación
(incf x) (decf x) ; incremento y decremento
(push x lista) (pop lista) ; mutación de estructuras
(setf (aref v 3) 99) ; escribir en un vector
(setf (gethash k tabla) v) ; y en una tabla hash
(dotimes (i 10) ...) (dolist (x l) ...) (loop ...)
(prog1 ...) (progn ...) (tagbody ... (go etiqueta))
Y sí, Lisp tiene go. tagbody con etiquetas y go es una construcción del estándar, y es la
primitiva sobre la que se implementan los bucles: loop, do y dotimes se expanden a tagbody
con go.
Eso es exactamente lo que Lisp hace distinto: las estructuras de control son macros sobre primitivas de salto, así que el lenguaje ofrece las estructuras civilizadas y deja la primitiva accesible para quien construya nuevas.
Y hay una razón histórica que conviene decir, porque desmonta una idea muy extendida: Lisp no era un
lenguaje funcional puro en 1958. Tenía setq, rplaca y rplacd —mutación destructiva de
conses— desde el principio. La pureza funcional llegó como estilo recomendado mucho después, y
principalmente desde la comunidad de Scheme y de ML.
El estilo dominante en Common Lisp real es mixto: funcional donde ayuda, imperativo donde es más
claro, y con loop como caballo de batalla. La razón la dan las clases 084 y 090: acumular con
push e invertir, o usar loop ... collect, es más rápido y más legible que la recursión pura, y la
comunidad lo asumió sin complejos.
Ficha completa · Diseño de chips (EDA), redes, testing · tclsh prog.tcl
gets stdin linea
set suma 0
foreach x [split [string trim $linea]] {
incr suma $x
}
puts "suma=$suma"
Lo que esta clase enseña en Tcl. Tcl es imperativo y sus estructuras de control son comandos, no sintaxis — lo que ya se vio en la clase 107 y que aquí tiene una consecuencia práctica muy concreta.
if {$x > 0} { ... } elseif { ... } else { ... }
while {$x > 0} { ... }
for {set i 0} {$i < 10} {incr i} { ... }
foreach x $lista { ... }
foreach {a b} $lista { ... } ;# de DOS en dos
foreach a $l1 b $l2 { ... } ;# DOS listas a la vez
switch -exact -- $x { ... }
break continue return
Dos de esas formas de foreach no las tiene casi nadie: recorrer de n en n y recorrer varias
listas en paralelo. Son cómodas y son consecuencia de que foreach sea un comando que recibe pares
de argumentos, no una construcción con gramática fija.
Y Tcl no tiene goto. Nunca lo tuvo, y su ausencia no se echa de menos porque break,
continue, return y las excepciones cubren los casos.
Lo que sí tiene, y es lo característico, es que el flujo de control se puede extender. uplevel y
upvar permiten escribir comandos que ejecutan código en el ámbito del llamante:
proc mientras_haya {var lista cuerpo} {
upvar 1 $var v
foreach v $lista {
uplevel 1 $cuerpo
}
}
Ese mientras_haya es una estructura de control nueva, y se usa exactamente igual que foreach.
Hay una consecuencia de rendimiento que conviene conocer: como if y while son comandos que
reciben cadenas, es crucial usar llaves y no comillas:
while {$i < 10} { ... } ;# la condición se COMPILA una vez
while "$i < 10" { ... } ;# se sustituye ANTES: bucle infinito, y lento
Con llaves, el compilador de bytecode de Tcl 8.0+ compila la expresión una sola vez. Sin ellas, se reanaliza en cada vuelta. Es la diferencia entre Tcl rápido y Tcl lento, y está en un par de llaves.
Ficha completa · Sysadmin, texto, bioinformática · perl prog.pl
use strict;
use warnings;
my $linea = <STDIN>;
chomp $linea;
my $suma = 0;
$suma += $_ for split ' ', $linea; # modificador de sentencia
print "suma=$suma\n";
Lo que esta clase enseña en Perl. La línea $suma += $_ for split ' ', $linea; usa un
modificador de sentencia, que es una de las señas de identidad del lenguaje:
print "hola" if $x > 0;
$total += $_ for @lista;
print while <$fh>;
$i++ until $i >= 10;
do { ... } while $cond; # la ÚNICA forma de do-while
Escribir la condición detrás de la acción viene de la sensibilidad lingüística de Larry Wall: en lenguaje natural se dice "cierra la puerta si hace frío", no "si hace frío, cierra la puerta". Para una sola acción, el modificador se lee mejor y evita las llaves.
Y Perl tiene el juego completo de estructuras, con detalles propios:
if / elsif / else / unless # unless = if not
while / until / for / foreach
last / next / redo # break / continue / REPETIR sin reevaluar
LOOP: foreach (...) { last LOOP; } # etiquetas de bucle
redo no lo tiene casi ningún lenguaje: repite el cuerpo del bucle sin reevaluar la condición
ni avanzar el iterador. Es útil para reintentos.
Y Perl tiene goto, en tres formas, y la tercera es una rareza que merece nombrarse:
goto ETIQUETA; # el goto clásico, prácticamente sin uso
goto &otra_funcion; # SUSTITUIR la llamada actual por otra
goto &funcion reemplaza el marco de pila actual por el de otra función, pasándole @_ tal
cual. La función original desaparece del rastro de llamadas, como si nunca hubiera estado.
Se usa para dos cosas reales: llamadas en cola sin crecer la pila —recursión de cola de verdad— y
funciones envolventes transparentes, que es como funcionan AUTOLOAD y varios módulos de CPAN
que interceptan llamadas.
Es una primitiva de bajo nivel expuesta en un lenguaje de alto nivel, muy en la línea de Perl: dejarte hacer lo que quieras y confiar en que sabes lo que haces.
Ficha completa · Videojuegos, navegadores, finanzas, HPC · g++ -std=c++17 prog.cpp
#include <iostream>
#include <iterator>
#include <vector>
int main() {
std::vector<int> v{std::istream_iterator<int>(std::cin),
std::istream_iterator<int>()};
int suma = 0;
for (int x : v) { // bucle basado en rango (C++11)
suma += x;
}
std::cout << "suma=" << suma << '\n';
return 0;
}
Lo que esta clase enseña en C++. C++ hereda de C todas las estructuras y también el goto, y
su historia en esta clase es la de un lenguaje que añadió abstracción sin quitar nada.
El bucle de este programa es la forma moderna:
for (int x : v) { ... } // C++11: sobre los ELEMENTOS
for (const auto& [k, val] : mapa) { ... } // C++17, con enlace estructurado
for (auto x : v | std::views::filter(par)) { ... } // C++20
Frente al de C, que sigue siendo legal:
for (std::size_t i = 0; i < v.size(); ++i) { ... }
La diferencia no es estética: el bucle basado en rango no puede tener un error de índice, porque no hay índice. Elimina de un plumazo la familia entera de errores de "uno de más" y de acceso fuera de rango, que son la causa de una parte enorme de los fallos de seguridad de C y C++.
Y esta clase es el sitio para el switch, porque C tomó una decisión que se considera un error de
diseño y C++ la heredó:
switch (x) {
case 1:
hacer_algo();
// ¡sin break! CAE al caso siguiente
case 2:
hacer_otra();
break;
}
La caída entre casos es el comportamiento por defecto, y olvidar un break es un error silencioso
tan común que los compiladores llevan décadas avisando con -Wimplicit-fallthrough. C++17 añadió
[[fallthrough]] para declarar que la caída es intencionada.
Pascal (1970), Ada (1983) y PL/I (1964) no tienen ese problema: en los tres, cada caso termina solo. C lo introdujo en 1972, después de que los tres lo hubieran resuelto, y todos sus descendientes —C++, Java, C#, JavaScript, PHP— lo arrastran.
Es probablemente el ejemplo más citado de que una decisión de diseño mala sobrevive a base de compatibilidad, y por eso Go, Rust, Swift y Kotlin rompieron con ella explícitamente.
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 IMPER;
entrada char(200) const;
end-pi;
dcl-s texto varchar(200);
dcl-s tok varchar(20) inz('');
dcl-s c char(1);
dcl-s i int(10);
dcl-s suma int(20) inz(0);
texto = %trimr(entrada);
for i = 1 to %len(texto) + 1;
if i <= %len(texto);
c = %subst(texto : i : 1);
else;
c = ' ';
endif;
if c = ' ';
if tok <> '';
suma += %int(tok);
tok = '';
endif;
else;
tok += c;
endif;
endfor;
dsply ('suma=' + %char(suma));
*inlr = *on;
return;
Lo que esta clase enseña en RPG. RPG es el lenguaje de esta página cuya historia con la
programación estructurada es más rara, porque su punto de partida no era el GO TO: era no tener
flujo de control en absoluto.
En el RPG original (clase 107), el programador no escribía el bucle principal: lo generaba el
ciclo. Y para decidir, no había IF: había indicadores.
C SALDO COMP 0 10 11 12
C 10 MOVE 'DEUDOR' ESTADO
Esa línea compara SALDO con 0 y enciende los indicadores 10, 11 o 12 según sea mayor, menor o
igual. La siguiente línea, condicionada por el indicador 10, hace algo. Son los indicadores *IN01
a *IN99, banderas globales numeradas que gobernaban todo el programa.
Es exactamente el IF aritmético de Fortran (clase 108, apartado de Fortran) llevado a su forma más
extrema: el estado del programa está en cien banderas globales con nombres de número, y para saber
qué hace una línea hay que saber qué indicadores están encendidos, que depende de todo lo anterior.
De ahí la mala fama del RPG clásico, y merece decirse que estaba justificada.
La estructuración llegó por etapas:
| Versión | Qué llegó |
|---|---|
| 1978 (RPG III) | DO, DOW, DOU, IF, ELSE, END, subrutinas |
| 1994 (RPG IV) | procedimientos con parámetros y valor de retorno |
| 2001 | formato libre en el cálculo: if x > 0; en lugar de columnas |
| 2013 | formato totalmente libre, sin columnas en ninguna parte |
Y el RPG moderno de este programa —for, if, endif, funciones %— se lee como cualquier lenguaje
imperativo actual. Los indicadores siguen existiendo y siguen usándose para la comunicación con
las pantallas, pero la lógica ya no depende de ellos.
Es probablemente la transformación más completa de esta página: de un generador de informes con banderas globales a un lenguaje estructurado con procedimientos, sin romper la compatibilidad en sesenta años.
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
imper: procedure options(main);
declare linea char(200) varying;
declare tok char(20) varying initial('');
declare c char(1);
declare (i, suma) fixed binary(31);
get edit (linea) (a(200));
linea = trim(linea);
suma = 0;
do i = 1 to length(linea) + 1;
if i <= length(linea) then c = substr(linea, i, 1); else c = ' ';
if c = ' ' then do;
if tok ^= '' then do;
suma = suma + tok;
tok = '';
end;
end;
else tok = tok || c;
end;
put skip list ('suma=' || trim(char(suma)));
end imper;
Lo que esta clase enseña en PL/I. PL/I es el lenguaje de esta página que nació estructurado, y es un dato que suele pasarse por alto: en 1964, PL/I ya tenía todo lo que a Fortran le faltaría hasta 1990 y a COBOL hasta 1985.
if ... then do; ... end; else do; ... end;
do while (cond); ... end;
do until (cond); ... end;
do i = 1 to 10 by 2; ... end;
select (x); when (1) ...; otherwise ...; end;
leave; /* break */
iterate; /* continue */
do while en 1964. select sin caída entre casos en 1964. leave e iterate con nombre de bloque
opcional para salir de bucles anidados:
externo: do i = 1 to n;
do j = 1 to m;
leave externo;
end;
end externo;
Todo eso estaba en el lenguaje cuatro años antes de la carta de Dijkstra sobre el GO TO, y esa
coincidencia no es casual: PL/I se diseñó con la influencia directa de Algol 60, que ya tenía
bloques y estructuras, y su comité incluía a gente que venía de ahí.
Es una de las mejores respuestas a la idea de que "los lenguajes antiguos eran primitivos": el problema no era que no se supiera cómo hacerlo, era que Fortran y COBOL ya tenían demasiado código escrito para cambiar.
PL/I sí tiene go to, y con una capacidad peligrosa que ya apareció en la clase 085: las
variables de etiqueta.
declare destino label;
destino = fin;
go to destino; /* salto INDIRECTO */
Y algo más raro todavía: se puede saltar a una etiqueta de un bloque exterior desde dentro de un
procedimiento anidado, deshaciendo los marcos de pila intermedios. Es un throw sin catch, y es
la construcción que hacía que el código PL/I de los setenta pudiera ser tan difícil de seguir pese a
tener todas las estructuras a mano.
La moraleja encaja con el cierre de esta clase: tener las estructuras no basta si el lenguaje deja abierta la puerta de atrás.
Ficha completa · Sanidad: historia clínica, VistA, Epic · YottaDB
IMPER ; Imperativo y estructurado -- clase 108
read linea
set suma = 0
for i=1:1:$length(linea, " ") set suma = suma + $piece(linea, " ", i)
write "suma=", suma, !
quit
Lo que esta clase enseña en M. M es el lenguaje menos estructurado de esta página, y no por antigüedad —PL/I es dos años anterior y estaba mucho mejor estructurado— sino por una decisión de diseño: M no tiene bloques.
No hay begin/end, no hay llaves y no hay endif. Lo que hay es:
El postcondicional, que pega la condición al comando:
set:x>0 y=1 ; ejecuta el set SOLO si x>0
quit:x="" ; salir si está vacío
do:valido procesar
write:n>10 "muchos",!
El if sin else real, cuyo alcance es hasta el final de la línea:
if x>0 write "positivo" set y=1 ; las DOS cosas si x>0
Y else no es un bloque: es un comando que se ejecuta si el último if fue falso, apoyándose en
una variable del sistema, $test.
if x>0 write "positivo"
else write "no positivo"
$test es global al proceso, así que cualquier cosa que se ejecute entre el if y el else puede
cambiarlo, incluida una llamada a otra rutina. Es una de las construcciones más frágiles que existen
en un lenguaje en producción.
La estructuración llegó a medias con los niveles de punto:
for i=1:1:10 do
. set x = i * 2
. if x > 10 do
. . write x,!
Un punto por nivel de anidamiento, al principio de la línea. Es sangría con significado sintáctico, veinticinco años antes que Python, y con la fragilidad que cabe esperar: un punto de más o de menos cambia la estructura en silencio.
Y M sí tiene goto, con indirección (clase 085): goto @etiqueta, donde el destino se construye
como cadena en ejecución.
Es la única entrada de esta página donde hay que decirlo sin matices: M se quedó fuera de la revolución estructurada, y lo que lo mantiene vivo no es su modelo de programación sino su modelo de datos (clase 107).
Ficha completa · Banca, seguros, trading · Pharo
| suma |
suma := 0.
stdin nextLine substrings do: [ :cada |
suma := suma + cada asNumber ].
Transcript show: 'suma=', suma printString; cr.
Lo que esta clase enseña en Smalltalk. Smalltalk resuelve esta clase de una forma que la trasciende: no tiene estructuras de control, y por eso no tuvo que estructurarse.
Como se dijo en la clase 107, ifTrue:, whileTrue: y to:do: son mensajes, no sintaxis. Y esa
decisión tiene una consecuencia directa sobre el tema de esta clase: no hay goto que prohibir,
porque no hay sitio donde ponerlo.
x > 0 ifTrue: [ ... ] ifFalse: [ ... ]
[ x > 0 ] whileTrue: [ ... ]
[ x = 0 ] whileFalse: [ ... ]
1 to: 10 do: [ :i | ... ]
10 timesRepeat: [ ... ]
coleccion do: [ :cada | ... ]
Y ifTrue:ifFalse: está implementado en el propio lenguaje, con polimorfismo:
True >> ifTrue: b1 ifFalse: b2 ^b1 value
False >> ifTrue: b1 ifFalse: b2 ^b2 value
El condicional es despacho dinámico sobre dos clases con un objeto cada una. No hay salto condicional en el modelo: hay un mensaje que dos clases responden distinto. Es de las ideas más elegantes que se han puesto en un lenguaje de programación.
En la práctica, el compilador reconoce esos mensajes cuando el receptor es un booleano y los integra
en línea generando saltos reales — pero el modelo sigue siendo válido, y si envías ifTrue: a un
objeto propio que lo implemente, funciona.
Lo único que se parece a un salto no local es el retorno desde un bloque:
coleccion do: [ :cada | cada = objetivo ifTrue: [ ^cada ] ].
^nil
Ese ^ dentro de un bloque no sale del bloque: sale del MÉTODO que lo creó (clase 083). Es un
retorno no local, y está implementado desenrollando la pila hasta el contexto del método — con la
posibilidad de que ese contexto ya haya terminado, en cuyo caso se lanza BlockCannotReturn.
Es lo más parecido a un goto que tiene Smalltalk, y su nombre lo dice todo: no es un salto, es un
retorno que ocurre desde otro sitio.
Lo transferible: el GO TO no se prohibió por dogma, se abandonó porque impide razonar
localmente. Con saltos arbitrarios, para saber qué vale una variable en una línea hay que examinar
el programa entero; con bloques anidados, basta con lo que hay encima. Esa propiedad —poder razonar
sobre un trozo sin leer el resto— es lo que buscan también las funciones puras, la inmutabilidad y
los tipos. La programación estructurada fue la primera victoria de esa idea, y las demás clases de
esta parte son continuaciones de la misma batalla.
⏮️ Volver a la clase 108 · 🧬 Los primos del Atlas · 🧟 Índice de lenguajes vivos