sexta-feira, 22 de agosto de 2008

Desenferrujando

Se você gosta de programar e adora Perl... nah... mesmo que você odeie Perl, pare o que está fazendo e assista já à entrevista do Damian Conway.

São meros 35 minutos em que ele fala do seu PhD em biologia computacional, do sotaque "errado" dos americanos, de linguagens de programação em geral e de Perl 6 especificamente. Eu nunca li ou ouvi uma explicação mais interessante sobre a diferença entre tipagem estática e tipagem dinâmica.

E o final da última resposta merece até uma tentativa de tradução:
...para ser um bom programador você tem que efetivamente programar. E isso é algo que não acontece. Sabe, a gente estuda computação, a gente aprende todas aquelas coisas e fica o tempo todo fazendo exercícios e provas. Daí a gente se forma e começa a freqüentar reuniões, a desenhar modelos, diagramas e todo o resto e você pára de programar. E se você é promovido, então você é literalmente promovido a perder a oportunidade de continuar programando e eu acho que isso é um problema. Se você quer ser um jogador de tênis realmente bom, você vai treinar todos os dias. Se você quer ser um grande lutador de artes marciais você vai pro tatame todo santo dia. Se você quer ser um grande programador, você vai programar todo dia—mesmo que você tenha que usar seu próprio tempo pra isso. Se você está acordado, você é um programador. Se você está acordado às 11 horas da noite, ou às 3 da manhã, você tem que usar parte desse tempo para programar porque assim que você começa a enferrujar você começa a morrer como um programador.

terça-feira, 19 de agosto de 2008

Nerdiness


I am nerdier than 88% of all people. Are you a nerd? Click here to find out!


Hmmm... devo ter mentido um pouco nas respostas porque não esperava que fosse tão alto assim.

O teste é antigo. Acho que eu já o havia feito há vários anos, mas nem me lembro quanto deu naquela época. Talvez eu tenha melhorado de tanto ouvir o nerdcast.

Lembrei do teste depois de ver o resultado do TK... acho que ele já foi muito mais nerd nos bons tempos. :-)

sábado, 16 de agosto de 2008

TIMTOWTDI

Essa semana eu e minha equipe assistimos a uma palestra muito interessante sobre Python, ministrada pelo meu colega João Bueno. Lá pelas tantas ele começou a apresentar uns slides perigosos... cada um comparando Python a uma outra linguagem de programação. Perl, bash, Java e Ruby. Esses slides são perigosos porque comparar decentemente duas linguagens é uma tarefa complexa que não se pode condensar em um só slide. Primeiro é preciso definir um conjunto de critérios objetivos para a comparação. Depois, é preciso levar em conta o contexto de uso da linguagem. Coisas como o domínio das aplicações que serão desenvolvidas, as plataformas de desenvolvimento e de implantação, a experiência dos desenvolvedores com a linguagem, o tamanho da equipe e as restrições de prazo do projeto. Depois disso tudo, é preciso resistir arduamente à tentação de "puxar a sardinha" pro lado da linguagem de nossa predileção pra não parecer que estamos apenas fazendo picuinha.

Mas tudo bem... num grupo pequeno esse tipo de discussão é tão estimulante e inofensiva quanto falar de política, futebol e bolsa de valores num happy hour.

Acho que foi no slide sobre bash que o João sugeriu um problema para o qual uma shell Unix padrão não ofereceria uma solução tão econômica e legível quanto o interpretador de comandos interativo Python. O problema era, mais ou menos, o seguinte. Suponha que haja em um diretório um conjunto de arquivos cujos nomes consistem de um prefixo alfabético, seguido de uma sequência de dígitos e terminando na extensão .jpg. Por exemplo:

 $ ls
 a0.jpg  b1.jpg  c123.jpg

O desafio é renomeá-los de modo que todos os arquivos tenham o mesmo número de dígitos. No caso acima, o resultado deveria ser: a000.jpg, b001.jpg e c123.jpg.

