Back-End

3 set, 2012

Programação Literal usando Sphinx e Haskell

Publicidade

Quando trabalhamos em novos projetos, procuramos anotar todas as ideias que temos em documentos, para referência futura. Depois de um tempo, alguns desses documentos tornam-se documentos de referência.

Às vezes, é útil fornecer alguns exemplos de código em tais documentos, para esclarecer certas coisas, por exemplo, fornecer uma implementação básica de um algoritmo.

Essas amostras devem estar na forma de pseudocódigo, de modo que não cresçam e nem exponham muitos detalhes. Entretanto, utilizar uma linguagem de programação falsa possui uma desvantagem séria: não há nenhuma maneira para validar a consistência, verificar os tipos… de uma forma automatizada.

Para escrever documentos no projeto mais recente, é usada a ferramenta Sphinx, que permite escrever documentos usando ReStructured Text reestruturado, e compilá-los em HTML, LaTeX/PDF ou outros documentos, incluindo funcionalidades úteis como destaque de sintaxe de blocos de código.

Ao escrever código nesses documentos, eu costumo usar Haskell como linguagem de pseudocódigo. Notação compacta do tipo segura e implementações falsas usando undefined permitem prototipagem e interpretação fáceis, mesmo para leitores não familiarizados com a linguagem.

Enquanto o GHC, o maior compilador Haskell, possui suporte nativo para a chamada Programação Literal (código fonte com outro texto no meio de uma formatação específica, como introduzida por Donald Knuth, confira na Wikipedia), isso não é compatível com a sintaxe Sphinx. Felizmente, os desenvolvedores do GHC permitem que os usuários especifiquem processadores literais personalizados na linha de comando. Isso elimina a necessidade de um pré-processamento adicional construir-passos, e permite usar arquivos personalizados literais no REPL ghci do jeito que está.

Recentemente, eu implementei tal pré-processador (usando um script simples em Python). O fonte está disponível em https://gist.github.com/1292596. O script irá extrair todos os blocos marcados como blocos de código. Ele não vai filtrar nenhum dos blocos, por isso, se você usar os blocos de código contendo códigos que não são Haskell, as coisas vão falhar. Existe o código de suporte de filtragem, mas isso não é exposto (a partir de agora).

Aqui está um passo a passo rápido. Imagine que você escreveu arquivo Sphinx, hello_world.rst, com o seguinte conteúdo:

Hello World
===========
To be able to print "Hello world" to the screen, we need to define this string:

.. code-block:: haskell

    helloWorld :: String
    helloWorld = "Hello world"

Printing a string to the screen is an IO operation, so we should perform this
action inside the IO monad. The action won't return any useful result, so we'll
return *()*:

.. code-block:: haskell

    printHelloWorld :: IO ()
    printHelloWorld = putStrLn helloWorld

Finally, we want to make a real application, so we need a main action:

.. code-block:: haskell

    main :: IO ()
    main = printHelloWorld

Para usar esse módulo em ghci, devemos habilitar nosso pré-processador, e dizer a GHC que o arquivo .rst é realmente Haskell Literate (.lhs), então ele vai realizar todos os passos de compilação necessários. Suponha que o script pré-processador seja armazenado como spp.py no diretório de trabalho atual, e tem permissões executáveis:

$ ghci -pgmL "./spp.py" -x lhs hello_world.rst
GHCi, version 7.0.2: http://www.haskell.org/ghc/
  for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
[1 of 1] Compiling Main             ( hello_world.rst, interpreted )
Ok, modules loaded: Main.
*Main> main
Hello world

Note que a localização do fonte é calculada corretamente:

*Main> :info main
main :: IO () 	-- Defined at hello_world.rst:24:1-4

Local de origem da anotação é calculada corretamente:

Você pode usar ghc com os mesmos argumentos para compilar documentos Sphinx.

Usando essa abordagem, deve ser possível escrever aplicativos completos como uma tree de documentos Sphinx, contendo documentos de projeto, documentação, bem como a implementação real.

É claro que o mesmo sistema também poderia ser usado com outras linguagens – (OCaml vem à mente, por exemplo, integrar o pré-processador como uma regra OcamlBuild).

***

Texto original disponível em http://blog.incubaid.com/2011/10/17/literate-programming-using-sphinx-and-haskell/