terça-feira, 14 de julho de 2009

ICFP2009 - lendo o arquivo

Essa é a primeira parte da tarefa do ICFP2009: ler o arquivo. Nada demais, uma vez que você conheça a Data.Binary e a Data.Binary.IEEE754! Vou percorrer um pouco desse código.

Inicialmente, declaramos o módulo e importamos as libraries:

module OBFFile where

import qualified Data.ByteString.Lazy as BL
import Control.Arrow
import Data.Binary.Get
import Data.Binary.IEEE754
import System.IO
import Data.Word
import OBFProgram
import OBFData
import OVM


A função principal é dividida em duas partes: primeiro, lemos o conteúdo inteiro em um ByteString e o passamos para a segunda parte. Na segunda parte, abusando da notação point-free, lemos todos os frames do arquivo, usando uma mônada Get do pacote Data.Binary. Em seguida, convertemos a lista de tuplas em uma tuplas de listas. Com a primeira lista, decodificamos as instruções do programa e convertemos em uma mônada OVM, que descreverei mais tarde. A segunda lista é convertida em um IntMap, e corresponde a inicialização da memória. O return está aí somente para empacotar o resultado em uma mônada IO.


readOBFFile::Handle->IO (OVM(),OBFData)
readOBFFile h=BL.hGetContents h >>= readOBFFile'

readOBFFile':: BL.ByteString->IO (OVM(),OBFData)
readOBFFile'=return.(toOBFProgram *** toOBFData). unzip . runGet getFrames
where
toOBFData=fromList .(filter ((/=0.0).snd)). (zip [0..])
toOBFProgram=toOVM.decode


Finalmente a leitura propriamente dita funciona como um fold em duas etapas. Se a entrada não está vazia, lemos o frame par, que tem o formato (memória, instrução) e chamamos a função do frame ímpar, com o formato invertido. A cada operação, concatenamos o resultado na cabeça de uma lista acumuladora. Finalmente, quando a entrada está vazia, retornamos a lista acumuladora revertida, nos valendo da amortização da operação.


getFrames::Get [(Word32,Double)]
getFrames=getEvenFrames []

getEvenFrames::[(Word32,Double)]->Get [(Word32,Double)]
getEvenFrames acc=
do
b<-isEmpty
if b then return (reverse acc)
else do
d<-getFloat64le
i<-getWord32le
getOddFrames ((i,d):acc)


getOddFrames::[(Word32,Double)]->Get [(Word32,Double)]
getOddFrames acc=
do
b<-isEmpty
if b then return (reverse acc)
else do
i<-getWord32le
d<-getFloat64le
getEvenFrames ((i,d):acc)


Se não fossem os dos e eu ter citado, o leitor provavelmente não notaria o uso da mônada Get, e imaginaria que Get é uma composição de funções. O segredo está na definição do Get:


newtype Get a = Get { unGet :: S -> (a, S) }

Na verdade, a poderosa mônada Get não passa de uma função que leva um estado a uma saída e outro estado. A função runGet chama unGet (a implementação do Get) com a inicialização do estado, obtida a partir do Lazy ByteString que é passado como parametro.

Quando combinamos mônadas, usando os getWord32le e getFloat64le, estamos gerando uma composição de função aonde não precisamos nos importar em costurar o estado de uma chamada para outra. Toda essa composição é feita na implementação do operador (>>=) e se torna irrelevante para o "usuário" da mônada.

Mas o leitor pode perguntar: "Legal, mas aonde esse operador foi usado?? Não o vejo em lugar nenhum, depois de readOBFFile". Fácil! Nesse ponto entra o maior syntatic sugar do Haskell, destinado a transfomar as mônadas em warm, fuzzy things: a notação do! Podemos subentender cada linha como separada por um >>= e cada "retorno" como uma definição lambda. Assim, i<-getWord32le poderia ser escrito "getWord32le >>= \i->" (em pseudo-Haskell).

Foi por isso que falei que as mônadas não são coisa de outro mundo! Continuarei dissecando meu código nos próximos posts!

Nenhum comentário:

Postagens populares