EDITAR: Como algunos de ustedes sospecharon, había un error en el intérprete oficial: .
se invirtió el orden de composición . Tenía dos versiones del intérprete y usé la incorrecta aquí. Los ejemplos también se escribieron para esta versión incorrecta. He arreglado el intérprete en el repositorio y los ejemplos a continuación. La descripción de >
también fue un poco ambigua, así que lo arreglé. Además, las disculpas por haber tardado tanto, quedé atrapado en algunas cosas de la vida real.
EDIT2: Mi intérprete tuvo un error en la implementación .
que se reflejó en los ejemplos (confiaban en un comportamiento indefinido). El asunto ha sido arreglado.
Introducción
Shift es un lenguaje de programación funcional esotérico que hice hace un par de años, pero que publiqué hoy. Está basado en la pila, pero también tiene curry automático como Haskell.
Especificación
Hay dos tipos de datos en Shift:
- Funciones, que tienen una aridad positiva arbitraria (número de entradas) y que devuelven una lista de salidas. Por ejemplo, una función que duplica su única entrada tiene arity 1, y una función que intercambia sus dos entradas tiene arity 2.
- Los espacios en blanco, que son todos idénticos y no tienen otro propósito que no ser funciones.
Un programa Shift consta de cero o más comandos , cada uno de los cuales es un único carácter ASCII. Hay 8 comandos en total:
!
( aplicar ) muestra una funciónf
y un valorx
de la pila, y se aplicaf
ax
. Sif
tiene arity 1, la listaf(x)
se agrega al frente de la pila. Si tiene arity, se empujan > 1
una nueva(n-1)
función -aryg
a la pila. Toma entradas y retornos .x1,x2,...,xn-1
f(x,x1,x2,...,xn-1)
?
(en blanco ) empuja un espacio en blanco a la pila.+
( clone ) empuja a la pila una función unaria que duplica su entrada: cualquier valorx
se asigna a[x,x]
.>
( shift ) empuja a la pila una función unaria que toma unan
función -aryf
, y devuelve una(n+1)
función -aryg
que ignora su primer argumentox
, invocaf
los restantes y agregax
delante del resultado. Por ejemplo,shift(clone)
es una función binaria que toma entradasa,b
y retornos[a,b,b]
./
( fork ) empuja a la pila una función ternaria que toma tres entradasa,b,c
y devuelve[b]
sia
está en blanco, y de lo[c]
contrario.$
( Llamada ) empuja a la pila de una función binaria que aparece una funciónf
y un valorx
, y se aplicaf
ax
exactamente como!
lo hace..
( cadena ) empuja a la pila una función binaria que muestra dos funcionesf
yg
devuelve su composición: una funciónh
que tiene la misma aridad quef
, y que toma sus entradas normalmente, se aplicaf
a ellas y luego se aplica completamenteg
al resultado (llamadas tantas veces como lo dicta su arity), con elementos no utilizados de la salida def
permanecer en el resultado deh
. Por ejemplo, suponga quef
es una función binaria que clona su segundo argumento yg
es call . Si la pila contiene[f,g,a,b,c]
y nosotros.!!
, entonces contiene[chain(f,g),a,b,c]
; si lo hacemos a!!
continuación,f
primero se aplica aa,b
, produciendo[a,b,b]
, luegog
se aplica a los primeros dos elementos de eso ya que su arity es 2, produciendo[a(b),b]
, y la pila finalmente lo será[a(b),b,c]
.@
( digamos ) empuja una función unaria que simplemente devuelve su entrada e imprime0
si estaba en blanco y1
si era una función.
Tenga en cuenta que todos los comandos, excepto !
simplemente insertar un valor en la pila, no hay forma de realizar la entrada, y la única forma de generar algo es usar @
. Un programa se interpreta evaluando los comandos uno por uno, imprimiendo 0
so 1
s cada vez que se llama "say", y saliendo. Cualquier comportamiento no descrito aquí (aplicar un espacio en blanco, aplicar una pila de longitud 0 o 1, llamar "cadena" en un espacio en blanco, etc.) no está definido: el intérprete puede fallar, fallar silenciosamente, pedir información o lo que sea.
La tarea
Su tarea es escribir un intérprete para Shift. Debe tomar de STDIN, línea de comando o argumento de función un programa Shift para ser interpretado, e imprimir en STDOUT o devolver la salida resultante (posiblemente infinita) de 0
sy 1
s. Si escribe una función, debe poder acceder a las salidas de longitud infinita de alguna manera (generador en Python, lista diferida en Haskell, etc.). Alternativamente, puede tomar otra entrada, un número n
y devolver al menos n
caracteres de la salida si es más larga que n
.
El conteo de bytes más bajo gana, y las lagunas estándar no se permiten.
Casos de prueba
Este programa Shift imprime 01
:
?@!@@!
Comenzando desde la izquierda: presione un espacio en blanco, presione say , luego aplique el say al espacio en blanco. Esto da salida 0
. Luego, presione decir dos veces y aplique el segundo decir al primero. Esto da salida 1
.
Este programa se repite para siempre, sin producir resultados:
$+.!!+!!
Presione call y clone , luego aplique chain a ellos (necesitamos dos !
s ya que chain es una función binaria). Ahora la pila contiene una función que toma un argumento, lo duplica y llama a la primera copia del segundo. Con +!!
, duplicamos esta función y la llamamos a sí misma.
Este programa imprime 0010
:
?@$.++>!.!!.!!.!!!!+?/!!!@!@>!!!
Empuje un espacio en blanco y diga . Luego, componga una función binaria que copie su segundo argumento b
, luego a
copie el primero y lo componga consigo mismo, luego aplique la composición a la copia de b
, regresando [a(a(b)),b]
. Aplíquelo a say y blank, luego aplique say a los dos elementos restantes en la pila.
Este programa imprime 0
. Para cada uno !!!
que anexe, imprime un adicional 0
.
?@+$>!>!+>!///!!>!>!.!!.!!.!!+!!!!
Empuje un espacio en blanco y diga . Luego, componga una función ternaria que tome f,g,x
como entradas y retornos [f,f,g,g(x)]
. Clone esa función y aplíquela a sí misma, digamos , y al espacio en blanco. Esta aplicación no cambia la pila, por lo que podemos volver a aplicar la función tantas veces como queramos.
Este programa imprime la secuencia infinita 001011011101111...
, donde el número de 1
s siempre aumenta en uno:
@?/!@>!??/!!>!+.!!.!!.!!.+>!.!!$$$$+$>!>!$>!>!+>!$>!>!>!+>!>!///!!>!>!>!.!!.!!.!!.!!.!!.!!.!!.!!.!!.!!+!!!!!
El repositorio contiene una versión anotada.
f(x1, x2, ..., xn)
y g(y1, y2, ..., ym)
. Llamar los .
hace saltar a ambos y empuja una función h(z1, z2, ..., zn)
. Ahora puedes consumir todos esos argumentos al graduarlos gradualmente !
. Después de n
tales aplicaciones, la función restante tenía solo un argumento, y en ese punto se computa f(z1, z2, ..., zn)
(es decir, se f
aplica a todos los argumentos en los que se cursó), lo que empuja algunos valores nuevos, y luego consume inmediatamente los m
valores de la pila y los invoca g
.
.
funciona exactamente como Martin describió, excepto que si f
devuelve una lista de m
valores inferiores a , el resultado no está definido (la composición tiene aridad n
, por lo que no puede comer más argumentos de la pila). Esencialmente, la salida de f
se usa como una pila temporal, en la que g
se empuja y se aplica m
veces !
, y el resultado se agrega a la pila principal.