EDIT: Como alguns de vocês suspeitavam, houve um erro no intérprete oficial: a ordem da composição .foi invertida. Eu tinha duas versões do intérprete e usei a errada aqui. Os exemplos também foram escritos para esta versão incorreta. Corrigi o intérprete no repositório e os exemplos abaixo. A descrição de >também era um pouco ambígua, então eu corrigi isso. Além disso, desculpas por isso demorar tanto, eu fui pego em algumas coisas da vida real.
EDIT2: Meu intérprete teve um erro cuja implementação .foi refletida nos exemplos (eles contavam com um comportamento indefinido). O problema está resolvido.
Introdução
Shift é uma linguagem de programação funcional esotérica que criei há alguns anos, mas publiquei hoje. É baseado em pilha, mas também possui currying automático como Haskell.
Especificação
Existem dois tipos de dados no Shift:
- Funções, que possuem uma aridade positiva arbitrária (número de entradas) e que retornam uma lista de saídas. Por exemplo, uma função que duplica sua única entrada possui aridade 1 e uma função que troca suas duas entradas tem aridade 2.
- Espaços em branco, todos idênticos e não têm outro objetivo além de não serem funções.
Um programa Shift consiste em zero ou mais comandos , cada um com um único caractere ASCII. Existem 8 comandos no total:
!( aplicar ) exibe uma funçãofe um valorxda pilha e aplicaf- se ax. Seftiver arity 1, a listaf(x)será adicionada à frente da pilha. Se houver aridaden > 1, uma nova(n-1)função -arygé enviada para a pilha. É preciso entradas e retornos .x1,x2,...,xn-1f(x,x1,x2,...,xn-1)?(em branco ) coloca um espaço em branco na pilha.+( clone ) envia para a pilha uma função unária que duplica sua entrada: qualquer valorxé mapeado para[x,x].>( shift ) empurra para a pilha uma função unária que assume umanfunção -aryfe retorna uma(n+1)função -arygque ignora seu primeiro argumentox, chamafos restantes e se alinhaxà frente do resultado. Por exemplo,shift(clone)é uma função binária que recebe entradasa,be retornos[a,b,b]./( fork ) empurra para a pilha uma função ternária que aceita três entradasa,b,ce retorna[b]seaestiver em branco ou[c]não.$( Chamada ) empurra para a pilha uma função binária que aparece uma funçãofe um valorx, e aplica-sefaxexatamente como!faz..( cadeia ) empurra para a pilha uma função binária que exibe duas funçõesfegretorna sua composição: uma funçãohque tem a mesma aridadefe que recebe suas entradas normalmente, aplicaf- se a elas e depois aplica - se totalmentegao resultado (chamadas tantas vezes quanto dita a sua aridade), com itens não utilizados da saída dofrestante no resultado deh. Por exemplo, suponha quefseja uma função binária que clone seu segundo argumento egseja chamada . Se a pilha contém[f,g,a,b,c]e nós o fazemos.!!, então ela contém[chain(f,g),a,b,c]; se fizermos a!!seguir,fé aplicado primeiroa,b, produzindo[a,b,b], entãogé aplicado aos dois primeiros elementos disso, já que sua aridade é 2, produzindo[a(b),b], e a pilha finalmente será[a(b),b,c].@( digamos ) empurra uma função unária que simplesmente retorna sua entrada e imprime0se estivesse em branco e1se fosse uma função.
Observe que todos os comandos, exceto !simplesmente enviam um valor para a pilha, não há como executar entradas e a única maneira de produzir qualquer coisa é usar @. Um programa é interpretado avaliando os comandos um por um, imprimindo 0s ou 1s sempre que "say" é chamado e saindo. Qualquer comportamento não descrito aqui (aplicar um espaço em branco, aplicar uma pilha de comprimento 0 ou 1, chamar "cadeia" em um espaço em branco etc.) é indefinido: o intérprete pode travar, falhar silenciosamente, solicitar informações ou qualquer outra coisa.
A tarefa
Sua tarefa é escrever um intérprete para o Shift. Deve levar do STDIN, da linha de comando ou do argumento da função um programa Shift a ser interpretado e imprimir em STDOUT ou retornar a saída resultante (possivelmente infinita) de 0s e 1s. Se você escreve uma função, deve poder acessar as saídas de tamanho infinito de alguma forma (gerador em Python, lista lenta em Haskell, etc). Como alternativa, você pode pegar outra entrada, um número ne retornar pelo menos ncaracteres da saída, se for maior que n.
A menor contagem de bytes vence e as brechas padrão não são permitidas.
Casos de teste
Este programa Shift imprime 01:
?@!@@!
Começando pela esquerda: pressione um espaço em branco, pressione dizer e aplique a palavra no espaço em branco. Isso gera 0. Em seguida, pressione say duas vezes e aplique a segunda palavra à primeira. Isso gera 1.
Este programa faz um loop para sempre, não produzindo saída:
$+.!!+!!
Empurre a chamada e o clone e aplique a cadeia a eles (precisamos de dois !s, pois a cadeia é uma função binária). Agora a pilha contém uma função que pega um argumento, o duplica e chama a primeira cópia no segundo. Com +!!, duplicamos essa função e a chamamos por si mesma.
Este programa imprime 0010:
?@$.++>!.!!.!!.!!!!+?/!!!@!@>!!!
Empurre um espaço em branco e diga . Em seguida, componha uma função binária que copie seu segundo argumento b, copie o primeiro ae o componha consigo mesmo, depois aplique a composição à cópia de b, retornando [a(a(b)),b]. Aplique para dizer e em branco, depois diga para os dois elementos restantes na pilha.
Este programa é impresso 0. Para cada um !!!que você anexa, ele imprime um adicional 0.
?@+$>!>!+>!///!!>!>!.!!.!!.!!+!!!!
Empurre um espaço em branco e diga . Em seguida, componha uma função ternária que tome f,g,xcomo entradas e retornos [f,f,g,g(x)]. Clone essa função e aplique-a a si mesma, digamos , e ao espaço em branco. Este aplicativo não altera a pilha, para que possamos aplicar a função novamente quantas vezes quisermos.
Este programa imprime a sequência infinita 001011011101111..., onde o número de 1s sempre aumenta em um:
@?/!@>!??/!!>!+.!!.!!.!!.+>!.!!$$$$+$>!>!$>!>!+>!$>!>!>!+>!>!///!!>!>!>!.!!.!!.!!.!!.!!.!!.!!.!!.!!.!!+!!!!!
O repositório contém uma versão anotada.
f(x1, x2, ..., xn)e g(y1, y2, ..., ym). A chamada .aparece nos dois e pressiona uma função h(z1, z2, ..., zn). Agora você pode consumir todos esses argumentos gradualmente fazendo curry com ele !. Após nessas aplicações, a função restante tinha apenas um argumento e, nesse ponto, calcula f(z1, z2, ..., zn)(ou seja, faplica-se a todos os argumentos que você inseriu), o que gera alguns novos valores e, em seguida, consome imediatamente os mvalores da pilha e os chama g.
.funciona exatamente como Martin descreveu, exceto que, se fretornar uma lista menor que mvalores, o resultado será indefinido (a composição tem aridade n, portanto, não pode ser consumido mais argumentos da pilha). Essencialmente, a saída de fé usada como uma pilha temporária, na qual gsão pressionados e aplicados os mtempos de utilização !, e o resultado disso é adicionado à pilha principal.