Sep 12, 2007

Carta favorita

Eu sou um grande fã de Richard Feynman. Depois de ler, reler e reler o delicioso Surely You're Joking, Mr. Feynman!, e ler What Do You Care What Other People Think?: Further Adventures of a Curious Character, No Ordinary Genius: The Illustrated Richard Feynman, Genius: The Life and Science of Richard Feynman, provavelmente minha citação favorita dele vem do livro com suas cartas em Perfectly Reasonable Deviations From The Beaten Track: The Letters Of Richard P. Feynman.

Feynman gostava muito de percussão e sempre se irritava quando as pessoas achavam curioso um cientista famoso gostar de algo artístico. Quando um editor escreveu pedindo uma fotografia dele tocando tambor para dar uma "abordagem humana para a apresentação da dificuldade que a física teórica representa", Feynman respondeu:

Caro Sr,

O fato que eu toco percussão não tem nada a ver com o fato que eu faço
física teórica. Física teórica é uma atividade humana, um dos maiores
desenvolvimentos dos seres humanos, e o desejo perpétuo de provar que
pessoas que fazem isso [física teórica] são humanos mostrando que eles
fazem outras coisas que alguns outros humanos fazem (como tocar bongô)
é insultante para mim.

Eu sou humano o suficiente para dizer para você ir para o inferno.

Atenciosamente,
RPF

Posted at 06:04

Aug 23, 2007

Lisp e parênteses

As pessoas sempre reclamam da quantidade de parênteses em Lisp. Contudo elas falham em perceber que outras linguagens tem tantos elementos sintáticos quanto lisp, ou até mais, só que como eles são diferentes entre si ficam "espalhados" pelo código não dando uma sensação tão presente como os parênteses de Lisp. Como um exemplo simples, comparemos uma função simples e não otimizada para calcular números de fibonacci em Pascal e em Common Lisp: O código em Pascal:
function fib(N : integer) : integer;
begin
  if N < 2 then 
    fib := N;
  else
    fib := fib (N - 1) + fib (N - 2);
end;
O código em Lisp:
(defun fib (n)
  (if (< n 2)
      n
      (+ (fib (- n 1))
         (fib (- n 2)))))
A primeira vista o código em Lisp parece mais "poluído", com inúmeros parênteses, né? Bom, se excluirmos todas as coisas que o código em Pascal tem em comum com o código em Lisp (por exemplo, function é equivalente a defun, if, +, e - tem em ambos, etc) deixamos somente os elementos sintáticos que Pascal tem mas Lisp não precisa, teremos algo como:
: integer : integer ;
begin
then
fib :=
else
fib := ;
end;
Ou seja, 15 elementos sintáticos. Por outro lado, se fizermos o mesmo com Lisp, isso é, tirarmos tudo que o código Lisp tem em comum com o código em Pascal, vamos terminar com isso:
(
(()
(()())))
ou seja, um total de 12 elementos sintáticos. Não apenas o código em Lisp tem menos elementos sintáticos, como eles são uniformes (apenas parênteses), enquanto o código em Pascal tem vários elementos diferentes que o programador tem que se lembrar. Esse é um ponto-chave ao se programar em Lisp; na superfície parece que a linguagem é inundada de parênteses, mas no fundo, o número de elementos sintáticos é o mesmo, ou até menor, que a maioria das linguagens. Com a vantagem de que os elementos sintáticos usados são mais uniformes (apenas parênteses), enquanto que outras linguagens usam diversas coisas como parênteses, virgulas, pontos, colchetes, etc.

Posted at 14:05

May 26, 2007

Comandos mais usados

O comando abaixo permite ver de maneira simples quais os comandos que você executa mais frequentemente no linux:
history | awk '{print $2}' | awk 'BEGIN {FS="|"} {print $1}'| \
        sort | uniq -c | sort -rn| head -10 
Só por diversão é comum alguns hackers verificarem seus comandos mais comuns e postarem em algum lugar. O Dorneles postou seus comandos mais comuns, e de quebra mostrou uma implementação em Python.

Como Lisp não é (erroneamente) considerada uma linguagem apropriada para escrever scripts de sistema eu gosto de fazer uma uma versão em Lisp de scripts como esse para ver como ela se sai. A minha implementação pode ser vista abaixo. Eu usei apenas ANSI Common Lisp. Se eu tivesse usado bibliotecas externas o código poderia ser ainda mais compacto.

