El desafío es escribir un intérprete para el cálculo lambda sin tipo en la menor cantidad de caracteres posible. Definimos el cálculo lambda sin tipo de la siguiente manera:
Sintaxis
Existen los siguientes tres tipos de expresiones:
Una expresión lambda tiene la forma
(λ x. e)
dondex
podría ser cualquier nombre de variable legal ye
cualquier expresión legal. Aquíx
se llama parámetro ye
se llama cuerpo de función.En aras de la simplicidad, agregamos la restricción adicional de que no debe haber una variable con el mismo nombre que
x
actualmente está en el alcance. Una variable comienza a estar dentro del alcance cuando su nombre aparece entre(λ
y.
y deja de estar dentro del alcance en el correspondiente)
.- La aplicación de función tiene la forma
(f a)
dondef
ya
son expresiones legales. Aquíf
se llama función ya
se llama argumento. - Una variable tiene la forma
x
dondex
es un nombre de variable legal.
Semántica
Una función se aplica reemplazando cada aparición del parámetro en el cuerpo de las funciones con su argumento. Más formalmente una expresión de la forma ((λ x. e) a)
, donde x
es un nombre de variable y e
y a
son expresiones, evalúa (o reduce) la expresión e'
, donde e'
es el resultado de reemplazar cada aparición de x
en e
con a
.
Una forma normal es una expresión que no puede evaluarse más.
El reto
Su misión, si elige aceptarla, es escribir un intérprete que tome como entrada una expresión del cálculo lambda sin tipo que no contenga variables libres y produzca como salida la forma normal de la expresión (o una expresión alfa-congruente con ella) . Si la expresión no tiene forma normal o no es una expresión válida, el comportamiento es indefinido.
La solución con el menor número de caracteres gana.
Un par de notas:
- La entrada puede leerse desde stdin o desde un nombre de archivo dado como argumento de línea de comando (solo necesita implementar uno u otro, no ambos). La salida va a stdout.
- Alternativamente, puede definir una función que tome la entrada como una cadena y devuelva la salida como una cadena.
- Si los caracteres que no son ASCII son problemáticos para usted, puede usar el carácter de barra diagonal inversa (
\
) en lugar de λ. - Contamos el número de caracteres, no bytes, por lo que incluso si su archivo fuente está codificado como ico unicode cuenta como un carácter.
- Los nombres de variables legales consisten en una o más letras minúsculas, es decir, caracteres entre a y z (no es necesario admitir nombres alfanuméricos, letras mayúsculas o letras no latinas, aunque hacerlo no invalidará su solución, por supuesto).
- En lo que respecta a este desafío, no hay paréntesis opcionales. Cada expresión lambda y cada aplicación de función estarán rodeadas exactamente por un par de paréntesis. Ningún nombre de variable estará rodeado de paréntesis.
- Azúcar sintáctico como escribir
(λ x y. e)
para(λ x. (λ y. e))
no necesita ser apoyada. - Si se requiere una profundidad de recursión de más de 100 para evaluar una función, el comportamiento es indefinido. Eso debería ser más que lo suficientemente bajo como para implementarse sin optimización en todos los idiomas y aún lo suficientemente grande como para poder ejecutar la mayoría de las expresiones.
- También puede suponer que el espaciado será como en los ejemplos, es decir, sin espacios al principio y al final de la entrada o antes de
λ
ao.
y exactamente un espacio después de ay.
entre una función y su argumento y después de aλ
.
Muestra de entrada y salida
Entrada:
((λ x. x) (λ y. (λ z. z)))
Salida:
(λ y. (λ z. z))
Entrada:
(λ x. ((λ y. y) x))
Salida:
(λ x. x)
Entrada:
((λ x. (λ y. x)) (λ a. a))
Salida:
(λ y. (λ a. a))
Entrada:
(((λ x. (λ y. x)) (λ a. a)) (λ b. b))
Salida:
(λ a. a)
Entrada:
((λ x. (λ y. y)) (λ a. a))
Salida:
(λ y. y)
Entrada:
(((λ x. (λ y. y)) (λ a. a)) (λ b. b))
Salida:
(λ b. b)
Entrada:
((λx. (x x)) (λx. (x x)))
Salida: cualquier cosa (este es un ejemplo de una expresión que no tiene forma normal)
Entrada:
(((λ x. (λ y. x)) (λ a. a)) ((λx. (x x)) (λx. (x x))))
Salida:
(λ a. a)
(Este es un ejemplo de una expresión que no se normaliza si evalúa los argumentos antes de la llamada a la función, y lamentablemente un ejemplo para el que falla mi intento de solución)Entrada:
((λ a. (λ b. (a (a (a b))))) (λ c. (λ d. (c (c d)))))
Salida:
`(λ a. (λ b. (a (a (a (a (a (a (a (a b))))))))))
Esto calcula 2 ^ 3 en números de la Iglesia.
(\y. a)
.Respuestas:
El más nuevo:
Lo reduje a 644 caracteres , factoricé partes de cEll en Copy y Par; llamadas en caché a la celda y cdr en variables locales temporales, y movió esas variables locales a globales en funciones "terminales" (es decir, no recursivas). Además, las constantes decimales son más cortas que los literales de caracteres y este asunto desagradable ...
... identifica correctamente letras minúsculas (suponiendo ASCII), pero también acepta cualquiera de `{|} ~. (Esta misma observación sobre ASCII se hace en este excelente video sobre UTF-8 ).
Et viola: |
Más temprano:
¿Puedo obtener algunos votos por esfuerzo? He estado trabajando en esto día y noche durante una semana. Saqué el artículo original de McCarthy y tuve una plaga de insectos en el periódico hasta que leí el apéndice de Las raíces de Lisp de Paul Graham . Estaba tan distraído que me encerré fuera de mi casa, luego me olvidé por completo hasta llegar a casa esa noche a las 12:30 (un poco tarde para llamar al administrador del edificio que vive en el condado), y tuve que pasar el tiempo noche en casa de mi abuela (cortando hasta que la batería de mi portátil estaba seca)
Y después de todo eso, ¡ni siquiera está cerca de la entrada ganadora!
No estoy seguro de cómo hacer esto más corto; ¡Y he usado todos los trucos sucios que se me ocurren! Quizás no se pueda hacer en C.
Con algo de generosidad en el conteo (el primer fragmento toma una secuencia e imprime el resultado), son
778770709694 caracteres. Pero para que sea independiente, tiene que tener esasbrk
llamada. Y para manejar expresiones más complicadas, también necesita elsignal
controlador. Y, por supuesto, no se puede convertir en un módulo con ningún código que intente usarmalloc
.Entonces, por desgracia, aquí está:
Aquí está el bloque justo antes de las reducciones finales. Los trucos aquí son cursores enteros en lugar de punteros (aprovechando el comportamiento 'implícito int') y el uso de 'memoria de memoria virtual':
char*n
es el puntero 'nuevo' o 'próximo' en el espacio libre. Pero a veces escribo una cadena en la memoria, luego llamo a strlen e incremento n; usando efectivamente la memoria y luego asignándola, después de que el tamaño sea más fácil de calcular. Puede ver que es bastante directo del documento de McCarthy, con la excepción decell()
qué interfaces entre las funciones y la representación de datos en cadena.fuente
sprintf(n,...);n+=strlen(n)+1;
es mejor ya quen+=sprintf(n,...)+1;
Invertir la sintaxis de la matriz enx[m]
lugar dem[x]
permitirme reemplazar todas las indirecciones con una macro 'postfix'#define M [m]
...x M
que ahorra 1 carácter y proporciona un salto de línea "libre" ya que es necesario un espacio en blanco para separar los tokens.// um ...
está recorriendo la cadena y contando paréntesis hasta que encuentra el par pareado cercano en el nivel de anidación correcto.Cálculo Lambda Binario 186
El programa que se muestra en el volcado hexadecimal a continuación
no acepta el formato que propones. Más bien, espera un término lambda en formato binario de cálculo lambda (blc). Sin embargo, muestra cada paso en la reducción de forma normal, usando paréntesis mínimos.
Ejemplo: calcular 2 ^ 3 en números de la Iglesia
Guarde el volcado hexadecimal anterior con xxd -r> symbolic.Blc
Tome un intérprete blc de http://tromp.github.io/cl/uni.c
Como el hexdump es bastante ilegible, aquí hay una versión "desmontada"
reemplazando 00 (lambda) con \ y 01 (aplicación) con @ Ahora es casi tan legible como brainfuck :-)
Ver también http://www.ioccc.org/2012/tromp/hint.html
fuente
Haskell,
342323317305 caracteresAl momento de escribir este artículo, esta es la única solución que evalúa ((λ f. (Λ x. (Fx))) (λ y. (Λ x. Y))) al resultado correcto (λ x. (Λ z. x)) en lugar de (λ x. (λ x. x)). La implementación correcta del cálculo lambda requiere una sustitución que evite la captura , incluso bajo la garantía simplificadora de este problema de que ninguna variable sombrea a otra variable en su alcance. (Mi programa funciona incluso sin esta garantía).
Notas:
Versión sin golf con documentación.
fuente
Python -
321320Aquí está mi intento (fijo):
fuente
Ruby 254 caracteres
Se puede usar como
La solución aún no está totalmente desarrollada, pero ya es casi ilegible.
fuente
Editar: verifique mi respuesta a continuación para 250 bajo JavaScript puro.
2852243 caracteres usando LiveScript (¡No Regex! No completamente golfizado - podría mejorarse)Prueba:
Que es
3^2=9
, como se indica en OP.Si alguien tiene curiosidad, aquí hay una versión extendida con algunos comentarios:
fuente
Waterhouse Arc - 140 caracteres
fuente
C 1039 bytes
Las variables permiten como entrada usando letras minúsculas [de a..z] el sistema puede generar variables usando letras mayúsculas [de A..Z] si es necesario en la salida ... Asumir la configuración de caracteres ascii.
fuente
Haskell 456 C
Puede ser mucho más corto si la característica de evaluación perezosa de Haskell se utiliza por completo. Lamentablemente, no sé cómo hacerlo.
Además, se desperdician muchos caracteres en el paso de análisis.
Versión sin golf
fuente
Obtuve 231 con JavaScript / no Regex
Recibe matrices de 2 elementos.
1
representaλ
y 2 representa una variable de índice bruijn.Prueba:
fuente
Python: 1266 caracteres (medidos con wc)
No es el más corto por mucho, pero maneja correctamente el cambio de nombre alfa y todos los ejemplos enumerados en la publicación de OP.
fuente
except NameError
con soloexcept
? (3) Los nombres de funciones de dos caracteres se pueden renombrar a nombres de un carácter. (4) Hay algunos lugares donde tiene asignaciones que tienen espacios alrededor del=
. (5)if t[0]=='c'
se puede reemplazar conif'c'==t[0]
.C ++ (gcc) ,
782766758731 bytesPruébalo en línea!
La idea básica aquí es que el código usa una representación interna basada en la idea de los índices de Bruijn , excepto que invierto los índices para indicar la profundidad lambda de la unión de la variable referida. En el codigo:
E::t
representa el tipo de nodo: 0 para un nodo hoja variable, 1 para un nodo lambda y 2 para un nodo de aplicación de función. (Elegido para que coincida con la aridad del nodo, que es posible.) Entonces,E::l
yE::r
son los hijos según corresponda (soloE::l
para un nodo lambda), yE::i
es el índice de profundidad lambda para un nodo de hoja variable.E::E(E&o,int d,int e)
clona una subexpresión que inicialmente estaba en profundidad lambdad
para pegarla en una nueva ubicación en profundidad lambdad+e
. Esto implica preservar las variables en la profundidad lambda menos qued
al incrementar las variables en la profundidad lambda al menosd
ene
.E::s
hace una sustitución de la subexpresiónv
en número variabled
en*this
mientras decrementar un número variable mayor qued
(ye
es un detalle interno de seguimiento el incremento lambda profundidad para cuando se necesita a la llamadaE::c
).E::R
busca una única reducción beta para realizar, prefiriendo las instancias más altas o más izquierdas de acuerdo con una búsqueda previa a través del AST. Devuelve un valor distinto de cero si encuentra una reducción para realizar o cero si no encuentra ninguna.E::u
es unato_string
operación de tipo que reconstituye una cadena "legible para humanos" usando nombres sintéticos para las variables. (Tenga en cuenta que debido a un poco de golf de laV
función auxiliar que sólo generará nombres que contienea
travési
.)E::E(int d, std::map<std::string, int> m, char*&s)
analiza una cadena de entradas
en una expresión AST basada en una asignaciónm
de nombres de variables actualmente vinculados a índices de profundidad lambda.f
es la función principal que responde la pregunta.(Como puede ver en el enlace TIO, el código maneja nombres de variables con múltiples caracteres, y también obtiene una respuesta correcta de
(\ a. (\ b. a))
for((\ f. (\ x. (f x))) (\ y. (\ x. y)))
. También sucede que el código de análisis puede manejar el sombreado de variables sin costo adicional).-16 bytes en parte debido a la idea de ceilingcat (que también había llegado con forma independiente), y en parte debido a los cambios
E*a=new E;
aE&a=*new E;
y luego cambiara->
aa.
-8 bytes más debido a otro comentario de ceilingcat (factorizar la asignación de
a.t
desde ternary)-27 bytes de convertir el analizador y el clon en constructores de
E
fuente
C 637 bytes
Esta versión no utiliza variables auxiliares (por lo que esto no sigue al 100% lo que dice el cálculo lambda ... como muchos otros aquí ...). Cada variable debe tener una longitud de 1 carácter (como alguna otra aquí). Código de prueba:
resultados:
este es el semi ungolf:
fuente