2010-12-23

run :: BF ->

(continuando…)

Falta o While!

Depois de ter funções que correm cada um dos diferentes comandos em BF, vamos agora juntar tudo até chegar à função run :: BF –> IO().

Em primeiro lugar vamos criar uma função que corre apenas um comando, mas suporta qualquer um deles. Basicamente queremos fazer pattern matching para chamar as pequenas funções que escrevemos atrás.

runOne :: BFCommand -> Memoria -> IO Memoria
runOne IncPtr m = return (incPtr m)
runOne DecPtr m = return (decPtr m)
runOne IncVal m = return (incVal m)
runOne DecVal m = return (decVal m)
runOne Read m = runRead m
runOne Print m = runPrint m
runOne (While b) m = undefined -- ????

Reparem mais uma vez no uso de return para transformar um valor normal do tipo Memoria num valor do tipo IO Memoria.

Até aqui tudo muito simples. Então e o While? Como vai ser?

runOne usando runMany usando runOne

Bem, vamos usar uma estratégia com duas funções mutuamente recursivas semelhante ao que se fez com o show e o readsPrec.

Mas antes disso ainda podemos implementar o caso em que o While termina: quando o valor da célula actual é 0.

runOne (While b) m = do if readVal m /= 0
then undefined -- ???
else return m

E agora temos de fazer duas coisas: executar todos os comandos em b e voltar a testar o While. Para executar todos os comandos do ciclo, vamos definir uma função runMany que executa vários comandos em sequência.

runMany :: BF -> Memoria -> IO Memoria
runMany (BF []) m = return m
runMany (BF (h:t)) m = do m' <- runOne h m
runMany (BF t) m'

Muito simples. Quando não há comandos, a memória mantém-se igual. Quando há comandos, executamos o que está na cabeça da lista, e depois continuamos recursivamente com a cauda, mas tendo cuidado para usar o novo estado da memória: m’.

A outra parte da execução do While (voltar a testar) é simplesmente charmar runOne recursivamente com o mesmo comando e com o estado da memória que resultou de runMany.

runOne w@(While bf) m = do if readVal m /= 0
then do m' <- runMany bf m
runOne w m'
else return m

Aqui usamos do duas vezes, porque precisamos de executar duas funções em sequência no caso do then. Também podiamos ter escrito else do return m, mas como é só uma função podemos não utilizar essa notação.

Junte tudo e mexa

Para completarmos a função run :: BF –> IO () só nos falta uma coisa: o estado inicial da memória. Basta usarmos um par onde a cabeça está no início e a memória é uma lista infinita de zeros:

emptyMem :: Memoria
emptyMem = (0,[0,0..])

E agora:

run :: BF -> IO ()
run bf = runMany bf emptyMem – erro! Tipo IO Memoria é diferente de IO ()

Ooops! run só faz I/O e não devolve nenhum resultado, mas runMany faz I/O e devolve um valor do tipo Memoria. Este problema é fácil de corrigir. Se ainda não repararam, ao usarmos a notação do, o resultado da função é o valor da última expressão. Assim, se usarmos a notação do e a última expressão for do tipo IO () podemos corrigir este erro.

run :: BF -> IO ()
run bf = do runMany bf emptyMem
return ()

Pronto, já está. Agora já podemos experimentar o exemplo dado no enunciado:

> run (read ",[.-]")
5
5
4
3
2
1

(nota: o primeiro 5 é para ser introduzido por vocês, porque é para isso que a vírgula lá está.)

(continua…)

11 commentários:

Anónimo disse...

Boas

Antes de mais obrigado pela ajuda, mas estou com um problema.

Quando faço run (read ",[.-]") o resultado é 0 e nem sequer posso inserir o 5 do exemplo.
Eis o k acontece:
> run (read ",[.-]")
0
>

nao sei porque não funciona pois fiz tudo como explicas-te.

cumprimentos

Martinho Fernandes disse...

@Anónimo: deves ter copiado o readsPrec do post anterior que tinha a , e o . trocados. A vírgula estava a fazer "Print" e o ponto "Read" :(

Anónimo disse...

Era mesmo isso.

obrigado, grande ajuda ;)

Anónimo disse...

Só mais uma duvida, no runOne para o While usas na função um w seguido de um @ (w@), o que significa isso?

Martinho Fernandes disse...

Basicamente w@(While bf) significa: "fazer pattern matching com While bf e substituir w por isso". É só um atalho. Mais à frente faço "runOne w m'", que neste caso é o mesmo que "runOne (While bf) m'"

Anónimo disse...

Ja percebi ;)
Nunca tinha visto um @ numa funçao, nem sabia que dava para fazer isso.

Obrigado ;)

Anónimo disse...

boas, ja consegui inserir no haskell, mas quando vou a fazer: run (read ",[.-]"), ele não me deixa inserir nenhum caracter, ficando o _ sempre a piscar

Martinho Fernandes disse...

@Anónimo: bem, há um limite para a quantidade de debugging psíquico que consigo fazer :P Se me mandares o código por e-mail posso ver melhor isso.

Anónimo disse...

Tens cabeça, pá!

Anónimo disse...

boas, esta-me a dar um erro no run na expresao bf

Martinho Fernandes disse...

Não deves ter isso bem alinhado. Se tiveres JavaScript ligado, nos excertos de código aparece uma série de botões no canto superior direito. Há um que está entre o "View Source" e o "Print" que copia para o clipboard, mas que tem um bug e só aparece ao pores o rato por cima (um dia destes tenho de ver o que se passa!). Se copiares e colares exactamente como está, deve dar.

Enviar um comentário