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ónfy un valorxde la pila, y se aplicafax. Siftiene arity 1, la listaf(x)se agrega al frente de la pila. Si tiene arity, se empujan > 1una nueva(n-1)función -aryga la pila. Toma entradas y retornos .x1,x2,...,xn-1f(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 valorxse asigna a[x,x].>( shift ) empuja a la pila una función unaria que toma unanfunción -aryf, y devuelve una(n+1)función -arygque ignora su primer argumentox, invocaflos restantes y agregaxdelante del resultado. Por ejemplo,shift(clone)es una función binaria que toma entradasa,by retornos[a,b,b]./( fork ) empuja a la pila una función ternaria que toma tres entradasa,b,cy devuelve[b]siaestá en blanco, y de lo[c]contrario.$( Llamada ) empuja a la pila de una función binaria que aparece una funciónfy un valorx, y se aplicafaxexactamente como!lo hace..( cadena ) empuja a la pila una función binaria que muestra dos funcionesfygdevuelve su composición: una funciónhque tiene la misma aridad quef, y que toma sus entradas normalmente, se aplicafa ellas y luego se aplica completamentegal resultado (llamadas tantas veces como lo dicta su arity), con elementos no utilizados de la salida defpermanecer en el resultado deh. Por ejemplo, suponga quefes una función binaria que clona su segundo argumento yges 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,fprimero se aplica aa,b, produciendo[a,b,b], luegogse 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 imprime0si estaba en blanco y1si 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 0so 1s 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 0sy 1s. 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 ny devolver al menos ncaracteres 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 acopie 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,xcomo 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 1s 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 ntales aplicaciones, la función restante tenía solo un argumento, y en ese punto se computa f(z1, z2, ..., zn)(es decir, se faplica a todos los argumentos en los que se cursó), lo que empuja algunos valores nuevos, y luego consume inmediatamente los mvalores de la pila y los invoca g.
.funciona exactamente como Martin describió, excepto que si fdevuelve una lista de mvalores 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 fse usa como una pila temporal, en la que gse empuja y se aplica mveces !, y el resultado se agrega a la pila principal.