⬅️ Volver a la clase 067 · 🧬 Primos del Atlas · 🧟 Índice de lenguajes vivos · 📚 Índice
Quedarse con los pares de una lista. Una comprensión dice qué quieres —"los elementos que cumplen esto"— en lugar de cómo recorrerlos. Y la pregunta de esta página es cuántos de estos lenguajes pueden expresarlo así, sin escribir el bucle. La respuesta sorprende: Fortran sí, y desde 1990.
🎯 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 describir una colección derivada en lugar de construirla paso a paso, y estos lenguajes lo enseñan por dos motivos opuestos. El primero: Fortran tiene
pack, una intrínseca que filtra un array con una máscara lógica, y constructores con bucle implícito[(expr, i = 1, n)]que son literalmente comprensiones. No fue un préstamo de los funcionales: llegó por la necesidad de vectorizar.El segundo: COBOL, RPG y PL/I no tienen nada de esto, y ver el bucle escrito a mano al lado de la versión de una línea es la mejor demostración de qué aporta la abstracción. Y Smalltalk enseña que
select:no es sintaxis: es un método deCollectionque cualquiera puede leer.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.
pares=<los pares unidos por -, en orden>pares = [x ∈ lista : x par]| stdin | esperado |
|---|---|
1 2 3 4 |
pares=2-4 |
10 15 20 |
pares=10-20 |
6 7 8 |
pares=6-8 |
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. PARES.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 LINEA PIC X(200).
01 I PIC 9(4) COMP-3.
01 LARGO PIC 9(4) COMP-3.
01 TOKEN PIC X(20).
01 TLEN PIC 9(4) COMP-3.
01 C PIC X.
01 V PIC S9(9) COMP-3.
01 ED-V PIC -(9)9.
01 TROZO PIC X(20).
01 TROZO-L PIC 9(4) COMP-3.
01 SEC PIC X(400).
01 PTR PIC 9(4) COMP-3.
PROCEDURE DIVISION.
ACCEPT LINEA
MOVE SPACES TO SEC
MOVE 1 TO PTR
MOVE SPACES TO TOKEN
MOVE 0 TO TLEN
COMPUTE LARGO = FUNCTION LENGTH(FUNCTION TRIM(LINEA)) + 1
PERFORM VARYING I FROM 1 BY 1 UNTIL I > LARGO
MOVE LINEA(I:1) TO C
IF C = SPACE
IF TLEN > 0
MOVE FUNCTION NUMVAL(TOKEN(1:TLEN)) TO V
IF FUNCTION MOD(V, 2) = 0
MOVE V TO ED-V
MOVE FUNCTION TRIM(ED-V) TO TROZO
COMPUTE TROZO-L = FUNCTION LENGTH(FUNCTION TRIM(TROZO))
IF PTR > 1
MOVE "-" TO SEC(PTR:1)
ADD 1 TO PTR
END-IF
MOVE TROZO(1:TROZO-L) TO SEC(PTR:TROZO-L)
ADD TROZO-L TO PTR
END-IF
MOVE SPACES TO TOKEN
MOVE 0 TO TLEN
END-IF
ELSE
ADD 1 TO TLEN
MOVE C TO TOKEN(TLEN:1)
END-IF
END-PERFORM
DISPLAY "pares=" FUNCTION TRIM(SEC)
STOP RUN.
Lo que esta clase enseña en COBOL. Treinta líneas para lo que en Perl es grep { !($_ % 2) }.
Y no es un defecto de COBOL: es exactamente la medida de lo que aporta una comprensión. Todo lo
que se ve aquí —el tokenizador, el índice del acumulador, el separador condicional— es cómo, y la
única línea que dice qué es la del MOD.
COBOL no tiene comprensiones, ni funciones de orden superior, ni colecciones de tamaño variable. Lo
más cerca que llega es SEARCH sobre una tabla, que expresa "encuentra el que cumpla" sin
escribir el bucle:
SEARCH ELEMENTO VARYING I
AT END DISPLAY "ninguno"
WHEN FUNCTION MOD(ELEMENTO(I), 2) = 0 DISPLAY "el primero par es " ELEMENTO(I)
END-SEARCH
Es un find_if, no un filter: devuelve el primero, no todos.
Y donde el COBOL de producción sí hace comprensiones es delegando en otro lenguaje:
EXEC SQL
SELECT IMPORTE INTO :WS-TABLA
FROM MOVIMIENTOS
WHERE MOD(IMPORTE, 2) = 0
END-EXEC
Ahí está el reparto real de trabajo en un sistema mainframe: COBOL lleva la lógica de negocio y SQL
lleva las operaciones sobre conjuntos. Es la misma división que en IBM i entre RPG y Db2, y explica
por qué a estos lenguajes nunca les hizo falta un filter.
Ficha completa · HPC, clima, física, BLAS/LAPACK · gfortran -O2 prog.f90
program pares
implicit none
character(len=1000) :: linea
integer :: v(200), n, i, ios
integer, allocatable :: filtrados(:)
character(len=:), allocatable :: sec
character(len=16) :: buf
read(*, '(A)') linea
n = 0
do i = 1, 200
read(linea, *, iostat=ios) v(1:i)
if (ios /= 0) exit
n = i
end do
! pack: se queda con los elementos donde la MÁSCARA es cierta.
filtrados = pack(v(1:n), mod(v(1:n), 2) == 0)
sec = ''
do i = 1, size(filtrados)
write(buf, '(I0)') filtrados(i)
if (i > 1) sec = sec // '-'
sec = sec // trim(buf)
end do
write(*, '(A,A)') 'pares=', sec
end program pares
Lo que esta clase enseña en Fortran. pack(v, mascara) es un filter, y está en el lenguaje
desde Fortran 90. mod(v, 2) == 0 sobre un array no devuelve un booleano: devuelve un array de
lógicos, la máscara, y pack se queda con los elementos donde es cierta.
Y no llegó por influencia de los lenguajes funcionales, sino por la necesidad de vectorizar: una
operación sobre el array completo se compila a instrucciones SIMD, y un bucle con if dentro no.
La familia es amplia y merece conocerse aunque no se programe en Fortran:
pack(v, v > 0) ! filter
unpack(comprimido, mascara, 0) ! la operación inversa
merge(a, b, mascara) ! elegir elemento a elemento
count(v > 0) ! cuántos cumplen
sum(v, mask = v > 0) ! ¡sumar SOLO los que cumplen!
maxval(v, mask = v < 100)
Ese mask = opcional en sum, product, maxval, minval y count es la comprensión completa
—filtrar y agregar— en una sola llamada.
Y el constructor con bucle implícito es la otra mitad, el map:
cuadrados = [(i * i, i = 1, 10)]
pares = [(2 * i, i = 1, n)]
filtrado = [(v(i), i = 1, n, 1)] ! y con una condición, vía pack
[(expresión, variable = inicio, fin)] es sintácticamente una comprensión de listas, con la
sintaxis de 1990 y sin haber tomado nada prestado de Haskell ni de Python.
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;
with Ada.Strings; use Ada.Strings;
with Ada.Strings.Fixed;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
procedure Pares is
Linea : String (1 .. 500);
Ultimo : Natural;
Pos : Positive := 1;
Fin : Positive;
Valor : Integer;
Sec : Unbounded_String := Null_Unbounded_String;
begin
Get_Line (Linea, Ultimo);
while Pos <= Ultimo loop
Get (Linea (Pos .. Ultimo), Valor, Fin);
if Valor mod 2 = 0 then
if Length (Sec) > 0 then
Append (Sec, "-");
end if;
Append (Sec, Ada.Strings.Fixed.Trim (Integer'Image (Valor), Both));
end if;
Pos := Fin + 1;
end loop;
Put_Line ("pares=" & To_String (Sec));
end Pares;
Lo que esta clase enseña en Ada. Ada 83, 95, 2005 y 2012 no tienen comprensiones: el bucle se escribe. Pero Ada 2022 sí las añadió, y su forma es notablemente limpia:
-- Comprensión con filtro (Ada 2022)
Pares : constant Vector := [for E of Datos when E mod 2 = 0 => E];
-- Con transformación
Cuadrados : constant Vector := [for I in 1 .. 10 => I * I];
-- Y sobre un mapa
Nombres : constant Map := [for C of Clientes => C.Id => C.Nombre];
[for ... when ... => ...] es exactamente la comprensión de Python con otra puntuación, y llegó a un
lenguaje de 1983 en 2022.
Fíjate en el constant: eso es lo que hace valiosa la comprensión en Ada, y es el mismo argumento de
la clase 060. Con un bucle, la colección se declara vacía y se rellena, así que no puede ser
constante. Con la comprensión, se construye completa en la declaración y queda sellada. La
expresividad no es el objetivo principal — lo es poder declarar más cosas inmutables.
Mientras tanto, la biblioteca estándar ofrece los contenedores genéricos —Ada.Containers.Vectors,
Doubly_Linked_Lists, Hashed_Maps, Ordered_Sets— con operaciones como Iterate y Query_Element
que cubren el recorrido, y con Ada.Containers.Generic_Array_Sort para ordenar.
Ficha completa · Escritorio empresarial, TPV, industria (vía Delphi y Free Pascal) · fpc -Mobjfpc prog.pas
program Pares;
{$MODE OBJFPC}{$H+}
uses SysUtils;
var
Linea, Token, Sec: string;
I, V: Integer;
begin
ReadLn(Linea);
Linea := Trim(Linea) + ' ';
Sec := '';
Token := '';
for I := 1 to Length(Linea) do
begin
if Linea[I] = ' ' then
begin
if Token <> '' then
begin
V := StrToInt(Token);
if (V mod 2) = 0 then
begin
if Sec <> '' then Sec := Sec + '-';
Sec := Sec + IntToStr(V);
end;
Token := '';
end;
end
else
Token := Token + Linea[I];
end;
WriteLn('pares=', Sec);
end.
Lo que esta clase enseña en Pascal. Pascal no tiene comprensiones ni funciones de orden superior en su forma clásica, y este bucle es el resultado. Es el mismo trabajo manual que en COBOL, con menos ceremonia.
Lo que sí tiene, y lleva desde 1970, son los conjuntos de la clase 062:
if C in ['a'..'z', 'A'..'Z'] then ...
Un conjunto es la mitad de una comprensión: expresa la pertenencia sin bucle, aunque no permite derivar una colección de otra. Y está limitado a tipos ordinales pequeños, porque se implementa como máscara de bits.
Delphi y Free Pascal modernos añadieron genéricos y métodos anónimos, y con ellos aparecieron bibliotecas que sí traen el vocabulario funcional:
uses Spring.Collections; { biblioteca de la comunidad }
Pares := Datos.Where(function(const X: Integer): Boolean
begin Result := X mod 2 = 0; end);
La verbosidad de esa lambda —function(const X: Integer): Boolean begin ... end— explica por qué el
estilo funcional nunca arraigó en el mundo Pascal: sin sintaxis ligera para las funciones anónimas,
el bucle explícito sale más corto. Es la misma razón por la que Java tardó hasta la versión 8 en
adoptarlo y C++ hasta C++11: la comprensión necesita que pasar código sea barato de escribir.
Ficha completa · IA simbólica, CAD, investigación · sbcl --script prog.lisp
(let ((lista (loop for v = (read *standard-input* nil :fin)
until (eq v :fin)
collect v)))
(format t "pares=~{~D~^-~}~%" (remove-if-not #'evenp lista)))
Lo que esta clase enseña en Common Lisp. Lisp tiene dos formas de expresar esta clase, y las dos son de biblioteca, no de sintaxis.
La primera son las funciones de secuencia, que funcionan sobre listas, vectores y cadenas por igual:
(remove-if-not #'evenp lista) ; los que cumplen
(remove-if #'evenp lista) ; los que NO
(mapcar #'1+ lista) ; map
(count-if #'plusp lista)
(find-if #'zerop lista)
(sort (copy-seq lista) #'<)
La convención -if / -if-not recorre toda la biblioteca y es muy uniforme: remove-if,
delete-if, count-if, find-if, position-if, substitute-if.
La segunda es loop con collect y when, que es literalmente una comprensión:
(loop for x in lista when (evenp x) collect x)
(loop for x in lista collect (* x x))
(loop for x in lista for y in otra collect (+ x y)) ; dos listas a la vez
(loop for x in lista when (plusp x) sum x) ; filtrar Y agregar
for ... when ... collect es exactamente [x for x in lista if x % 2 == 0] de Python, con otra
puntuación y quince años antes.
Y —esto es lo importante— loop es una macro. Alguien implementó un lenguaje de comprensión
entero como biblioteca, sin tocar el compilador. Es la respuesta de Lisp a por qué nunca necesitó que
el comité añadiera comprensiones: cuando aparecen, se escriben.
Ficha completa · Diseño de chips (EDA), redes, testing · tclsh prog.tcl
gets stdin linea
set pares {}
foreach v [split [string trim $linea]] {
if {$v % 2 == 0} {
lappend pares $v
}
}
puts "pares=[join $pares -]"
Lo que esta clase enseña en Tcl. Tcl 8.6 añadió lmap, que es el map con sintaxis de
bucle, y con él la comprensión se escribe en una línea:
set pares [lmap v [split [string trim $linea]] {
expr {$v % 2 == 0 ? $v : [continue]}
}]
Ese [continue] dentro de lmap es el truco idiomático para filtrar: continue salta la
iteración y no añade nada al resultado. Es feo y es la única forma, porque lmap es un map y
Tcl no tiene lfilter.
Para filtrar de verdad, la biblioteca Tcllib trae struct::list:
package require struct::list
set pares [struct::list filter $lista {apply {{v} {expr {$v % 2 == 0}}}}]
set dobles [struct::list map $lista {apply {{v} {expr {$v * 2}}}}]
set total [struct::list fold $lista 0 {apply {{a b} {expr {$a + $b}}}}]
La verbosidad de {apply {{v} {...}}} es la misma barrera que en Pascal: sin sintaxis ligera para
las funciones anónimas, el bucle explícito gana. Por eso el foreach de este programa es lo que un
programador de Tcl escribiría de verdad.
Y hay una tercera vía muy propia del lenguaje, que aprovecha que las listas son cadenas: para
operaciones sobre listas grandes, lsearch -all -inline con -regexp filtra sin bucle y en C.
Ficha completa · Sysadmin, texto, bioinformática · perl prog.pl
use strict;
use warnings;
my $linea = <STDIN>;
chomp $linea;
my @pares = grep { $_ % 2 == 0 } split ' ', $linea;
print "pares=", join('-', @pares), "\n";
Lo que esta clase enseña en Perl. grep { condición } lista es el filtro, y su nombre viene
directamente de la herramienta de Unix. Junto a map forma el vocabulario básico, y las dos se
encadenan de derecha a izquierda como las tuberías del shell:
my @resultado = map { $_ * 2 }
grep { $_ % 2 == 0 }
split ' ', $linea;
Se lee de abajo arriba: parte, filtra, transforma. Es una comprensión sin sintaxis especial, porque
en Perl un bloque es un argumento normal y no hace falta escribir sub { }.
Dos detalles importantes de esta clase. El primero: $_ en grep y map es un alias, no una
copia, igual que en el foreach de la clase 064. Modificarlo dentro del bloque cambia la lista
original:
my @x = map { $_ * 2 } @lista; # correcto: no toca @lista
my @y = map { $_ *= 2 } @lista; # ¡MODIFICA @lista!
El segundo: map puede devolver cualquier número de elementos por entrada, no solo uno. Ese es
su superpoder frente al map de otros lenguajes:
my @pares = map { $_ % 2 == 0 ? ($_) : () } @lista; # map haciendo de filtro
my %h = map { $_ => 1 } @lista; # lista -> hash
my @dobles = map { ($_, $_) } @lista; # cada uno DOS veces
Devolver la lista vacía elimina el elemento; devolver dos lo duplica. map de Perl es en realidad un
flatMap, y por eso puede hacer de filter y de constructor de hash a la vez.
Ficha completa · Videojuegos, navegadores, finanzas, HPC · g++ -std=c++17 prog.cpp
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>
int main() {
std::string linea;
if (!std::getline(std::cin, linea)) return 1;
std::istringstream iss(linea);
const std::vector<int> v{std::istream_iterator<int>(iss),
std::istream_iterator<int>()};
std::string sec;
for (int x : v) {
if (x % 2 != 0) continue;
if (!sec.empty()) sec += '-';
sec += std::to_string(x);
}
std::cout << "pares=" << sec << '\n';
return 0;
}
Lo que esta clase enseña en C++. Hasta C++20 no había comprensiones, y la forma de la STL era
std::copy_if con un iterador de inserción:
std::vector<int> pares;
std::copy_if(v.begin(), v.end(), std::back_inserter(pares),
[](int x) { return x % 2 == 0; });
Funciona y es genérico, y tiene un problema: cada paso materializa un vector. Encadenar filtrar, transformar y volver a filtrar crea tres vectores intermedios.
C++20 lo resolvió con los rangos y su operador de tubería, que es la comprensión de C++:
#include <ranges>
auto pares = v | std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * 2; });
for (int x : pares) { ... } // NADA se ha calculado hasta aquí
Esas vistas son perezosas y componibles: no construyen ningún contenedor intermedio, y el recorrido final aplica las dos operaciones en una sola pasada. Es la fusión de bucles de la ficha de Fortran, obtenida en la biblioteca mediante plantillas.
Y esa es la razón de que los rangos se consideren el cambio más importante de C++20: no añaden
capacidad —copy_if ya filtraba— sino que eliminan el coste de componer, que es lo que hacía que
en la práctica la gente escribiera bucles a mano.
Compilado con -std=c++17, este programa usa el bucle. Es exactamente la diferencia.
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 PARES;
linea varchar(200) const;
end-pi;
dcl-s trozos varchar(20) dim(50);
dcl-s i int(10);
dcl-s v int(10);
dcl-s sec varchar(500) inz('');
dcl-s salida char(520);
trozos = %split(linea : ' ');
for i = 1 to %elem(trozos);
v = %int(trozos(i));
if %rem(v : 2) = 0;
if sec <> '';
sec += '-';
endif;
sec += %char(v);
endif;
endfor;
salida = 'pares=' + sec;
dsply salida;
*inlr = *on;
return;
Lo que esta clase enseña en RPG. RPG no tiene comprensiones ni funciones de orden superior: no hay forma de pasar un bloque de código como argumento. El filtro se escribe.
Y como en COBOL, el filtrado de verdad en IBM i se delega en SQL:
exec sql
declare c1 cursor for
select importe from movimientos
where mod(importe, 2) = 0
order by fecha;
exec sql open c1;
dow sqlcode = 0;
exec sql fetch c1 into :importe;
...
enddo;
Ese reparto —RPG para la lógica, SQL para los conjuntos— es la arquitectura estándar de la plataforma
desde hace veinte años, y es el motivo de que a RPG nunca le hiciera falta un filter: cuando los
datos están en una base de datos integrada en el sistema operativo, la comprensión la escribe el
motor.
Y ahí hay una idea que va más allá de RPG: SQL es una comprensión de listas. SELECT x FROM t
WHERE p es exactamente [x for x in t if p], y la cláusula SELECT es el map. La diferencia es
que el optimizador puede elegir la estrategia —índice, recorrido, orden de los filtros—, que es justo
el argumento de esta clase sobre separar el qué del cómo.
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
pares: procedure options(main);
declare linea character(200) varying;
declare trozo character(20) varying;
declare sec character(500) varying initial('');
declare (i, p, v) fixed binary(31);
get edit (linea) (a(200));
linea = trim(linea) || ' ';
p = 1;
do i = 1 to length(linea);
if substr(linea, i, 1) = ' ' then do;
if i > p then do;
trozo = substr(linea, p, i - p);
v = trozo;
if mod(v, 2) = 0 then do;
if sec ^= '' then sec = sec || '-';
sec = sec || trim(char(v));
end;
end;
p = i + 1;
end;
end;
put skip list ('pares=' || sec);
end pares;
Lo que esta clase enseña en PL/I. PL/I no tiene comprensiones, pero sí tiene operaciones sobre arrays completos, heredadas de FORTRAN, y con ellas se puede expresar buena parte de esta clase sin bucle:
declare v(100) fixed binary(31);
declare mascara(100) bit(1);
v = v * 2; /* map sobre todo el array */
mascara = (mod(v, 2) = 0); /* la máscara, elemento a elemento */
total = sum(v);
if any(mascara) then ...
cuantos = sum(binary(mascara)); /* contar los ciertos */
Lo que falta es pack: PL/I puede calcular la máscara pero no comprimir el array quedándose solo
con los seleccionados. Esa intrínseca es específica de Fortran 90, y es justo la pieza que convierte
las operaciones de array en un filter de verdad.
PL/I tiene además una construcción para arrays que casi nadie recuerda y que viene al caso:
declare v(10) fixed binary(31) initial((10) 0); /* factor de repetición */
declare w(5) fixed binary(31) initial(1, 2, 3, 4, 5);
(10) 0 significa "diez ceros". Es un constructor de array con repetición, la mitad de un
constructor por comprensión.
Y sobre v = trozo en el programa: es otra vez la conversión implícita de texto a número de la clase
050, funcionando en silencio hasta que el texto no es numérico.
Ficha completa · Sanidad: historia clínica, VistA, Epic · YottaDB
PARES ; Comprension -- clase 067
read linea
set sec = ""
for i = 1:1:$length(linea, " ") do
. set v = $piece(linea, " ", i)
. quit:v#2'=0
. set:sec'="" sec = sec _ "-"
. set sec = sec _ v
write "pares=", sec, !
quit
Lo que esta clase enseña en M. Fíjate en quit:v#2'=0 dentro del bloque do: ese quit
no sale de la rutina, sale de la iteración actual. Es el continue de M, escrito como un
postcondicional.
Esa es la forma idiomática de filtrar en M: no hay filter, hay un quit condicional al principio
del cuerpo del bucle que descarta lo que no interesa. Se lee como una guarda —tema de la clase 058—
aplicada a cada vuelta.
M no tiene funciones de orden superior en el sentido habitual, pero tiene indirección, que da algo parecido:
set filtro = "v#2=0"
for i = 1:1:n do
. set v = $piece(linea, " ", i)
. quit:'@filtro ; @ EVALÚA la cadena como código
. ...
@ es el operador de indirección: toma una cadena y la ejecuta como si fuera código. Con él se
puede pasar una condición —o un nombre de rutina— como dato, que es la mitad de lo que hace una
función de orden superior.
Es enormemente flexible y tiene el coste que cabe esperar: un programa con indirección no se puede
analizar estáticamente, porque qué se ejecuta se decide en tiempo de ejecución. Es el eval de
JavaScript con cincuenta años más, y con los mismos problemas de seguridad si la cadena viene de
fuera.
Ficha completa · Banca, seguros, trading · Pharo
| pares |
pares := (stdin nextLine substrings collect: [ :cada | cada asNumber ])
select: [ :cada | cada even ].
Transcript
show: 'pares=', ((pares collect: [ :c | c printString ])
inject: '' into: [ :acc :s | acc isEmpty ifTrue: [ s ] ifFalse: [ acc , '-' , s ] ]);
cr.
Lo que esta clase enseña en Smalltalk. select: es el filtro y collect: el map, y los dos son
métodos de Collection que puedes abrir y leer:
Collection >> select: unBloque
| resultado |
resultado := self species new.
self do: [ :cada | (unBloque value: cada) ifTrue: [ resultado add: cada ] ].
^ resultado
Ocho líneas. No hay sintaxis de comprensión porque no hace falta: con bloques baratos de escribir
y do: implementado, todo el vocabulario funcional es biblioteca.
El protocolo completo es amplio y muy uniforme:
coleccion select: [ :x | ... ] "filter"
coleccion reject: [ :x | ... ] "filter negado"
coleccion collect: [ :x | ... ] "map"
coleccion detect: [ :x | ... ] ifNone: [ ... ]
coleccion inject: 0 into: [ :a :b | ... ] "reduce"
coleccion count: [ :x | ... ]
coleccion anySatisfy: [ :x | ... ]
coleccion groupedBy: [ :x | ... ] "agrupar en un diccionario"
coleccion sorted: [ :a :b | a < b ]
Y self species new en la implementación es un detalle elegante: el resultado es del mismo tipo que
el receptor. select: sobre un Set devuelve un Set, sobre una OrderedCollection devuelve una
OrderedCollection, y sobre un String devuelve un String. Es lo que en C++ se consigue con
plantillas y en Java no se consigue del todo.
Lo transferible: una comprensión separa el qué del cómo, y eso permite que el "cómo" cambie sin
tocar tu código. pack de Fortran puede vectorizarse, select: de Smalltalk puede estar
implementado con un índice, y un filter perezoso puede no materializar nada. Cuando escribes el
bucle a mano, congelas la estrategia. Es la misma razón por la que se prefiere SQL a recorrer una
tabla: declarar deja margen a quien ejecuta.
⏮️ Volver a la clase 067 · 🧬 Los primos del Atlas · 🧟 Índice de lenguajes vivos