Este é um bom exercício para se tornar mais fluente na linguagem de programação que você pretende aprender, mas que apenas mexeu levemente. Isso envolve trabalhar com objetos, usar ou simular fechamentos e esticar o sistema de tipos.
Sua tarefa é escrever código para gerenciar listas preguiçosas e usá-lo para implementar esse algoritmo para gerar números de Fibonacci:
As amostras de código estão em Haskell
let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
in take 40 fibs
Resultado:
[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986]
Sua implementação de lista lenta deve atender a estas diretrizes:
- Um nó da lista é uma das três coisas:
- Nil - lista vazia.
[]
- Contras - Um único item, emparelhado com uma Lista dos itens restantes:
1 : [2,3,4,5]
(:
é o operador contras em Haskell) - Thunk - Uma computação adiada que produz um nó de Lista quando necessário.
- Nil - lista vazia.
- Ele suporta as seguintes operações:
- nil - Construa uma lista vazia.
- contras - Construa uma célula contras.
- thunk - Construa um Thunk, dada uma função que não aceita argumentos e retorna Nil ou Contras.
- force - Dado um nó de lista:
- Se for Nil ou Contras, basta devolvê-lo.
- Se for um Thunk, chame sua função para obter Nil ou Cons. Substitua o thunk por Nil ou Cons e devolva-o.
Nota: A substituição da conversão por seu valor forçado é uma parte importante da definição de "preguiçoso" . Se esta etapa for ignorada, o algoritmo Fibonacci acima será muito lento.
- vazio - Veja se um nó da Lista é Nil (depois de forçá-lo).
- head (aka "car") - Pegue o primeiro item de uma lista (ou ajuste se for Nil).
- tail (aka "cdr") - Obtenha os elementos após o cabeçalho de uma lista (ou ajuste se for Nil).
- zipWith - Dada uma função binária (por exemplo
(+)
) e duas listas (possivelmente infinitas), aplique a função aos itens correspondentes das listas. Exemplo:
zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
- take - Dado um número N e uma lista (possivelmente infinita), pegue os primeiros N itens da lista.
- imprimir - Imprima todos os itens de uma lista. Isso deve funcionar de maneira incremental quando é fornecida uma lista longa ou infinita.
fibs
usa-se em sua própria definição. Configurar recursão lenta é um pouco complicado; você precisará fazer algo assim:- Aloque um thunk para
fibs
. Deixe-o em um estado fictício por enquanto. - Defina a função thunk, que depende de uma referência a
fibs
. - Atualize o thunk com sua função.
Você pode ocultar esse encanamento definindo uma função
fix
que chama uma função de retorno de lista com seu próprio valor de retorno. Considere tirar um cochilo curto para que essa ideia possa surgir.- Aloque um thunk para
O polimorfismo (a capacidade de trabalhar com listas de qualquer tipo de item) não é necessário, mas veja se você consegue encontrar uma maneira de fazer isso que seja idiomática no seu idioma.
- Não se preocupe com o gerenciamento de memória. Mesmo os idiomas com coleta de lixo tendem a carregar objetos que você nunca mais usará (por exemplo, na pilha de chamadas), portanto, não se surpreenda se o seu programa perder memória ao percorrer uma lista infinita.
Sinta-se à vontade para se afastar um pouco dessas diretrizes para acomodar os detalhes do seu idioma ou para explorar abordagens alternativas às listas preguiçosas.
Regras:
- Escolha um idioma que você não conhece bem. Não posso "exigir" isso, daí a tag "sistema de honra". No entanto, os eleitores podem verificar seu histórico para ver em quais idiomas você está postando.
Não use o suporte à lista lenta do seu idioma para fazer tudo. Poste algo substancial ou pelo menos interessante.
Haskell está praticamente fora. Ou seja, a menos que você faça algo assim:
data List a = IORef (ListNode a) data ListNode a = Nil | Cons !a !(List a) | Thunk !(IO (ListNode a))
Nota: A avaliação não estrita de Haskell não está fora dos limites, mas sua implementação de lista lenta não deve derivar sua capacidade diretamente a partir daí. De fato, seria interessante ver uma solução eficiente e puramente funcional que não exija preguiça.
Python:
- Não use ferramentas.
- Os geradores são bons, mas, se você os usar, precisará encontrar uma maneira de memorizar valores forçados.
fonte
zipWith
duas listas de diferentes comprimentos?zipWith (+) [1,2,3,4,5] [0,0,0] == [1,2,3]
,. No entanto, isso não importa para o algoritmo Fibonacci acima, pois os dois argumentos para zipWith são listas infinitas.fibs
corretamente, pois depende de si mesmo. Atualizei a questão para elaborar uma recursão lenta. FUZxxl descobriu por si mesmo.Respostas:
PostScript
Já toquei com PostScript antes , mas não diria que o conheço particularmente bem (na verdade, meu palpite é que você pode contar o número de pessoas no mundo que realmente conhecem o PostScript usando uma mão).
Eu me desviei das suas especificações, pois a função usada para criar um thunk pode retornar outro thunk;
force
continuará avaliando até que o resultado seja anil
ou acons
.As listas são implementadas como dicionários:
O código a seguir. Observe que estamos substituindo alguns operadores internos (em particular
print
; não verifiquei se há mais); no uso no mundo real, isso teria que ser observado. Claro que não haverá uso no mundo real, então tudo bem.Os comentários antes dos procedimentos devem ser lidos como
ou seja, mostrando o conteúdo esperado da pilha antes da chamada e o conteúdo resultante da pilha após a chamada. Os comentários dentro dos procedimentos mostram o conteúdo da pilha após a execução da linha específica.
Carregue isso no Ghostscript, ignorando a página exibida - estamos trabalhando apenas com o intérprete. Aqui está o algoritmo de Fibonacci:
Duas funções interessantes adicionais:
Comece a contar com 5, multiplique cada elemento da lista resultante por 3 e exiba os dez primeiros valores:
Sobre o polimorfismo: Embora o PostScript seja fortemente digitado, ele permite tipos arbitrários como valores de dicionário, para que você possa inserir o que quiser:
Observe que erros de tipo, por exemplo, ao tentar adicionar strings a números, ocorrerão apenas no momento da avaliação:
fonte
force
memoriza valores retornados?copy
operador copia o conteúdo da versão avaliada no original, substituindo/type
e possivelmente configurando outros valores. Depois de avaliar recursivamente até termos umnil
oucons
, ele também (viaundef
) remove/func
e, onde aplicável/data
,. O último passo não é estritamente necessário (/func
e/data
só iria ser ignorado), mas deixando isso passo iria vazar ainda mais memória :)C
Eu sou totalmente iniciante em C, esse código é realmente a primeira coisa real que codifiquei em C. Ele compila sem nenhum aviso e funciona bem no meu sistema.
Como construir
Primeiro, pegue o tarball do meu servidor . Ele inclui um makefile; portanto, basta executar
make
para compilá-lo e depoismake run
executá-lo. O programa imprime uma lista dos primeiros 93 números de fibonacci. (Após o número 94, um número inteiro de 64 bits sem sinal transborda)Explicação
O núcleo do programa é o arquivo
lazy-list.c
. No arquivo de cabeçalho correspondente, defino uma estruturalist
, que é a nossa lista lenta. Se parece com isso:O membro
kind
é uma espécie de tag. Ele marca, se reciclamos as listas end (NIL
), uma célula que já está avaliada (CONS
) ou uma conversão (THUNK
). Então, segue-se uma união. Isto éO conteúdo da união é afirmado pela tag. Se a tag for
NIL
, o conteúdo da união é indefinido.Ao definir as funções auxiliares mencionadas na especificação acima, geralmente é possível abstrair a definição de lista de seu uso, por exemplo. você pode simplesmente ligar
nil()
para obter uma lista vazia em vez de criar uma sozinha.As três funções mais interessantes são
zipWith
,take
efibonaccis
. Mas não quero explicartake
, pois é muito parecido comzipWith
. Todas as funções, que operam preguiçosamente, têm três componentes:No caso de
zipWith
, estes sãozipWith
,__zipWith
e__zipArgs
. Eu apenas os mostro aqui, sem mais explicações, a função deve ser bem clara:A outra função interessante é
fibonaccis()
. O problema é que precisamos passar um ponteiro da primeira e da segunda célula para a thunk da terceira, mas, para criar essas células, também precisamos de um ponteiro para a thunk. Para resolver esse problema, preenchi o ponteiro do thunk com umNULL
primeiro e o troquei para thunk, depois que ele foi criado. Aqui está a escuta:Possíveis melhorias
content_t
, que pode ser alterado para o que couber.fonte
void*
como o tipo decontent_t
.void*
também, mas achei que isso iria desviar o sistema de tipos muito longe. Isso não é possível usando modelos?void*
amigos.kind
é uma espécie de etiqueta.” Você pode simplesmente chamá-lotag
, já que esse é um termo bastante aceito para o conceito (por exemplo , união etiquetada , máquina G Spineless Tagless . Por outro lado, "tipo" tem um significado diferente em um . contexto Haskell: o tipo de um tipoInt
tem tipo*
,[]
tem tipo* -> *
, e(,)
tem tipo* -> * -> *
.C ++
Esta é a maior coisa que eu já escrevi em C ++. Eu normalmente uso Objective-C.
É polimórfico, mas nunca libera nada.
Minha
main
função (e aadd
função toZipWith
) acabou assim:Isto dá
As classes funcionam assim:
Fonte completa: aqui . É uma bagunça, principalmente porque está em um arquivo grande.
Edit: mudou o link (o antigo estava morto).
fonte
()
operador) e usar a herança para evitar ter que usarvoid*
. Veja aqui um exemplo trivial de fazer isso.Python
Não usa geradores para implementar a lista, apenas para implementar o
__iter__
método para uso comfor
.A lista de Fibonacci é criada assim:
fonte
self.__class__ = node.__class__
. Observe que isso atinge uma exceção NotImplemented quando atinge 2971215073 (long), que aparentemente é um argumento inválido para int .__ add__. Para suportar números inteiros grandes, façafib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
Rubi
Meu primeiro programa Ruby. Representamos todos os nós como matrizes, em que o comprimento da matriz determina o tipo:
O código é bastante simples, com um hack para redefinir a função thunk para configurar a fib recursiva.
fonte
[...]
vez deArray[...]
.Google Go
Um idioma relativamente novo, e eu aprendi usando
CTRL+F
o Spec .O problema foi resolvido, lidando com thunk-inside-a-thunks. No entanto, parece que o compilador online não pode receber 40 elementos, talvez por causa da memória. Vou testá-lo no meu Linux mais tarde.
Testei o código com o compilador online , porque não consigo instalar o Go on Windows facilmente.
fonte
iota
gerador constante. Veja um exemplo em Especificação da linguagem de programação Go e uma resposta no StackOverflow .Fibs
função não funciona porque o Go usa avaliação rigorosa e seFibs
repete sem uma condição final.Fibs0
/Fibs1
usa uma abordagem de gerador simples, em vez do algoritmo descrito em meu post, para que ele não atenda aos "requisitos". Atualizei meu post para elaborar uma recursão lenta, necessária para implementarfibs = 0 : 1 : zipWith (+) fibs (tail fibs)
.Cons(0, Cons(1, ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs)))))
, fica sem memóriaCons(0, Cons(1, Thunk(func() List { return ZipWith(Plus, Thunk(Fibs), Thunk(func() List { return Tail(Fibs()) })) })))
e eu recebo erro de endereço de memória inválidoCristal
Apesar de seguir o repositório GitHub, nunca usei o Crystal até agora. Crystal é uma variante Ruby de tipo estatístico com inferência de tipo completo. Embora já haja uma resposta Ruby, a digitação estática de Crystal me levou a usar o polimorfismo, em vez de uma matriz, para representar os nós. Como o Crystal não permite modificações
self
, eu criei uma classe de invólucro denominadaNode
que envolveria todo o resto e gerenciaria os thunks.Junto com as aulas, eu criei as funções de construtor
lnil
,cons
ethunk
. Eu nunca usei Ruby para mais de um script de 20 linhas antes, também, então o material do bloco me assustou bastante.Baseei a
fib
função na resposta Go .fonte
Inclinei um pouco as regras porque ainda não há uma solução .NET aqui - ou mais geralmente uma solução OOP, exceto a do Python que usa herança, mas é diferente o suficiente da minha solução para tornar as duas interessantes (em particular desde o Python permite modificar a
self
instância, tornando a implementação de thunk simples).Então, isso é c # . Divulgação completa: não estou nem perto de um iniciante em C #, mas não mexo no idioma há algum tempo, pois atualmente não tenho utilidade para ele no trabalho.
Os pontos salientes:
Todas as classes (
Nil
,Cons
,Thunk
) derivam de uma classe abstrata de base comum,List
.A
Thunk
classe usa o padrão Envelope-Letter . Isso emula essencialmente aself.__class__ = node.__class__
atribuição na fonte Python, pois athis
referência não pode ser modificada em C #.IsEmpty
,Head
eTail
são propriedades.Todas as funções apropriadas são implementadas de forma recursiva e preguiçosa (exceto
Print
, que não pode ser preguiçosa) retornando thunks. Por exemplo, isto éNil<T>.ZipWith
:... e é isso
Cons<T>.ZipWith
:Infelizmente, o C # não tem vários despachos, caso contrário, eu também poderia me livrar da
if
declaração. Infelizmente, não há dados.Agora, não estou muito feliz com minha implementação. Estou feliz até agora porque tudo isso é totalmente direto. Mas . Eu sinto que a definição de
Fib
é desnecessariamente complicada, pois preciso agrupar os argumentos em thunks para fazê-lo funcionar:(Aqui,
List.Cons
,List.Thunk
eList.ZipWith
são invólucros de conveniência.)Gostaria de entender por que a seguinte definição muito mais fácil não está funcionando:
dada uma definição apropriada, é
Concat
claro. Isso é essencialmente o que o código Python faz - mas não está funcionando (= jogando um ajuste)./ EDIT: Joey apontou a falha óbvia nesta solução. No entanto, substituir a segunda linha por uma conversão também gera um erro (Mono segfaults; estou suspeitando de um estouro de pilha que o Mono não lida bem):
O código fonte completo pode ser encontrado como uma essência no GitHub .
fonte
fib.ZipWith
efib.Tail
use o antigofib
, que permanece[0,1]
e não muda. Assim, você começa[0,1,1]
(eu acho), e suaTake
função não deixá-lo tomar a partir nula (de Haskell take faz, embora). Tente agrupar o rvalue da segunda linha em uma conversão, para que ele se refira ao novofib
e não ao antigo.Pico
para registro, esta solução usa uma conversão da força de atraso do esquema, conforme definido no srfi-45 . e cria listas preguiçosas em cima disso.
os olhares de saída como este: (mas dependendo de como
tpico
. é remendado ele pode ter citações mais duplas neledisplay
. normalmente imprime strings com aspas ou seja, todas as aparências de[
,,
,]
teria aspas em torno deles como"["
.)devido aos limites do tipo de dados inteiro no tpico, isso falha ao calcular o 45º (ou 46º deslocamento) número de Fibonacci.
note que o tpico 2.0pl11 está quebrado nisso
begin(a,b)
(que geralmente é escrito como{a;b}
) e aif
função não é recursiva da cauda. para não mencionar que demorei 5 anos para descobrir por que abegin
cauda não era recursiva. Também naquela época escrevi a tradução de srfi-45 no Pico. acabou sendo quebegin
estava aguardando o valor deb
antes de retornar quando não precisou esperar. e quando cheguei, também consegui consertarif
, pois tinha o mesmo problema. e houve esse outro erro que tornou o construtor de meta nívelmake
inoperante.O Pico permite que uma função controle se seus argumentos são avaliados antes que a função seja chamada ou apenas empacotada como thunks. para esse código, posso reticências sobre as esquisitices de chamada por função .
Pico não tem inferência de tipo. Eu pensei sobre isso por um tempo, mas corri para um problema devido às peculiaridades da chamada por função . Eu vim com a declaração de que os tipos devem codificar a existência de nomes de variáveis vinculados . mas eu estava pensando principalmente em como adaptar a inferência do tipo Hindley-Milner a um subconjunto do Pico sem mutação. a idéia principal era que o verificador de tipos retornasse vários esquemas possíveis se houver mais de uma ligação possível e a verificação de tipos seja bem-sucedida se houver pelo menos um esquema de tipos possível . um esquema possível é aquele em que nenhuma atribuição de tipo entra em conflito.
fonte