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