Na aula fizemos uma revisão dos assuntos que estão na Lista 4.
Primeiro mostrei outros exemplos de fragmentos de um interpretador que
usam diretamente a memória ao invés de usar a abstração de ações, e
mostrei possíveis falhas no uso linear da memória.
Mais alguns exemplos, usando Seq. Primeiro o caso correto:
Agora algumas versões com bugs de linearidade:
A ordem de avaliação é dada pela “costura” da memória entre
as expressões, então no exemplo correto de Seq acima primeiro
executamos e1 depois e2. Podemos trocar as chamadas a eval
sem mudar a ordem, usando aplicação parcial:
Para trocar a ordem de avaliação precisamos mudar a “costura”:
Quando introduzimos continuações, o que acontece é que execução não
mais retorna seus resultados (o valor e a memória afetada pelos
efeitos colaterais), mas os passa adiante para uma continuação:
Notem que ter os mesmos problemas de lineraridade que tínhamos antes.
Também podemos eliminar mem, deixando a chamada de eval parcial e
eliminando uma fonte de bugs:
Ainda precisamos de nmem, pois temos que acessar uma posição
da memória.
O caso de Seq tem o mesmo paralelismo entre a versão sem continuações
e a com continuações:
Mas ao contrário de Deref, em Seq só estamos passando a memória adiante,
então podemos simplificar e deixá-la implícita:
A ideia da recursão aberta é poder reutilizar
uma função já existente, mas modificar o comportamento
de chamadas recursivas dentro dessa função. Podemos fazer
uma função map que aplica a função f ao resultado
de cada chamada da função super:
Os nomes super e self em map deixam mais claro o que está
acontecendo. Em proto o parâmetro self é implícito, então
temos a recursão aberta de map sem a chance dos erros que estão
comentados:
Quando chamamos super.apply(x), o método chamado é o
do objeto sendo extendido (no nosso caso, fat), mas
self dentro desse método será o objeto criado em make,
portanto chamadas recursivas (self.apply) no apply de fat
caem novamente no objeto que criamos em make, e temos
a recursão aberta.