- Este texto presenta una narración satírica en un contexto de entrevista donde un candidato intenta implementar el simple problema de FizzBuzz usando combinadores de funciones puras basados en cálculo lambda
- El protagonista empieza con JavaScript, pero poco a poco define los combinadores S, K e I, reconstruyendo por su cuenta la estructura básica del lenguaje
- Luego expresa booleanos, números, listas y cadenas usando solo combinadores, e implementa recursión mediante el combinador Y
- Finalmente completa FizzBuzz en un estilo totalmente point-free, pero el código crece hasta volverse incomprensible para los humanos
- El texto explora con humor la esencia de los lenguajes de programación y el extremo de la abstracción, dejando ver una autocrítica satírica de la cultura de desarrolladores
El inicio de la entrevista: FizzBuzz y combinadores
- La historia comienza con la entrevistadora Dana pidiéndole al candidato que resuelva el problema de FizzBuzz
- En lugar de usar un bucle convencional, el candidato empieza definiendo los combinadores S y K
- Dana se desconcierta, pero el candidato insiste en que “solo falta un poco más” y sigue adelante
- El candidato define varios combinadores como I, B, C, W y T, y los usa como componentes básicos de un nuevo lenguaje
- Cada combinador implementa conceptos clave del cálculo lambda como composición de funciones, intercambio de argumentos y autoaplicación
- En los comentarios del código aparecen alias de nombres de combinadores como “Bluebird, Cardinal, Warbler, Thrush”
Definición de booleanos y números
- El candidato define TRUE, FALSE y NOT usando solo combinadores, construyendo así la lógica booleana
- Dana pregunta si eso es “cálculo lambda”, pero el candidato responde que “el cálculo lambda es demasiado voluminoso”
- Después define ZERO, SUCC, PRED e IS_ZERO, construyendo un sistema numérico (numerales de Church)
SUCC implementa el sucesor, PRED el predecesor y DECREMENT una operación de decremento que no baja de 0
- Define secuencialmente los números del
ONE al TEN
Recursión y el combinador Y
- El candidato define la función ADD y gradualmente la transforma a una forma point-free
- Define
ADD_MAKER y lo envuelve con el combinador Y para hacer posible la recursión
- Dana señala que “en JavaScript también se puede hacer recursión”, pero el candidato responde que “pronto ya no será JavaScript”
- Cuando el eager evaluation de JavaScript provoca un desbordamiento de pila, el candidato ejecuta el código en Skoobert, un intérprete de JS “perezoso”
- El resultado imprime exitosamente
3
Aritmética, listas y cadenas
- El candidato implementa todas las operaciones aritméticas, como SUBTRACT, MULTIPLY, MOD y DIVIDE, basándose en combinadores
- Cada operación se construye combinando recursivamente
IS_ZERO, PRED, SUCC, etc.
- Luego define CONS, FIRST, REST y EMPTY, construyendo una estructura de listas
- También implementa en forma de combinadores operaciones funcionales de orden superior como
MAP, FOLD, RANGE y CONCAT
- Para imprimir listas como cadenas, escribe las funciones
renderList y showLines
- Dana parece haberse rendido y ya no reacciona
Construcción de caracteres y cadenas
- El candidato define códigos de caracteres del 1 al 9 y luego en unidades de 10 usando combinadores
- Expresa desde
CHAR_A hasta CHAR_Z, y desde CHAR_0 hasta CHAR_9, completamente como combinaciones numéricas
- Mediante la función
ARRAY, convierte cadenas en listas y genera FIZZ, BUZZ y FIZZBUZZ
- Con la función
extractString, transforma la lista en una cadena real
- El resultado en consola es
"fizzbuzz"
Conversión de números a cadenas
- Define
NUMBER_TO_DIGIT_LIST para convertir números en una lista de dígitos, y NUMBER_TO_STRING para transformarla en cadena
- Usa
DIVIDE, MOD y TEN para separar cada dígito
- Realiza el mapeo a caracteres numéricos mediante la lista
DIGITS_NUMERAL
- Con este proceso, la conversión número → carácter → cadena queda completamente dentro del sistema de combinadores
La culminación de FizzBuzz
- Define
FIFTEEN y ONE_HUNDRED, y usa MAP y RANGE para generar una lista del 1 al 100
- Para cada número, determina si es múltiplo de 3, 5 o 15 y produce
"Fizz", "Buzz", "FizzBuzz" o la cadena del número
- Imprime el resultado final con
showLines(extractString)(FIZZBUZZ_RESULT)
- Dana pregunta “¿ya estás satisfecho?”, pero el candidato elimina todas las definiciones de variables y reescribe el código usando solo combinadores puros
- El resultado es una expresión gigantesca compuesta únicamente por S y K que abarca miles de líneas
Desenlace y significado
- El texto explora los componentes fundamentales de los lenguajes de programación y, al mismo tiempo, satiriza la tendencia de los desarrolladores a complicar en exceso problemas simples
- El título “programar con menos que nada” simboliza el intento de reconstruir el mundo desde las unidades mínimas del lenguaje
- Esta obra es literatura técnica que desarrolla con humor y narrativa una comprensión profunda de la programación funcional, el cálculo lambda y la lógica de combinadores
- A la vez, retrata satíricamente “el espíritu desarrollador que convierte hasta FizzBuzz en un experimento filosófico”
1 comentarios
Opinión en Hacker News
Como parece que la gente está pensando “¿qué demonios es esto?”, voy a explicar el punto de los combinators
Un combinator es una función que no modifica estado global ni captura variables externas. Si le das los mismos argumentos, siempre produce el mismo resultado y no afecta en nada al resto del sistema
Si compones estas funciones puras, obtienes otro combinator. Es decir, la composición de funciones puras sigue siendo una función pura
Estas funciones también se pueden escribir fácilmente en ensamblador o en C. Por ejemplo, si implementas directamente S y K en x64 y compilas Common Lisp sobre esa base usando combinators, al final Lisp estaría corriendo sobre dos funciones de ensamblador
El rendimiento no sería muy bueno, pero si haces unos cientos de patrones de uso frecuente como combinators inline y les pones nombre, se vuelve una “máquina” bastante práctica
Esta estructura también se parece a Forth, que le teme a la mutación, a una VM de bytecode, o a una CPU donde a los combinators se les llama “instrucciones”
Al final, se puede decir que un combinator es una expresión de ciencias de la computación aplicada que ignora al máximo los detalles de implementación. Por eso se puede decir que es más simple que el cálculo lambda
Si implementas el cálculo lambda con unos cuantos combinators y montas Lisp encima, se reduce de forma extrema la cantidad de trabajo dependiente de la máquina que hay que hacer en la parte más baja del stack
Solo hay que combinar esto unas cuantas veces
“Jamás uso cosas como cálculo lambda. Es un lenguaje demasiado inflado.”
Pero en realidad la lógica combinatoria está todavía más inflada. Se necesitan más bits para expresar el mismo programa
El cálculo lambda, de hecho, es uno de los lenguajes más concisos que existen
Incluso en un lenguaje eager como JavaScript se puede volver lazy envolviendo los argumentos en lambdas dummy. Vean este intérprete JS de BLC como ejemplo
Para artículos relacionados, vean este PDF y el ejemplo de IOCCC
“¿Conoces FizzBuzz?”
“Claro. ¿La versión code golf de 10 caracteres, la versión de 12 líneas que hasta un junior puede entender, o la versión totalmente absurda para presumir conocimiento raro?”
Link al resultado
La explicación de la lógica combinatoria estuvo buenísima. El estilo del texto me recordó esta serie
Me recordó un artículo que leí hace tiempo: “FizzBuzz en TensorFlow”
Link
Este marco de entrevista de programación me parece medio equivocado
El objetivo de una entrevista es (1) comprobar el conocimiento de programación del candidato y (2) verificar si encaja con la cultura de programación de la empresa
Por ejemplo, si estás contratando para un puesto de JavaScript y alguien está profundamente metido en lógica combinatoria, probablemente no haya mucho encaje cultural. Así que no sería raro descartarlo
Ya va siendo hora de recordar a Moses Schönfinkel
Inventó la lógica combinatoria en 1920, cuando era alumno de Hilbert. Después de volver a Rusia, sufrió una enfermedad mental y murió en la pobreza en Moscú. Según Wikipedia, sus artículos fueron quemados por los vecinos para usarlos como calefacción
El último bloque de código es tan grande que genera scroll (166KB)
S y K son funciones curried, así que reciben un solo argumento a la vez
Para más detalles, vean esta respuesta de StackOverflow
Me encantan esos nombres de aves como “Bluebird, cardinal, warbler, thrush”. Quiero adoptarlos como nueva convención de nombres
“Barendregt. Church es demasiado mainstream.”
“Pronto ya ni siquiera será JavaScript.”
Se siente como si Douglas Adams estuviera enseñando SICP
“Entonces… ¿ese es el Y combinator?”
“Exacto. Lo necesitas para hacer recursión.”
Dato curioso: hay una cantidad infinita de combinadores de punto fijo (fixed-point combinator). O sea, también hay infinitas maneras de implementar recursión sin usar el Y combinator