2 puntos por GN⁺ 2025-10-24 | 1 comentarios | Compartir por WhatsApp
  • 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

 
GN⁺ 2025-10-24
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

    • Siento como si en algún lado hubiera una función llamándose a sí misma y hubiera recibido una seed round
    • Hice algo parecido cuando era estudiante (no era Lisp, sino un lenguaje simple que expandía macros a cálculo lambda), y descubrir que se podía implementar todo solo con S y K fue realmente impactante. Pero al mismo tiempo también me sorprendió lo ineficiente que era. Aun así, mientras más grande se vuelve el conjunto de instrucciones, mucho mejor se pone la situación
    • En teoría todo esto es posible, pero en la práctica hay que elegir una base computacional mucho más práctica que la reescritura de grafos. A menos que estés experimentando con los límites de la computabilidad, esto es casi un truco de fiesta. Además, la representación de números también es un problema. Como mínimo, habría que usar enteros en complemento a dos y destructores eficientes para que resulte admirable
    • Me pregunto si realmente existe algún Lisp implementado sobre combinators de esta manera. Si existe, portarlo podría ser bastante divertido
  • let A = (x) => (y) => (z) => x(z)(y((w) => z))
    

    Solo hay que combinar esto unas cuantas veces

    let K = A(A(A))(A(A(A))(A)(A)(A)(A)(A);
    let S = A(A(A(A)(A(A)(A(A)))(A(A(A(A)((A(A)))))))(A)(A);
    

    “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

    • Lo interesante es que algunos de los intérpretes de cálculo lambda más rápidos (por ejemplo, uni.c) al final calculan convirtiendo las expresiones lambda en lógica combinatoria. Solo que usan un conjunto base más grande que S y K
    • “bloated language” se refiere a un lenguaje con muchas funciones innecesarias. Por ejemplo, C++ puede producir código más corto que C, pero sigue siendo un lenguaje mucho más complejo
    • Al ver ese bloque de código, los sonidos que me salen de la boca casi coinciden con la lista de argumentos
    • Parece que hay un error de paréntesis en las definiciones de K y S. La versión corregida es esta
      let K = A(A(A))(A(A(A))(A)(A)(A)(A)(A));
      let S = A(A(A(A)(A(A)(A(A)))(A(A(A(A)(A(A)))))))(A)(A);
      
      Y volver lazy un lenguaje eager se puede hacer de esta manera
      let A = x => y => z => () => x()(z())(y()(w => z()));
      
    • Me quedé con la duda de “¿qué es w?”
  • “¿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?”

    • Después de ver esto, implementé el FizzBuzz más rápido en C64 BASIC. Desperdicié la mañana de una forma divertida
      Link al resultado
  • La explicación de la lógica combinatoria estuvo buenísima. El estilo del texto me recordó esta serie

    • Más que una explicación, se siente como una demostración performática. Aun así, estuvo bastante impresionante
    • Definitivamente parece inspirado en esa serie. Habría sido mejor si hubieran mencionado el nombre del autor
    • En el Reino Unido está bloqueado por la Online Safety Act, así que qué lástima
  • Me recordó un artículo que leí hace tiempo: “FizzBuzz en TensorFlow
    Link

    • Me gustó que este texto al menos sí se podía seguir
  • 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

    • Supongo que hace referencia a esta serie. Solo que no se indica explícitamente en el texto original
    • A veces siento que los comentaristas de HN son personas que ya olvidaron divertirse
    • Está bien enfatizar la diversidad de la programación, pero eso no deja de ser una forma de contratar especialización en cierto ecosistema. La mayoría de las veces es una decisión para trabajar con código legado o aprovechar un ecosistema existente
    • Frases como “si tu IQ es bajo quedas fuera” dan risa, pero al final, si pagas lo suficiente, uno puede adaptarse a cualquier estilo OOP/FP/FRP
    • En la realidad, este tipo de entrevista ideal casi no existe. La mayoría son entrevistadores frustrados imponiendo su propia visión del mundo. El trabajo real casi no tiene relación con lo que sale en la entrevista
  • 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

    • Lo más chistoso fue el momento en que el resaltado de sintaxis se rindió mientras hacía scroll hacia abajo
  • 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

    • Los nombres de aves vienen de 『To Mock a Mockingbird』 de Smullyan. Ahí aparecen muchas más aves
  • “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