Eu saí da palestra com o problema na cabeça e a primeira coisa que fiz foi bolar algumas soluções de uma linha e mandar pra ele por email:

 # imprimindo os nomes
 $ ls | perl -lpe 's/^([a-z]+)(\d+)\.jpg/sprintf "%s%03d.jpg",$1,$2/e'
 a000.jpg
 b001.jpg
 c123.jpg

 # gerando comandos para renomeá-los
 $ ls | perl -lpe 's/^([a-z]+)(\d+)\.jpg/sprintf "mv %s %s%03d.jpg",$&,$1,$2/e'
 mv a0.jpg a000.jpg
 mv b1.jpg b001.jpg
 mv c123.jpg c123.jpg

 # executando os comandos na shell
 $ ls | perl -lpe 's/^([a-z]+)(\d+)\.jpg/sprintf "mv %s %s%03d.jpg",$&,$1,$2/e' | sh

É assim que eu normalmente desenvolvo uma solução na shell. Ao invés de loops eu prefiro usar comandos que gerem outros comandos, como os mv acima, de modo que eu posso verificar facilmente se estou fazendo a coisa certa. Depois de ter certeza disso, basta acrescentar um "| sh" no final pra executar os comandos gerados.

Perl tem algumas opções muito úteis na confecção de one liners como o anterior. -l, -a, -n, -p e -e são as que eu utilizo mais frequentemente. Execute um "perldoc perlrun" pra saber mais sobre elas e outras tantas opções interessantes.

Mas, pra não dizer que Perl não pode fazer as coisas sozinho eu acrescentei uma solução que não usa a shell no final.

 # fazendo tudo sozinho
 $ ls | perl -lne 'if (/^([a-z]+)(\d+)\.jpg/) {rename $_,sprintf "%s%03d.jpg",$1,$2}'

 $ ls
 a000.jpg  b001.jpg  c123.jpg