(defun get-comandos ()
  (with-open-file (stream "/home/kroger/.bash_history")
    (loop for line = (read-line stream nil nil) while line
       collect (subseq line 0 (position #\Space line)))))

(defun conta-itens ()
  (let ((comandos (get-comandos)))
    (mapcar (lambda (item) (list (count item comandos :test #'string=) item))
            (remove-duplicates comandos :test #'string=))))

(format t "~{~{~a ~}~%~}" (sort (conta-itens) #'> :key #'first))
O objetivo aqui não é, naturalmente, fazer uma comparação com python (no estilo "minha linguagem é melhor que a sua"), mas sim ver como Lisp se sai nesse tipo de coisa. Eu acho que o resultado foi bem satisfatório. Lisp não tem nenhum comando padrão para ler todo o arquivo em uma string, mas dá para fazer isso com apenas 2 linhas de código. A função count conta quantas vezes um item aparece em uma sequencia, de modo que foi possivel implementar o código para contar os elementos de uma maneira funcional.

Um pouco de estatísticas: a implementação de python tem 14 linhas e 46 caracteres, enquanto a implementação em lisp tem 10 linhas e 52 caracteres. A implementação em lisp é mais curta, porém mais densa.

Eu não tenho dúvidas que é possível escrever um código em python ainda menor, mas como eu disse anteriormente, esse não é o objetivo aqui. Meu objetivo foi mostrar como é possível escrever um código simples, curto, e elegante em Lisp para lidar com coisas corriqueiras do dia-a-dia.

Posted at 12:15

Mar 03, 2007

Como baixar o seu currículo lattes em XML (nerd edition)

Se você tem seu currículo na plataforma lattes do Cnpq vai ficar contente em saber que agora é possível baixar a versão XML do seu currículo usando a versão on-line da plataforma. A versão off-line foi descontinuada recentemente e a versão para linux ficou muito tempo sem atualizações e correções de bugs (eu enviei vários relatórios de bugs mas nunca recebi uma resposta). A versão em XML do currículo pode ser baixada clicando no icone apropriado na barra de ícones da plataforma lattes:

A vantagem de ter o currículo em XML é que ele pode ser usado como entrada para outro programa, ou você pode criar seus proprios filtros e estilos e fazer uma versão personalizada do currículo para impressão.

Naturalmente é um saco ter que usar o navegador e clicar diversas vezes para fazer uma coisa tão simples como baixar um arquivo XML. Nesse post eu vou mostrar como baixar seu currículo em XML usando o fantástico curl.

Curl pode ser definido como um navegador internet de linha de comando. Não confunda com navegadores de texto, como Lynx, o curl está para o Lynx assim como o sed está para o vi. Além da ferramenta de linha de comando, existe o libcurl, uma biblioteca com bindings para diversas linguagens como Lisp, Python, PHP, Ruby, C, etc. Nesse post vamos usar a ferramenta de linha de comando e criar um scrip simples em bash. Uma descrição detalhada do curl não cabe nesse post, mas um lugar bom para começar é o tutorial Using cURL to automate HTTP jobs no próprio site do curl. Com o curl é possivel criar scrips para fazer coisas como se logar na amazon e baixar sua lista de desejos, enviar (postar) figuras para wikis, etc.

Para poder baixar o currículo em XML é necessário se logar na plataforma lattes e preencher um formulário com cpf e senha. O código abaixo cuida dessa tarefa:

function site_login {
    curl -A "Mozilla/4.73 [en] (X11; U; Linux 2.2.15 i686)" \
        -d "sub=Confirma&opcao=lattes&cpf=${cpf}&senha=${senha}" \
        -k -e ";auto" \
        -b "imp=cnpqrestritos ; path=/; domain=.cnpq.br" \
        -c $tmpdir/cookie.txt\
        -o $tmpdir/login.html \
        "https://wwws.cnpq.br/sigef_imp/owa/pservicos.entrada"
}

Algumas páginas não funcionam direito em certos navegadores, ou funcionam diferentemente para programas que baixam páginas, como o spider do google. A flag -A diz para o servidor que quem está acessando a página é o Mozilla. A opção -d tem as opções para o formulário de login , com duas variáveis para cpf e senha, respectivamente. A opção -k aceita os certificados de segurança do site. A página de login tem uma complicação, ela gera cookies via javascript. O curl tem como lidar com cookies automaticamente com a opção -c, mas nesse caso tivemos que criar o cookie na mão com -b. A página de login será armazenada no arquivo login.html (flag -o). Finalmente, o último argumento indica a página de login.

O código para baixar o XML pode ser visto abaixo. As opções -c e -b lidam com os cookies e a opção -e envia a informação de "página de referência" (Referer Page) para o servidor, já que essa página torna isso obrigatório. Ou seja, se você copiar a (última) url diretamente no seu navegador não vai funcionar porque teria que ter acessado a url logo depois de -e primeiro. Felizmente o curl cuida disso automaticamente. Finalmente, observe que as páginas usam uma variável id.

function baixa_pagina {
    curl -A "Mozilla/4.73 [en] (X11; U; Linux 2.2.15 i686)" \
        -e "http://plsql1.cnpq.br/curriculo/pkg_menu.menu?f_cod=${id}" \
        -c $tmpdir/cookie.txt -b $tmpdir/cookie.txt \
        -o lattes.zip \
        "http://plsql1.cnpq.br/curriculo/pkg_xml.recupera_xml_cv?f_cod=${id}"
}

Para obter o id você precisará se logar na página do cnpq usando um navegador comum. Quando você se loga, a url mostrada é http://plsql1.cnpq.br/curriculo/gn_seg.inicio. Essa página usa frames, então se você clicar com o botão direito no meio da página e escolher "this frame", e "show only this frame" vai ver que a url muda para algo como http://plsql1.cnpq.br/curriculo/pkg_menu.menu?f_cod=K3912383Z3. O código K3912383Z3 é o seu id (é claro que esse não é o meu id ;-) Naturalmente existem diversas maneiras de achar o seu id, essa é uma delas.

Eu sugiro colocar as informações de senha e cpf em um arquivo separado (como ~/.lattesrc) com permissão 600. O arquivo deve ter o seguinte formato (sem espaço antes e depois do =):

id=K3912383Z3
senha=foobar
cpf=75687344611
Então é só criar uma função para ler essas informações (pase_init). O script completo pode ser visto abaixo:
#!/bin/sh

initfile=~/.lattesrc
tmpdir=/tmp

function parse_init {
    if [ -f $initfile ]
    then
        source $initfile
    else
        echo "Arquivo ~/.genos não existe, por favor crie um arquivo com os dados:"
        echo "usuario="
        echo "senha="
        exit
    fi
}

function site_login {
    curl -A "Mozilla/4.73 [en] (X11; U; Linux 2.2.15 i686)" \
        -d "sub=Confirma&opcao=lattes&cpf=${cpf}&senha=${senha}" \
        -k -e ";auto" \
        -b "imp=cnpqrestritos ; path=/; domain=.cnpq.br" \
        -c $tmpdir/cookie.txt\
        -o $tmpdir/login.html \
        "https://wwws.cnpq.br/sigef_imp/owa/pservicos.entrada"
}

function baixa_pagina {
    curl -A "Mozilla/4.73 [en] (X11; U; Linux 2.2.15 i686)" \
        -e "http://plsql1.cnpq.br/curriculo/pkg_menu.menu?f_cod=${id}" \
        -c $tmpdir/cookie.txt -b $tmpdir/cookie.txt \
        -o lattes.zip \
        "http://plsql1.cnpq.br/curriculo/pkg_xml.recupera_xml_cv?f_cod=${id}"
}

parse_init && site_login && baixa_pagina

Uma vantagem dessa abordagem é a separação das informações sensíveis (senhas, etc) e o script em si. Eu não achei um jeito simples de fazer o script achar o id automaticamente. Isso é desejável já que o script não está plug and play, ou seja, se você executá-lo agora ele não vai rodar porque não tem o seu id. Você terá que descobrir o id primeiro. Não é impossível fazer com que o script faça isso automaticamente, mas fica para depois. De qualquer forma, tendo visto qual o seu id você pode baixar seu currículo lattes em XML diversas vezes. Agora só falta eles implementarem a possibilidade do sistema on-line poder importar o currículo no formato XML.

Posted at 12:10

Feb 26, 2007

Pretty lambdas

Um recurso interessante do emacs é a capacidade de mostrar um texto de maneira diferente do que foi escrito. Um exemplo óbvio disso é para mostrar versões WYSIWYG de documentos de marcação como o LaTeX. Outro uso interessante é para mostrar o caractere lambda no lugar do nome "lambda". Por exemplo, se eu digitar a função abaixo:
lambda1

no momento que eu terminar de digitar "lambda", o emacs vai mostrar o caractere lambda no lugar. Isso é útil para deixar o código mais limpo e ocupar menos espaço.
lambda1

Tudo o que é preciso fazer é usar a função pretty-lambdas abaixo:

(defun pretty-lambdas ()
  (font-lock-add-keywords
   nil `(("(\\(lambda\\>\\)"
          (0 (progn (compose-region (match-beginning 1) (match-end 1)
                                    ,(make-char 'greek-iso8859-7 107))
                    nil))))))

E "conectar" essa função com os modos desejados:

  (add-hook 'emacs-lisp-mode-hook 'pretty-lambdas)
  (add-hook 'lisp-mode-hook 'pretty-lambdas)

Posted at 16:13

Feb 14, 2007

Lisp Não é Uma Linguagem de Programação Funcional.

Repita comigo: Lisp não é uma linguagem de programação funcional. Novamente: Lisp não é uma linguagem de programação funcional. Uma última vez: Lisp não é uma linguagem de programação funcional.

Acho que agora posso começar o artigo :-)

Primeiramente, Lisp não é uma linguagem. O termo "Lisp" se refere a uma família de linguagens. Nos anos 80 iniciou-se um esforço para padronizar os diversos dialetos de Lisp que existiam como Maclisp, Interlisp, Zetalisp, entre outros. Esse esforço resultou no Common Lisp, que é definido pelo padrão ANSI. Hoje em dia existem três dialetos principais: Common Lisp, Scheme, e Emacs Lisp, sendo que os dois primeiros são usados para programação geral e o último como linguagem de extensão para o Emacs.

Sim, o criador do Lisp, John McCarthy, incorporou diversas idéias do cálculo lambda na linguagem. Isso não é surpreendente, já que McCarthy foi aluno do Alonzo Church (o criador do cálculo lambda). Mas isso foi no final dos anos 50! Nesse meio-tempo (ou deveria dizer meio século?) diversos paradigmas de programação foram incorporados nos mais diversos dialetos de Lisp. Na verdade, não só esses paradigmas foram incorporados como Lisp foi usado como inspiração ou veículo de pesquisa para trabalhos chaves como o Smalltalk. Mas isso é conversa para outro post.

O ponto chave desse post é que Common Lisp tem suporte integrado para os mais diversos tipos de paradigmas como funcional, imperativo, aspectos, além de ter um dos mais avançados sistemas de programação a objetos. Então dá para imaginar minha surpresa quando eu envio o texto abaixo para divulgar o II Encontro Lisp-br no FISL 8.0:

Lisp abrange uma família de linguagens como Common Lisp e Scheme. Lisp
é uma linguagem multi-paradigma com diversos recursos e abstrações
para programação funcional, procedural, imperativa, aspectos, um
sistema avançado e flexível de programação orientada a objetos, e um
carater dinâmico sem precedentes.
E eles publicam no lugar o seguinte texto:
O Lisp é uma família de linguagem de programação funcional,
sendo que as mais conhecidas são Common Lisp e Scheme.
Pô, eu sei que as vezes tem que cortar o texto para ficar menor, mas que tal colocar:
O Lisp é uma família de linguagem de programação
multi-paradigma, sendo que as mais conhecidas são Common Lisp e
Scheme.
Seria muito mais fiel com o texto que eu enviei e com a verdade. Segundo o Mario Goulart (do Lisp-br) esse não é um caso isolado. Esse tipo de coisa já aconteceu com ele. Porque as pessoas tem dificuldade em aceitar que Common Lisp e Scheme são linguagens multi-paradigma?

Na verdade você fazer programas puramente funcionais com Lisp (como eu faço), ou usar apenas classes e objetos sem nenhum recurso funcional. Ou ainda, você pode usar um estilo de programação do tipo fluxograma (com algo semelhante ao GOTO com esteróides e tudo), útil para estudar e testar os algoritmos do The Art of Computer Programming (TAOCP). A propósito, antes de fazer cara feia para o GOTO, você já leu o clássico Structured Programming with Goto Statements do Knuth?

O livro Practical Common Lisp (ou PCL para os íntimos) é uma excelente referência para quem quer saber mais sobre Common Lisp e como usar os diversos paradigmas, como programação orientada a objetos.

Posted at 00:05

Jan 31, 2007

Emacs e Rails

E agora para algo completamente diferente, dois vídeos mostrando recursos do emacs para Ruby on Rails. Vale a pena ver mesmo se você não usa emacs ;-)

O primeiro é um vídeo com áudio e mostra alguns recursos gerais. O segundo é um screencast (sem áudio) mostrando alguns recursos interessantes para navegar pelo código do Rails.

Posted at 18:15

Jan 23, 2007

Tecnologia WEB e Gol

A sistema de validação no site da gol é uma piada. Eu acabei de tentar usar o atendimento on-line da gol. Para acessa-lo é necessário entrar diversos dados como email e telefone. Eu coloquei o meu email kroger em pedrokroeger ponto net e o sistema de validação da página deu erro porque dizia que o email estava provavelmente errado. Não, não havia nenhum erro de digitação. Daí eu digitei foo@bar.com como email e o sistema aceitou.

Mas a coisa não para por aí. A algum tempo atras eu tive problemas em entrar meu nome completo (ele tem 27 caracteres) no campo para esse fim quando se compra uma passagem. O campo não aceitava o nome completo e se eu deixava alguma coisa de fora o sistema acusava erro porque o nome não era o mesmo do CPF. Quando eu vi a fonte em HTML da página, notei que eles limitam o número de caracteres do campo nome no formulario para 20 caracteres. O que? Porque não colocar um número mais "conservador" como 50? Porque não colocar limitação nenhuma?

O pior é que eu, com meu treinamento "clássico" do mundo open source em reportar bugs, enviei um bug report para o caso da limitação do número de caracteres do formulario com trechos do código HTML, maneiras de reproduzir o problema, etc. Tudo que ganhei foi uma resposta esfarrapada. Você acha que eu vou reportar o problema da verificação do email? No way! Para referência, esse é o email que enviei para a Gol:

Ao comprar passagens pela internet na gol o usuario entra diversos dados
pessoais como endereço, etc até chegar a pagina onde os dados do cartão
de credito sao pedidos. O campo do formulario que pede o nome do
proprietário do cartão só aceita um máximo de 20 caracteres e o nome
deve ser igual ao CPF, então um nome como o meu "Pedro Ribeiro Kroger
Junior" não pode ser digitado no campo do formulário e nao pode ser
abreviado porque o validador recusa porque ele nao é igual ao CPF. esse
é o trecho da página:

<td nowrap="nowrap" align=left>
<font face="arial,Arial,Helvetica" size=2 color=#000000>
Nome do proprietário do cartão <br>
<font size=1> (Nome idêntico ao do CPF)
</font></font></td>
<td></td>
<td colspan="2">
<input type="TEXT" name="cc_holder" maxlength="20"
onChange="filterFieldValue(this,1,20); cardEvent(this, 'CC')"
class='SBlarge' size="26" value="" />
</td>

o valor maxlength deveria ser _muito_ maior que 20 caracteres.

por favor encaminhem isso para o setor de desenvolvimento da página
porque _todos_ os usuários com nome maior que 20 caracteres terão
problemas em comprar passagens on-line. (e se forem como eu vão procurar
outras empresas aereas)

para reproduzir esse erro:

1. escolha um voo pela pagina da gol, clique em continue

2. digite as "Informações do(s) Passageiro(s)" e "Informações de Contato
   do Comprador", e clique em continue

3. digite as informações de cartão de credito colocando um nome com mais
   de 20 caracteres no campo "Nome do proprietário do cartão (Nome
   idêntico ao do CPF)"

Atenciosamente,

Pedro Kröger

Posted at 10:36

Jan 20, 2007

Processando XML com Lisp

Esse post bastante interessante sobre um (pequeno) programa em Factor para converter um documento no formato de XML do Excel para CSV (comma separated value) me inspirou a escrever algo sobre processamento de XML com Lisp.

Naturalmente, existem diversas maneiras de se trabalhar com XML em Lisp, incluindo representação em objetos, DOM, SAX, Xpath, etc. Nesse artigo eu vou falar sobre uma maneira "old school" (da velha guarda :-) que é bastante produtiva e interessante.

Talvez você tenha observado que as expressões simbólicas (a representação que Lisp usa para dados e código) e XML são bem parecidas. O exemplo abaixo mostra um arquivo em XML para representar produtos. Cada produto possui dados como nome do produto, preço, e fornecedor:

<produtos>
  <produto>
    <nome>Cadeira</nome>
    <preco>35.13</preco>
    <fornecedor>Pedro</fornecedor>
  </produto>
  <produto>
    <nome>Mesa</nome>
    <preco>45.13</preco>
    <fornecedor>Pedro</fornecedor>
  </produto>
</produtos>
O exemplo anterior poderia ser representado da seguinte maneira usando expressões simbólicas em Lisp:
(produtos
  (produto
   (nome "Cadeira")
   (preco "35.13")
   (fornecedor "Pedro"))
  (produto
   (nome "Mesa")
   (preco "45.13")
   (fornecedor "Pedro"))
Sim, eu posso copiar o trecho acima e colar no meu código Lisp e usar tranquilamente, sem conversão ou parseamento. Para clarificar vamos ver um exemplo prático. O exemplo abaixo guarda uma lista de produtos na variável produtos:
(setf produtos 
  '((produto (nome "Cadeira") (preco 35.13) (fornecedor "Pedro"))
    (produto (nome "Mesa") (preco 45.13) (fornecedor "Pedro"))
    (produto (nome "Tapete") (preco 95.13) (fornecedor "Pedro"))))
A vantagem dessa abordagem é que como a representação acima é um código válido de Lisp, eu posso usar as funções padrões para extrair informações. Por exemplo, (first produtos) retorna a lista com a representação do primeiro produto:
  (produto (nome "cadeira") (preco 35.13) (fornecedor "pedro"))
O comando (second produtos) retorna a lista do segundo produto e assim por diante. Naturalmente, combinações são permitidas, como pegar o segundo elemento dentro do terceiro elemento do segundo produto, que nesse caso vai retornar o preço do produto:
(second (third (second produtos))) => 45.13
Common Lisp é a linguagem para trabalhar com listas. Então fazer coisas como usar filtros para selecionar os produtos com o preço maior que 40 é tão simples como:
  (remove-if-not (lambda (x) (> (second x) 40)) produtos :key #'third)
Então, visto que XML e expressões simbólicas são semelhantes, uma maneira muito comum e natural de trabalhar XML com Lisp é usar um parser que converte uma expressão XML em uma expressão simbólica. Existem vários parsers que fazem isso. Nesse exemplo eu vou usar o s-xml. O programa do post original converte arquivos do Excel. Como eu não tenho esse programa, vou criar um programa que converte arquivos do scalc, a planilha do OpenOffice, para CSV. Claro que o OpenOffice tem uma função para exportar para csv, esse código que veremos é só um exemplo.

Para parsear um arquivo XML e converter para expressões simbólicas (sexp), é apenas necessário rodar o comando parse-xml-file. Ele retorna uma lista com a representação em sexp.

  (s-xml:parse-xml-file "arquivo.xml")
Para extrair o valor da primeira célula, por exemplo, o seguinte código poderia ser usado:
  (second (second (second (third (second (second (fifth (s-xml:parse-xml-file "arquivo.xml"))))))))
Contudo, essa é a pior maneira lidar com dados. Nenhum programador deveria escrever algo assim em qualquer linguagem. Dessa maneira não há abstração de dados. Que parte do documento está sendo extraída? É impossivel dizer em uma primeira olhada. Outro problema é que se o arquivo XML mudar, o código vai extrair alguma informação errada, no lugar da que queremos. A melhor maneira de lidar com esse tipo de coisa é criando barreiras de abstração.

A primeira função que vamos criar aceita uma expressão contendo o documento inteiro e retorna apenas o corpo do documento. Tudo que está na marcação office:body

(defun get-body (expr)
  (first (remove-if-not (lambda (x) (and (listp x) (eql (first x) '|office|:|body|)))
                        expr)))
A segunda função retorna o código que contém a marcação office:spreadsheet
(defun get-spreadsheet (expr)
  (second (get-body expr)))
A próxima função retorna a folha (sheet) dentro de uma planilha que desejamos extrair os dados. Cada arquivo pode ter inúmeras colhas diferentes. Por padrão ela retorna a primeira folha.
(defun get-sheet (expr &optional (sheet-n 1))
  (nth (1- sheet-n) (remove-if-not #'listp (get-spreadsheet expr))))
A próxima função retorna apenas o código com as linhas da tabela (ficam dentro da marcação table:table-row:
(defun get-rows (expr &optional (sheet-n 1))
  (remove-if-not (lambda (x) (eql '|table|:|table-row| x))
                 (remove-if-not #'listp (get-sheet expr sheet-n) :key #'first)
                 :key #'caar))
Essa função retorna uma lista com os valores e dados das células:
(defun get-cell-data (expr &optional (sheet-n 1))
  (mapcar (lambda (x) (subseq x 1)) (get-rows expr sheet-n)))
Observe que cada função que criamos chama a anterior. get-cell-data chama get-rows, que chama get-sheet, que chama get-spreadsheet, que finalmente chama get-body. Isso acontece porque essas sessões (linhas, páginas, planilha) estão aninhadas no documento. Escrever funções específicas para partes específicas do documento ajuda na legibilidade e em manutenções futuras.

Finalmente, a função seguinte retorna uma lista com listas com os dados para cada linha da planilha. Por exemplo, ((foo bar) (10 100)):

(defun extract-cell-data (expr &optional (sheet-n 1))
  (mapcar #'(lambda (w) (mapcar #'(lambda (x) (second (second x))) w))
          (get-cell-data expr sheet-n)))
Pronto, o código abaixo vai ler o arquivo XML e extrair apenas as informações da planilha e colocar em um formato de listas.
  (extract-cell-data (s-xml:parse-xml-file "arquivo.xml"))
Por exemplo, os dados dessa planilha: planilha
seriam representados da seguinte maneira, após a execução do código acima:
  (("nome" "telefone" "profissao") ("pedro" "3333-4040" "hacker")
  ("joao" "4444-1010" "programador") ("fulano" "5555-2020" "faxineiro"))
Tudo que precisamos agora é um código para converter a lista acima em um formato separado por vírgulas. Isso é bem simples de fazer com format:
  (format stream "~{~{~a~^, ~}~%~}" lista)
O formato do OpenOffice na verdade é um arquivo compactado com diversos arquivos dentro. Por exemplo, o comando unzip -l em um arquivo do openoffice mostra a seguinte saida:
  46  01-20-07 22:03   mimetype
   0  01-20-07 22:03   Configurations2/statusbar/
   0  01-20-07 22:03   Configurations2/accelerator/current.xml
   0  01-20-07 22:03   Configurations2/floater/
   0  01-20-07 22:03   Configurations2/popupmenu/
   0  01-20-07 22:03   Configurations2/progressbar/
   0  01-20-07 22:03   Configurations2/menubar/
   0  01-20-07 22:03   Configurations2/toolbar/
   0  01-20-07 22:03   Configurations2/images/Bitmaps/
5644  01-20-07 22:03   content.xml
6155  01-20-07 22:03   styles.xml
1032  01-20-07 22:03   meta.xml
 952  01-20-07 22:03   Thumbnails/thumbnail.png
8389  01-20-07 22:03   settings.xml
1873  01-20-07 22:03   META-INF/manifest.xml
De todos esses arquivos o que nos interessa é o content.xml. A próxima função usa o unzip para extrair esse arquivo, converte a representação de listas para formato separado por vírgulas (usando o código com format visto acima) e envia tudo para um arquivo de saída:
(defun lista->csv (ods-file csv-file &optional (sheet-n 1))
  (sb-ext:run-program "/usr/bin/unzip" (list "-d" "/tmp/" "-o" ods-file "content.xml"))
  (with-open-file (stream csv-file :direction :output :if-exists :supersede)
    (format stream "~{~{~a~^, ~}~%~}"
            (extract-cell-data (s-xml:parse-xml-file "/tmp/content.xml")
                               sheet-n))))
Finalmente, o seguinte código vai converter o arquivo de planilha do openoffice foo.ods para foo.csv, usando a função acima:
  (lista->csv "foo.ods" "foo.csv")
Para converter diferentes folhas é só acrescentar o número da folha no final. Por exemplo, para converter a terceira folha:
  (lista->csv "foo.ods" "foo.csv" 3)
Esse foi um post longo porque eu fui mostrando como fazer as coisas passo-a-passo. Eu espero que não pareça mais complexo do que é na verdade. O código total tem 30 linhas, o que é um número razoável. O código poderia ser "expremido" para ficar com menos linhas. Por exemplo, a função get-spreadsheet só tem uma linha e só é usada em get-sheet. Eu poderia fundir as duas em uma só. Mas a vantagem de ter o código dividido em funções como está é que a legibilidade é maior e ajuda na manutenção do código. Se no futuro o formato do arquivo XML mudar fica mais fácil saber onde fazer a mudança no código.

Uma grande vantagem dessa abordagem é que praticamente nenhum código no programa foi específico de XML, nós trabalhamos com listas o tempo inteiro. Isso ajuda a separar a apresentação do conteúdo, e não deixa o código fixo em nenhuma tecnologia. Se no futuro o openoffice usar outro formato diferente do XML eu só precisarei mudar uma linha no meu código fonte.

Uma outra vantagem é que a função extract-cell-data retorna os valores em listas simples. Com isso podemos facilmente usar funções para converter essa representação de listas para outros formatos como HTML, LaTeX, ou CSV, como foi nosso caso. Por exemplo, o seguinte código é suficiente para gerar uma tabela em HTML a partir da lista gerada por extract-cell-data

(format stream "<table>~{<tr>~{<td>~a</td>~}</tr>~%~} </table>" lista)
Espero que tenha gostado de ver como se pode trabalhar com XML em Lisp. Tudo sem vodu de XML :-)

Posted at 19:53

Jan 18, 2007

Ruby e Lisp

As vezes os usuários de linguagens novas como Tcl, Python, e Ruby não percebem o quanto essas linguagens foram influenciadas por Lisp. Talvez essa mensagem do criador do Ruby, Yukihiro Matsumoto, ajude a enteder o processo:
Ruby é uma linguagem criada nos seguintes passos:
  • pegue uma linguagem lisp simples (como uma antes de CL)
  • remova macros e expressões simbólicas
  • acrescente um sistema de objetos simples (muito mais simples que CLOS)
  • acrescente blocos, inspirado por funções de ordem superior
  • acrescente métodos encontrados em Smalltalk
  • acrescente funcionalidades encontradas em Perl (mas em uma maneira orientada a objetos)
Então, Ruby foi um tipo de Lisp originalmente, teoricamente. Vamos chama-lo de MatzLisp de agora em diante. ;-)
Claro que os lispers vão dizer que Ruby seria melhor se tivesse os itens que lisp tem e que foram "retirados", e os programadores de Ruby vão dizer que essa é uma das razões que Ruby é uma boa linguagem :-)

Posted at 08:55

Jan 13, 2007

Quem disse que Lisp é lento?!

O Guaracy fez um post interessante sobre benchmarks e comparação entre linguagens. Eu concordo com ele que comparações entre linguagens tem valor quando são bem feitas, o que nem sempre é o caso. Muitas vezes quem faz uma comparação entre linguagens acaba fazendo um código que não é bem representativo da linguagem. Por exemplo, esse é um código de Python listado na comparação:
  print(math.sqrt(reduce(add, map(square, a))))
Como Guaracy aponta, apesar dele ser válido, não é exatamente Pythonico, nem necessariamente rápido. De fato, ele parece muito com Lisp. Até o número de parênteses é o mesmo :-) A versão abaixo é uma transcrição do código para Common Lisp.
  (print (sqrt (reduce #'+ (mapcar #'square a))))
No post Guaracy aponta versões para o código de Python que são mais rápidas que o original. Eu resolvi experimentar e ver como Common Lisp se saia nesse caso. Uma implementação simples pode ser vista abaixo:
(time
  (let ((a (loop for x from 1 to 100000 collect (random 1.0))))
    (print (reduce #'+ a))
    (print (sqrt (reduce #'+ (mapcar #'(lambda (x) (* x x)) a))))))
Não apenas essa versão é mais curta e simples de todas as versões na comparação original, como ela é mais rápida (0.036) que a versão de python original (0.24s) e a versão otimizada do Guaracy (0.12s) (todas elas rodando no meu computador, é claro). Eu não consegui compilar a versão de C++, provavelmente porque ela foi escrita para o VC++. Naturalmente, esse código ainda pode ser otimizado. A versão abaixo roda em 0.014s.
(defun run2 ()
  (let ((a1 0)
        (a2 0))
    (loop for x from 1 to 100000 do
         (let ((n (random 1.0)))
           (setf a1 (+ a1 n))
           (setf a2 (+ a2 (* n n)))))
    (print a1)
    (print (sqrt a2))))

(time (run2))
Ela não é particularmente bonita, mas é bem mais rápida que a versão original. Ambas as versões de Lisp foram compiladas com SBCL. Resolvi experimentar com o CMUCL (um compilador de Lisp altamente otimizado). A primeira versão levou 0.07s para rodar, enquanto a segunda executou em 0.03s. Uau!

Naturalmente outras otimizações poderiam ser feitas, mas é interessante notar que a primeira versão de Lisp tem apenas 3 linhas (sem contar o time) e executa em 0.07s. Um excelente tempo para um código bem compacto e elegante. Eu acho reconfortante saber que pode-se extrair uma performance ainda melhor se for necessário. Eu acho incrível que algumas pessoas não usem Lisp porque "é lento" e acabem usando algo como Python no lugar.

Um artigo bem interessante para quem quiser saber mais sobre otimização em Common Lisp é o How to make Lisp go faster than C.

Atualização: Um comentário feito pelo Jorge Godoy No blog do Guaracy recomenda o uso do psyco para o código de Python. De fato, com o psyco o tempo de execução da versão otimizada de python cai para 0.09s, um ganho significativo.

Posted at 23:49

Jan 10, 2007

Linux Kernel in a Nutshell

O livro Linux Kernel in a Nutshell foi lançado recentemente pela O'Reilly. O melhor de tudo é que o livro é distribuído sob os termos da licença Atribuição-Compartilhamento da Creative Commons. O livro é uma referência completa de como compilar, costumizar, e usar os parâmetros de boot do kernel do Linux. Ele é escrito por Greg Kroah-Hartman, um dos desenvolvedores do Kernel. Uma citação do site oficial do livro:
If you want to know how to build, configure, and install a custom Linux kernel on your machine, buy this book. It is written by someone who spends every day building, configuring, and installing custom kernels as part of the development process of this fun, collaborative project called Linux. I'm especially proud of the chapter on how to figure out how to configure a custom kernel based on the hardware running on your machine. This is an essential task for anyone wanting to wring out the best possible speed and control of your hardware.

O mais genial é que todo o histórico do livro está disponível em um repositório git. Desse modo pode-se acompanhar a evolução do livro (que começou com 1000 páginas e terminou com 175!), a diferença que os editores e revisores técnicos fizeram, etc.

Posted at 23:05

Jan 06, 2007

Programação de Computadores como Arte

Existem várias idéias se a Ciência da Computação é de fato uma ciência, ou é alguma forma de engenharia ou arte. O meu texto favorito sobre o assuto é a palestra Computer Programming as an Art de 74 do Knuth quando ele ganhou o Turing Award. O sumário da palestra é:
Nós vimos que a programação de computadores é uma arte, porque ela aplica conhecimento acumulado ao mundo, porque ela requer habilidade e engenhosidade, e especialmente porque ela produz objetos de beleza. Um programador que subconcientemente se vê como um artista irá gostar do que faz e o fará melhor. Desse modo nos podemos ficar contentes quando pessosas que fazem palestras em conferências sobre computação falam sobre o estado da Arte.
Claro que filosofar sobre arte e ciência é "fácil", mas o que faz esse texto do Knuth ser particularmente bom e porque ele tem propriedade para falar sobre isso? Knuth é um dos mais renomados cientistas da computação do mundo, autor do The Art of Computer Programming, criador do TeX, do sistema de criação de fontes METAFONT, e pioneiro da programação literária. Sem contar que ele é um músico (ele toca orgão de tubos), ou seja, entende de questões relacionadas a arte. Uma outra citação favorita do artigo:
Criadores de linguagens [de programação] tem a obrigação de prover linguagens que encorajam bom estilo, já que todos sabemos que o estilo é fortemente influenciado pela linguagem em que ele é expressado.
Finalmente, um post interessante sobre o assunto pode ser encontrado aqui. O autor perguntou a alguns programadores o que eles achavam do assunto e postou o resultado. Eu gosto muito da resposta do Guido van Rossum (o criador da linguagem Python):
If there was no art in it, it wouldn't be any fun, and then I wouldn't still be doing it after 30 years.

Posted at 12:51