Mas assim como eu sou fã de Perl e o João é fã de Python, o Andreyev é fã de Bash e não deixou barato, mandando o seguinte email pro grupo:

 $ ls
 a0.jpg  b1.jpg  c123.jpg

 $ for i in *.jpg; do j=${i%*.jpg}; printf "mv %s %s%03d.jpg\n" $i ${j//[0-9]/} ${j//[a-z]/}; done
 mv a0.jpg a000.jpg
 mv b1.jpg b001.jpg
 mv c123.jpg c123.jpg

 $ for i in *.jpg; do j=${i%*.jpg}; printf "mv %s %s%03d.jpg\n" $i ${j//[0-9]/} ${j//[a-z]/}; done | sh

 $ ls
 a000.jpg  b001.jpg  c123.jpg

 $ echo $BASH_VERSION
 3.2.25(1)-release

Ninja! Eu vou confessar que nunca tive força de vontade pra aprender esses golpes avançados de manipulação de strings em bash. Pra mim, shell é uma cola que serve pra "grudar" outros comandos. Sempre que eu preciso de algo mais complicado, como estruturas de dados ou expressões regulares, eu não penso duas vezes antes decidir por Perl.

Mas o João pagou pra ver com essa:

 > $ python
 > Python 2.5.2 (r252:60911, Jul 31 2008, 17:28:52)
 > [GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)] on linux2
 > Type "help", "copyright", "credits" or "license" for more information.
 >>>> import os
 >>>> for nome in os.listdir("."):
 > ...   base, numero, ext = nome[0], nome[1:nome.find(".")], nome.split(".")[-1]
 > ...   os.rename(nome, "%s%03d.%s" % (base, int(numero), ext))
 > ...
 >>>>

 > # readability counts

Ah... que crítica sutil nesse último comentário... "Legibilidade conta."

É?

Mas Succinctness is Power!

Quando eu quero resolver uma questão com um one liner a "legibilidade" é irrelevante, porque se eu não vou salvar a solução num script, ninguém mais vai lê-la, certo? Mas vá lá... se fosse pra salvar num script eu provavelmente escreveria algo mais parecido com a sua versão em Python. Algo assim:

    opendir CWD, '.';
    foreach $nome (readdir CWD) {
        if (($base, $numero, $ext) = ($nome =~ /^(.)(\d+)\.(.*)/)) {
           rename $nome, sprintf("%s%03d.%s", $base, $numero, $ext);
        }
    }
    closedir CWD;

Hmmm... eu nem tentei quebrar o nome com operações de strings porque eu acho a expressão regular mais direta e, nesse caso, mais legível. Pra ficar ainda mais legível eu substituiria os comandos opendir, readdir e closedir por um glob pattern:

    foreach $nome (<*.jpg>) {
        if (($base, $numero, $ext) = ($nome =~ /^(.)(\d+)\.(.*)/)) {
           rename $nome, sprintf("%s%03d.%s", $base, $numero, $ext);
        }
    }

Melhor, né?

Mas ainda não está bom. Está muito... carregado, sei lá. Uma das grandes diferenças de Perl em relação a maioria das linguagens, e a Python em particular, é que não precisamos ser sempre explícitos. É mais ou menos como usar pronomes ou sujeito oculto. De início você não entende o idioma e fala assim:

- José é casado. José tem cinco filhos. Os filhos de José são todos solteiros.

Depois, você aprende a usar os pronomes e começa a falar de modo mais econômico.

- José é casado. Ele tem cinco filhos. Eles são todos solteiros.

Até que você fica realmente fluente no idioma e fala naturalmente assim:

- José é casado e tem cinco filhos, todos solteiros.

Ininteligível? Só pra quem está só começando a aprender o português. Normalmente conversamos com pessoas que são tão fluentes quanto nós, de modo que podemos, e devemos, ser econômicos e diretos. Evitando redundâncias nós não somos apenas mais diretos. Somos também mais inteligíveis (ou legíveis), porque não inserimos no discurso aquela série de nomes repetidos que acabam poluindo o texto, escondendo o conteúdo real da mensagem.

Bom, tudo isso pra justificar minha próxima versão, na qual eu suprimo a variável $nome, pois em Perl o iterador de um loop é implícito:

    foreach (<*.jpg>) {
        if (($base, $numero, $ext) = /^(.)(\d+)\.(.*)/) {
           rename $_, sprintf("%s%03d.%s", $base, $numero, $ext);
        }
    }

Se você não conhece Perl não vai saber que a expressão regular está sendo aplicada ao iterador implícito do foreach. Mas se você nunca viu Perl, esse não é o seu maior problema, né? Ah, e o $_ é o "pronome" que usamos pra nos referirmos explicitamente ao iterador dentro do loop.

Pensando bem, essas variáveis locais não estão servindo pra muita coisa além de dar nomes às partes capturadas pela expressão regular. Se fôssemos usá-las muitas vezes, vá lá. Mas pra só usarmos uma vez na próxima linha? A expressão regular já é suficientemente clara (depois de adquirir alguma experiência com elas, obviamente). Que tal nos livrarmos dessas variáveis?

    foreach (<*.jpg>) {
        if (/^(.)(\d+)\.(.*)/) {
           rename $_, sprintf("%s%03d.%s", $1, $2, $3);
        }
    }

Hmmm... tá parecendo C. Em Perl é mais direto e legível interpolar as variáveis diretamente na string de formato:

    foreach (<*.jpg>) {
        if (/^(.)(\d+)\.(.*)/) {
           rename $_, sprintf("$1%03d.$3", $2);
        }
    }

Hmmm... o importante é o rename... o if é acessório. Em Perl, podemos inverter o teste e a ação, mais ou menos quando escolhemos a voz ativa ou a voz passiva por razões estilísticas. Então, vamos colocar primeiro o que interessa:

    foreach (<*.jpg>) {
        rename $_, sprintf("$1%03d.$3", $2)
            if /^(.)(\d+)\.(.*)/;
    }

Legal. Economizamos um par de chaves também, viram?

Ah... direto assim fica mais fácil perceber a oportunidadade de fazer otimizações triviais:

    foreach (<*.jpg>) {
        rename $_, sprintf("$1%03d.jpg", $2)
            if /^(.)(\d+)\.jpg$/;
    }

Ou generalizações oportunas:

    foreach (<*.jpg>) {
        rename $_, sprintf("$1%03d.jpg", $2)
            if /^([a-z]+)(\d+)\.jpg$/i;
    }

Ficou bem legível pra mim. E pra vocês?

De qualquer modo, pelo menos isso prova que There Is More Than One Way To Do It.

Adendo: Algum tempo depois de escrever isto eu descobri o comando rename na linha de comando Linux. Com ele a solução é trivial:

    rename 's/(\d+)/sprintf("%03d", $1)/e' *.jpg

Ah... o rename é escrito em Perl. :-)