Elmord's Magic Valley

Software, lingüística, mitologia nórdica e rock'n'roll

LotR #2

2013-05-20 03:05 -0300. Tags: book, rant

Tolkien é um ótimo escritor. As histórias que ele conta é que não compartilham da mesma glória.

Acabei de ler o primeiro capítulo do segundo volume do Senhor dos Anéis. Ok, eu sobrevivi ao primeiro volume (que eu comecei a ler sem nenhuma pressa algum tempo atrás). Sobrevivi ao Tom Bombadil e ao banho anual da Goldberry. Sobrevivi às canções élficas que derrubam até os hobbits de sono (sim, isso é parte da história). Sobrevivi até a Lothlórien e o um mês que a galera ficou jogando fora lá comendo, dormindo e vendo televisão (a.k.a. Mirror of Galadriel) enquanto Sauron et al. tocavam o terror no mundo. Mas este capítulo foi demás.

Veja bem. Boromir, o inútil, depois de tentar roubar o anel do Frodo e atrapalhar a missão, não tendo feito nada de muito útil até esse ponto da história exceto ter ajudado a escavar a neve do monte Caradhras, que no fim das contas eles não atravessaram, perde a briga para um bando de orcs. Aragorn encontra Boromir semi-morto, depois de os orcs terem ido embora. "Eu ferrei com tudo. Agora os orcs levaram os hobbits embora. Vou morrer minha morte miserável aqui", diz Boromir, e morre sua morte miserável ali. Nesse momento a galera sai correndo na pista dos orcs para salvar os hobbits, certo?

Errado. Primeiro, Aragorn, Legolas e Gimli discutem como eles vão fazer o funeral do Boromir. Sim, é mais importante satisfazer o morto do que ir salvar os hobbits (afinal ninguém se importa muito com os hobbits nessa história). Aí eles decidem que cavar dá muito trabalho, então melhor é colocar o Boromir dentro do barquinho dele, junto com seus pertences e os dos orcs que ele conseguiu matar, e levar o barquinho até um ponto do rio e deixar o rio levar embora, não sem primeiro fazer dez mil preparativos. Claro que depois de largarem o barquinho, Aragorn e Legolas ainda cantam umas musiquinhas em homenagem ao morto. A essas alturas o Tolkien já está de saco cheio de escrever poemas (o livro é cheio de poemas), então as musiquinhas têm uma métrica totalmente capenga em comparação com as do primeiro livro. Really, a cena parece as firulas de cavalaria do Dom Quixote, com a diferença de que o Dom Quixote é intencionalmente zoação.

Até que, no final da tarde (essa história toda começou de manhã), Aragorn conclui: "Já sei! Quem sabe a gente vai atrás dos orcs, for a change?" "‘Well, after them!’ said Gimli. ‘Dwarves too can go swiftly, and they do not tire sooner than Orcs. But it will be a long chase: they have a long start.’" A long start? Imagine! Só umas seis horas de vantagem enquanto a gente colocava um morto num barco. O que são meras seis horas comparadas com os trinta dias que a gente passou comendo e dormindo em Lothlórien?

A essas alturas, a motivação primária que eu tenho para continuar lendo esse livro é o prospecto de ler The Last Ringbearer depois. E no fim das contas eu estou reclamando à toa; essa história é uma boa fonte de diversão.

6 comentários

fgetcsv: A cautionary tale

2013-05-18 01:52 -0300. Tags: comp, prog, web, php, rant

Quando eu reescrevi o blog system, eu resolvi manter o índice de posts em um arquivo CSV e usar as funções fgetcsv e fputcsv para manipulá-lo. Minha intuição prontamente me disse "isso é PHP, tu vai te ferrar", mas eu não lhe dei importância. Afinal, provavelmente seria mais eficiente usar essas funções do que ler uma linha inteira e usar a explode para separar os campos. (Sim, eu usei um txt ao invés de um SQLite. Eu sou feliz assim, ok?)

Na verdade o que eu queria era manter o arquivo como tab-separated values, de maneira que ele fosse o mais fácil possível de manipular com as ferramentas convencionais do Unix (cut, awk, etc.). As funções do PHP aceitam um delimitador como argumento, então pensei eu que bastaria mudar o delimitador para "\t" ao invés da vírgula e tudo estaria bem. Evidentemente eu estava errado: um dos argumentos da fputcsv é um "enclosure", um caractere que deve ser usado ao redor de valores que contêm espaços (ou outras situações? who knows?). O valor padrão para a enclosure é a aspa dupla. Acontece que a fputcsv exige uma enclosure: não é possível passar uma string vazia, ou NULL, por exemplo, para evitar que a função imprima uma enclosure em volta das strings que considerar dignas de serem envoltas. Lá se vai meu tab-separated file bonitinho. Mas ok, não é um problema fatal.

A segunda curiosidade é que a fgetcsv aceita um argumento "escape", que diz qual é o caractere de escape (\ por default). Evidentemente, você tem que usar um caractere de escape; a possibilidade de ler um arquivo em um formato em que todos os caracteres exceto o delimitador de campo e o "\n" tenham seus valores literais é inconcebível. Mas ok, podemos setar o escape para um caractere não-imprimível do ASCII (e.g., "\1") e esquecer da existência dele. Acontece que a fputcsv não aceita um caractere de escape, logo você não tem como usar o mesmo caractere não-imprimível nas duas funções. WTF?

Na verdade, agora testando melhor (já que a documentação não nos conta muita coisa), aparentemente a fputcsv nunca produz um caractere de escape: se o delimitador aparece em um dos campos, ele é duplicado na saída (i.e., a"b vira a""b). Evidentemente, não há como eliminar esse comportamento. Mas então o que será que faz o escape character da fgetcsv?

# php -r 'while ($a = fgetcsv(STDIN, 999, ",", "\"", "\\"))
             { var_export($a); echo "\n"; }'
a,z
array (
  0 => 'a',
  1 => 'z',
)
a\tb,z
array (
  0 => 'a\\tb',
  1 => 'z',
)

Ok, o escape não serve para introduzir seqüências do tipo \t. Talvez para remover o significado especial de outros caracteres?

a\,b,z
array (
  0 => 'a\\',
  1 => 'b',
  2 => 'z',
)
a b,z
array (
  0 => 'a b',
  1 => 'z',
)
a\ b,z
array (
  0 => 'a\\ b',
  1 => 'z',
)

Muito bem. Como vimos, o escape character serve para, hã... hmm.

Mas o fatal blow eu tive hoje, olhando a lista de todos os posts do blog e constatando que o post sobre o filme π estava aparecendo com o nome vazio. Eis que:

# php -r '
    $h = fopen("entryidx.entries", "r");
    while ($a = fgetcsv($h, 9999, "\t"))
       if ($a[0]=="20120322-pi") var_export($a);'
array (
  0 => '20120322-pi',
  1 => 'π',
  2 => '2012-03-22 23:53 -0300',
  3 => 'film',
)

# LC_ALL=C php -r '
    $h = fopen("entryidx.entries", "r");
    while ($a = fgetcsv($h, 9999, "\t"))
       if ($a[0]=="20120322-pi") var_export($a);'
array (
  0 => '20120322-pi',
  1 => '',
  2 => '2012-03-22 23:53 -0300',
  3 => 'film',
)

A próxima versão do Blognir deverá usar fgets/explode.

UPDATE: Aparentemente o problema só ocorre quando um caractere não-ASCII aparece no começo de um campo. Whut?

UPDATE 2:

"a b","c d"
array (
  0 => 'a b',
  1 => 'c d',
)
"a"b","c"d"
array (
  0 => 'ab"',
  1 => 'cd"',
)
"a\"b","c d"
array (
  0 => 'a\\"b',
  1 => 'c d',
)

5 comentários

Blergh

2013-03-29 02:03 -0300. Tags: about, life, mind, rant

[Este post foi engolido por um tigre. (31/03/2013)]

10 comentários

inf.ufrgs.br, PHP, e outros infortúnios

2013-03-10 22:26 -0300. Tags: comp, life, php, rant

Com este post tenho o objetivo único e exclusivo de reclamar da vida. É improvável que haja qualquer informação proveitosa contida aqui. You have been warned.

Quando eu entrei para a Inf, os Sioux possuíam o mundo as páginas pessoais dos alunos eram contidas no diretório home de seus usuários (especificamente, em ~/public_html). O camarada podia acessar a portal.inf.ufrgs.br por SSH e editar as páginas remotamente com o Vim. Outra vantagem é que se podia rodar scripts remotamente para gerar páginas ou fazer algum procedimento de manutenção. Bem, mais ou menos. O home da portal é montado sem permissão de execução, o que significa que só dá para rodar os binários que já estão lá (e.g., bash, perl), e para poder rodar os scripts que eu tinha no ~/bin sem ter que digitar bash nome-do-script toda vez eu tinha que fazer uma gambiarra no ~/.bashrc para criar uma função com o mesmo nome do script para cada script presente no ~/bin. Mas ok, dava pra tolerar.

Em algum momento da história do mundo, as páginas pessoais foram levadas para um servidor separado, "por motivos de segurança". Conseqüentemente, não dava mais para alterar as páginas via portal. Montar o diretório das páginas por sshfs? Claro que não, a portal não tem sshfs e não executa binários do home. Solicitar à administração da rede para instalarem o sshfs na portal? Não faremos isso, por motivos de segurança. Well, dá pra montar o sshfs usando a máquina de casa, e se divertir com a latência do 3G. Heh.

Um dia me contaram que dava para acessar a portal, e de lá se logar nas máquinas dos laboratórios da graduação, de onde se pode rodar sshfs e tudo o mais que se queira. Hmm, boa! O camarada tem que se logar três vezes (uma na portal, uma nos labs, e uma no sshfs), mas ainda vale a pena. Se o 3G cai meu Vim fica parado lá me esperando e eu não corro o risco de ter salvo meio arquivo no home.

No semestre passado, atualizaram o OS das máquinas dos labs, e agora não é mais possível fazer o login remoto. Yay! Bom, sobrou o sshfs de casa. Felizmente, agora o meu 3G é um pouquinho melhor, com uma latência normalmente abaixo do 1 segundo (vs. os 4 da Tim), e a coisa é pelo menos utilizável.

O servidor das páginas pessoais também monta os homes sem permissão de execução, o que significa que só dá para usar as linguagens de script que já estão lá, i.e., PHP. PHP é uma linguagem/implementação notavelmente medonha, mas ok, é o que temos. Eu quero um blog, e eu quero agora, e eu quero escrever meus posts no Vim e salvar um txt somewhere para postá-los e ser feliz. Let's do it.

O PHP tem uma (so-called) "feature" chamada "magic quotes". A idéia é: programadores são incompetentes e esquecem de escapar as aspas antes de montar uma query de SQL, então vamos escapar automaticamente qualquer entrada que venha de um formulário. (A noção de montar queries de SQL na mão e escapar strings na mão é uma coisa que strikes me as fundamentally wrong, but I digress.) Acontece que o PHP só faz isso se ele estiver configurado para fazer isso no php.ini. E como (pelo que eu entendo) ele faz o escaping antes de rodar o script, o script não tem a oportunidade de desativar as magic quotes. Conclusão? Eu tenho uma função no blog cujo único propósito é receber uma string e remover os escapes se as magic quotes estiverem ativas, ou retornar a string intacta caso contrário. Lindo, hein?

E as closures. Em JavaScript (é, eu vou usar JavaScript, outra linguagem linda, como exemplo, mas acho que assim será mais compreensível do que o Common Lisp que eu ia usar) podemos fazer coisas do tipo:

function menores_que(n, lista) {
    return lista.filter(function(x) { return x<n; });
}

menores_que(5, [2,3,5,7,11]);   // Retorna [2,3].

Aqui, passamos uma função anônima para o método filter, que seleciona os elementos da lista que satisfazem o predicado passado como argumento. (Esse método só existe no JavaScript da Mozilla, eu acho.) Note que a subfunção usa o n definido fora dela. Em PHP 5.3 foi introduzida uma feature similar. Porém, na versão PHP, você tem que declarar explicitamente quais variáveis externas serão capturadas pela closure. WTF? Isso é tão ridículo que não sei quem mais poderia ter a mesma idéia. Oh, well. (Pelo menos em C++11 há a opção de dizer "captura tudo e não enche".)

Ok, ok. Eu sobrevivi. 753 linhas de PHP. Até que ficou pequeninho para a quantidade de coisas que o blog faz. O problema é que o código ficou todo gambiarrento, e sempre que eu vou adicionar uma feature é um trabalho medonho, e sempre que eu penso em reescrever do zero (coisa que uma hora ou outra eu vou acabar fazendo, já que eu não consigo ficar cinco minutos editando o código atual sem ranger os dentes) eu lembro que terei que fazer isso em PHP (o que produzirá uma quantidade equiparável de ranger de dentes) e abandono a idéia. Mas terá que ser feito, porque PHP é a única coisa que roda na Inf (e muitos outros lugares). Very well. (Actually, not.)

A última coisa que eu quis adicionar no blog foi uma feature de "Comentários recentes" na barra lateral. A idéia original era simplíssima: quando um leitor posta um comentário, além de criar o arquivinho de comentário no diretório apropriado, criamos também um symlink para o arquivinho em um diretório recent_comments da vida. Se o número de links exceder uma quantidade x, removem-se os links mais antigos. Simples, hã? Só que se eu tento usar a função symlink(), ela simplesmente retorna FALSE e nada acontece. What gives?

Acontece que o tal do Suhosin bloqueia a função symlink() do PHP se a opção base_opendir do PHP estiver ativada. Essa opção serve para limitar de que diretórios os scripts podem abrir arquivos. A idéia é que se o usuário puder usar a symlink(), ele poderia em tese despistar a base_opendir criando um link no diretório local para um lugar de onde o script não teria acesso (e.g., /etc/passwd). Porque o PHP não poderia primeiro resolver o symlink e depois testar se o arquivo está dentro dos diretórios permitidos antes de abri-lo, right? Nem a symlink() poderia verificar se o alvo do link está dentro dos diretórios permitidos e criar ou não o link dependendo do caso, right?

Acontece que aparentemete o PHP verifica o caminho resolvido antes de abrir o arquivo. You see, embora o PHP não possa criar symlinks, eu mesmo posso, por sshfs. Quer dizer que qualquer vulnerabilidade que links arbitrários pudessem causar ainda poderiam ser provocadas por mim. Mas eu testei criar um symlink local para o /etc/passwd, e o PHP se recusa a abri-lo, alegando violação da base_opendir. Quer dizer que mesmo que a symlink() estivesse ativa ainda não haveria vulnerabilidade. Mas ela não está, e isso impede gratuitamente minha implementação simples da feature de comentários recentes. Eu poderia entrar em contato com a administração da rede da Inf e pedir para eles habilitarem a feature. Eu não farei isso por duas razões: (1) a admrede vai me dizer que não vai habilitar nada "por motivos de segurança"; (2) um dos princípios por trás deste blog system é rodar em qualquer lugar que tenha um servidor com PHP, sem depender nem de bibliotecas non-standard nem da boa-vontade de sysadmins, e como o symlink() desativado é o padrão do Suhosin, não posso contar com a utilizabilidade dessa função. Tudo bem, o Unix original não tinha symlinks. Vamos brincar de anos setenta.

8 comentários

Qué voy a ser, je ne sais plus

2013-01-11 04:03 -0200. Tags: life, rant, academia

(For your enlightening. Midnight rant follows.)

Tendo terminado hoje a "prova" de Tópicos X (embora ainda não a tenha enviado), termina para mim para todos os fins práticos o semestre de 2012/2. Tópicos Especiais em Computação X, a.k.a. Grandes Desafios da Computação, é uma cadeira de 2 créditos que visa apresentar diversas áreas de pesquisa em Ciência da Computação. A maior parte das aulas consiste de uma palestra dada por um professor sobre sua área de pesquisa, no que ela consiste e quais são os desafios atuais que ela tem para resolver, além de dar uma idéia de como funciona a pós-graduação. Em uma dessas aulas, um professor mencionou (com uma certa ênfase) que a pós-graduação "não é mais do mesmo", que a dinâmica das aulas é diferente, que os alunos têm um papel muito mais ativo do que na graduação.

A primeira reação que eu tive diante disso foi uma boa dose de descrença. Talvez alguns professores dêem boas aulas, e os alunos desempenhe um papel mais ativo e autônomo, mas desconfio seriamente que com muitos professores a coisa não seja bem assim.

By the way, quem falou em autônomo? Isso aí brotou da minha cabecinha não-categorial. Só porque os alunos são mais ativos, não quer dizer que eles tenham algum poder de decisão sobre o que aprendem. Disse ainda o camarada palestrante que como não há cadeiras obrigatórias no mestrado, o aluno pode escolher fazer cadeiras apenas nas áreas que lhe interessam. Porém, no caso das cadeiras eletivas da graduação, os fatos de (1) haver um número mínimo de créditos eletivos obrigatórios, (2) não haver cadeiras suficientes em determinados tópicos, e (3) diversas cadeiras existirem apenas "para inglês ver", não sendo oferecidas em nenhum semestre na prática, significam que no geral é inviável fazer todas as eletivas em assuntos de interesse, e não vejo por que isso seria diferente no mestrado.

O segundo pensamento que me ocorreu é, se o mestrado é toda essa maravilha, por que diabos a graduação não é? É para filtrar dos reles mortais os poucos que têm saco de aturar a graduação inteira e ainda têm ânimo de continuar na academia? É por falta de professores (assumindo que uma melhoria exigisse turmas menores, como são no mestrado, proposição da qual eu duvido)? Porque "sempre foi assim"? Porque no final das contas o objetivo da graduação é produzir um diploma que dá ao camarada o direito de ser empregado em lugares que pagam melhor, e não adquirir e construir conhecimento? Todas as alternativas acima?

É nessas horas que eu me pergunto se vale a pena seguir a vida acadêmica. Meu objetivo inicial, logo que eu entrei nesse curso, era me tornar professor e viver uma vida feliz e contente dando aula e fazendo pesquisa. O curso começou a ficar menos-que-excelente pelo terceiro semestre, e lá pelo sexto ou sétimo eu já estava de saco cheio o suficiente para ter vontade de largar a faculdade. Muita gente que eu conheço encheu o saco bem antes; o que me sustentou nesse tempo foi primariamente o fato de que eu me interesso realmente muito por Ciência da Computação, e leio sobre coisas que me interessam fora do curso, o que mantém meu "amor à arte" firme e forte, apesar do curso. Felizmente a parte mais maçante do curso já se foi, e agora só me restam 6 créditos eletivos por fazer (ou 8, se o professor de Tópicos X não gostar das minhas respostas, já que a prova não é lá muito objetiva*) e o TCC (que é trabalhoso, mas pelo menos eu arranjei um assunto que me interessa para fazer ele sobre (e que venha o monstro da gramática me devorar)).

Como se não bastassem os horrores da graduação para desmotivar o sujeito, eu vejo por aí coisas do tipo:

Basically everyone who gets tenure, including me, finds him/herself continuing to get busier every year. Not only do the demands on our time increase, but we can no longer fall back on the convenient selfish excuse “sorry—can’t possibly do that time consuming task while I’m on the tenure track.” The constantly overloaded TODO list gets old, and it also makes it hard to get actual research done. It’s no coincidence that a lot of tenured professors rely on students to do all of the technical work associated with their research programs. Untenured professors not only have more time to do research but also may be reluctant to risk their careers by putting students on too many critical paths.

Life After Tenure (ênfase minha)

Mais de uma vez eu já li ou ouvi relatos de que os encargos administrativos de um professor tomam tempo suficiente para atapalhar a "pesquisa de verdade". E aí reside um baita de um problema, porque se eu tiver que escolher entre ser professor e desenvolver software, eu prefiro desenvolver software. Na minha concepção inicial do universo, eu não teria que escolher, mas parece que minhas suposições podem estar erradas. Je ne sais plus.

* Appendix A: De como eu saí da Letras

(Não, isso não tem nada a ver com o resto do post.)

Para quem não sabe, antes de entrar para a Ciência da Computação (em 2009) eu cursei um semestre e meio de Letras. Os motivos para isso foram meu interesse por lingüística e a ausência de um curso específico de lingüística em uma faculdade próxima (a faculdade mais próxima com um curso de Lingüística é a Unicamp), o fato de que eu estava de saco cheio de computadores (primariamente porque eu tinha zilhões de problemas com computadores na época, primariamente porque eu tinha uma máquina extremamente capenga, mas também porque viviam me alugando para resolver problemas), e o fato de que eu estava de saco cheio de matemática (graças a um curso de iniciação científica oferecido pelo IMPA para os primeiros dez bilhões de colocados na Olimpíada Brasileira de Matemática das Escolas Públicas, o qual eu abandonei depois de três meses, embora eu estivesse sendo pago para participar). Quaisquer que tenham sido os motivos, eu tomei a decisão de fazer o curso de "Letras – Tradutor Português e Japonês" (porque foi a língua que me pareceu lingüisticamente mais interessante, e não porque eu gosto de animê e mangá, como aparentemente era o caso de todos os outros (cerca de oito) alunos).

Embora algumas cadeiras fossem insuportáveis (e.g., Leitura e Produção Textual), algumas cadeiras eram muito legais (Conceitos Básicos de Lingüística, Japonês I), e embora historicamente eu detestasse de coração o estudo de literatura que se faz no ensino fundamental/médio, até as cadeiras de literatura estavam interessantes.

Até que chegou a primeira prova de Literatura Brasileira A. Eu fiz a bendita prova e tirei um C. Nesse momento eu contemplei a prova, mirei as respostas, e me dei conta de que eu não fazia a menor idéia de por que eu tinha tirado um C, ou o que eu poderia ter respondido diferente para tirar uma nota melhor. Nesse momento eu me dei conta de que se eu não era capaz de determinar o que estava "errado" na prova, meu desempenho no resto do curso provavelmente seria a mesma coisa, já que responder "certo" parecia exigir uma habilidade mágica que não só eu não tinha, como sequer compreendia a natureza da mesma. Nesse momento eu tive um profundo insight: "Que diabos eu estou fazendo aqui?!"

(Fora que o conteúdo de lingüística do curso é um quase nada.)

E foi assim que eu não fiz a segunda prova de Literatura Brasileira A (embora tenha ido às aulas), terminei as outras cadeiras decentemente, no semestre seguinte peguei apenas quatro cadeiras (incluindo uma eletiva, nenhuma relacionada a literatura), larguei todas em setembro para me mudar temporariamente para a casa da minha mãe e estudar para o vestibular, e no ano seguinte entrei para a Ciência da Computação, onde, se muitas das cadeiras são terrivelmente maçantes, pelo menos os critérios de avaliação são largamente objetivos.

Post scriptum

O vídeo linkado diz "que voy a hacer" ao invés de "qué voy a ser". Nunca tinha me ocorrido essa interpretação (acentuação notwithstanding). Mas camarada Google diz:

E no entanto os primeiros sites retornados por uma pesquisa por manu chao me gustas tu lyrics favorecem o "que voy a hacer". E agora?

9 comentários

The shell is completely wrong

2013-01-04 13:56 -0200. Tags: comp, prog, bash, unix, wrong, rant

Já faz algum tempo que eu estou para escrever uma série de posts sobre como os ambientes computacionais modernos estão completamente errados. Este não é um dos posts previstos. Ao contrário da maior parte das idéias que eu pretendia/pretendo expor na série, este post trata de um tópico dentro do âmbito do tranqüilamente implementável, sem envolver mudanças de paradigma. Considere este o zero-ésimo post da série. Considere este também o proto-manifesto de um shell que eu posso vir a escrever ou não durante as férias. Comentários são bem-vindos.

[Foto de John McCarthy com a legenda 'Programming: You're doing it completely wrong']

It's a fucking programming language

Although most users think of the shell as an interactive command interpreter, it is really a programming language in which each statement runs a command. Because it must satisfy both the interactive and programming aspects of command execution, it is a strange language, shaped as much by history as by design.

The Unix Programming Environment

Embora desde os primórdios o shell do Unix tenha sido reconhecido como uma linguagem de programação, as pessoas tendem a não encará-lo como uma "linguagem de verdade"; o shell serve para rodar "scripts", ao invés de "programas". Não só os usuários de shells têm essa visão, mas também os próprios desenvolvedores de shells: embora bash, zsh e companhia tenham acumulado diversas features ao longo dos anos, faltam nos shells features básicas que se espera encontrar em qualquer linguagem de programação razoável. Por exemplo, extensões do bash em relação ao Bourne shell (sh) original incluem:

  • Variáveis array (que só podem ser arrays de string);
  • Arrays associativos, i.e., arrays indexados por strings;
  • Um comando mapfile para ler o conteúdo de um arquivo para dentro de um array (mas não mapeia coisa nenhuma: alterações no array não se refletem no arquivo);
  • Sintaxe para permitir fall-through das cláusulas de um case (i.e., permitir o comportamento de um case sem break em C);

E no entanto:

  • Arrays só podem conter strings; não é possível criar estruturas de dados aninhadas;
  • Arrays não são elementos de primeira classe: não é possível passar um array como argumento para uma função, ou retornar um array; by the way...
  • Não existe um mecanismo decente para retornar valores de uma função; o jeito é usar uma variável global, ou imprimir o valor a retornar para a stdout e ler usando a sintaxe $(função args...), que cria um subprocesso só para executar a função e captura a saída.

Além disso, a sintaxe e a semântica de certas features são bastante facão. Isso em parte se deve ao fato de que o bash tentou acrescentar features novas sem quebrar compatibilidade com a sintaxe do sh, mas também por falta de princípios de design decentes. Um mecanismo para retornar valores de funções já seria um bom começo, e ajudaria a limpar outros aspectos do shell, já que seria possível forncecer mais funcionalidades através de funções, ao invés de usar sintaxe abstrusa (por exemplo, uma função lowercase string, ao invés da nova novidade, a substituição ${varname,,pattern}, onde pattern é opcional e indica quais caracteres devem ser transformados (sim, o padrão casa com cada caractere; se o padrão tiver mais de um caractere ele não casa com nada); ou uma função substr string start length, ao invés da substituição ${varname:start:length}, cujo uso de : conflita com a substituição ${varname:-string}, o que impede que start comece com -).

Se tanto desenvolvedores quanto usuários do shell tendem a concordar que o shell não foi feito para ser uma "linguagem de verdade", poder-se-ia argumentar que eu que sou o perdido e que o propósito do shell é de fato ser uma linguagem "de brincadeira". Mas que sentido faz isso? Para que termos uma linguagem de programação pela metade, se podemos ter uma linguagem mais decente adicionando umas poucas funcionalidades básicas?

É possível argumentar alguns motivos técnicos para a resistência a estruturas de dados. Um deles é que o mecanismo de invocação de programas é totalmente baseado em strings: não é possível chamar um programa externo passando um array como argumento, por exemplo, e isso não é culpa do shell, e sim da maneira como programas são chamados no Unix. (O que por sinal é lamentável; seria ótimo poder passar estruturas complexas entre os programas. Voltaremos a esse assunto mais adiante.) Isso não é um problema insuperável; só porque comandos externos não podem receber dados estruturados não quer dizer que não possamos passar dados estruturados internamente entre as funções do shell. O caso em que o usuário tenta chamar um comando externo com um array exige um tratamento especial (transformar o array em string, ou em múltiplos argumentos, ou emitir um erro), mas isso não é motivo para eliminar estruturas de dados complexas da linguagem.

(Outro possível motivo é medinho de pôr um garbage collector dentro do shell. Há quem ache ainda hoje que garbage collection é coisa do demônio. Mas dada a popularidade de linguagens garbage-collected hoje em dia (Java, Perl, Python, Ruby, C#, etc.) e dada a existência de bibliotecas de garbage collection para C, esse também não é um motivo forte.)

Shells alternativos

Houve e há diversos shells que tentam escapar da tradição do Bourne shell. Um deles (que serviu de inspiração para outros) é o rc, o shell do Plan 9, que possui versões para Unix. O rc tem uma sintaxe um pouco mais limpa (ou não) do que o sh, mas não apresenta grandes avanços em termos de features. Uma diferença não diretamente relacionada com o shell é que no Plan 9 o exit status de um programa é uma string, e não um inteiro entre 0 e 255 como no Unix, o que possibilita usar o exit status como meio de retornar valores de funções. Porém, o shell não apresenta nenhum recurso sintático para executar uma função e substituir a chamada pelo exit status.

Inspirado no rc surgiu o es, um shell com funções de primeira classe, variáveis com escopo léxico, closures e exceptions. Uma característica interessante do es é que boa parte dos internals do shell são expostos ao usuário. Por exemplo, o operador pipe (|) é apenas açúcar sintático para uma chamada a uma função %pipe, que pode ser substituída pelo usuário de modo a modificar o comportamento do pipeline (um exemplo dado no artigo linkado é calcular o tempo de execução de cada programa da pipeline). O es possui listas/arrays, mas não permite listas aninhadas. (O motivo oferecido é fazer com que passagem de arrays para comandos externos e para funções tenham a mesma semântica; porém, dado que o shell tem que lidar com a possibilidade de o usuário tentar passar uma função para um programa externo, esse argumento não é tão convincente. Não sei o que o shell faz nessa situação; pelo artigo eu suponho que ele passe o corpo da função como uma string, e de fato parece que o shell armazena as funções internamente como strings.) O es também não possui outros tipos de estruturas de dados complexas, embora seja possível implementá-las (ainda que de maneira convoluta) através de closures. O es também permite retornar valores complexos a partir de funções, com uma sintaxe para chamar a função e recuperar o valor. Um ponto levantado no artigo é que esse mecanismo de retorno e o mecanismo de exceptions não interage bem com comandos externos: um script externo não tem como retornar valores ou propagar uma exception para o script que o chamou. Voltaremos a esse tópico mais adiante.

Um shell posterior inspirado no rc e no es é o shell do Inferno. Esse shell não tem grandes novidades comparado com o es (embora o fato de ele rodar no Inferno lhe confira alguns poderes mágicos, como por exemplo realizar comunicação pela rede atavés do sistema de arquivos, e embora ele tenha alguns módulos interessantes, tais como uma interface gráfica). Entretanto, um ponto que chama a atenção é a sintaxe: comandos como if, for e while não são tratados de maneira especial sintaticamente. Ao invés disso, eles são comandos normais que recebem blocos de código como argumentos. A idéia é similar ao método each do Ruby: ao invés de se usar uma estrutura de controle especial para iterar sobre os itens de uma lista, chama-se um método que recebe um bloco de código e o chama sobre cada elemento:

# Ruby.
a = [1, 2, 3, 4, 5]
a.each {|i|
    puts i
}

Com isso, o usuário pode definir novas estruturas de controle que se comportam de maneira similar às estruturas padrão da linguagem.

Outros shells alternativos incluem o fish, um shell com diversos recursos interativos, mas (na minha humilde opinião) sem grandes avanços em termos de programação, e o xmlsh, um shell escrito em Java cujo objetivo é permitir a manipulação de estruturas de dados baseadas em XML, e que conspira em última instância substituir a comunicação baseada em texto dos programas atuais do Unix por um modelo de comunicação estruturada baseada em XML. (Voltaremos a esse assunto mais adiante (ainda).)

O que eu faria diferente

Um shell tem dois casos de uso distintos: como um interpretador de comandos interativo, e como um interpretador de programas. O fato de que o shell deve ser conveniente de usar interativamente se reflete na sintaxe de sua linguagem: strings literais geralmente não precisam ser colocadas entre aspas; valores literais são mais freqüentes do que variáveis, e portanto nomes por si sós representam strings (o caso mais freqüente), e variáveis são indicadas com um símbolo especial ($); execução de comandos externos usa uma sintaxe simples; há uma sintaxe conveniente para gerar listas de arquivos (*.txt, ao invés de uma chamada de função), combinar entrada e saída de comandos (|), redirecionar entrada e saída para arquivos, etc. Essa moldagem da sintaxe ao redor do uso interativo limita as possibilidades sintáticas das features voltadas à programabilidade (e.g., sintaxe para chamadas de função, estruturas de dados, operações aritméticas).

Conveniências lingüísticas à parte, a minha opinião é de que o núcleo do shell deva ser a linguagem de programação em si, e não as facilidades de uso interativo. Coisas como edição de linha de comando, histórico e completamento automático de nomes de arquivos e comandos não deveriam ser internas ao shell; ao invés disso, a linguagem do shell deveria ser suficientemente capaz para que essas coisas todas pudessem ser implementadas como scripts.

Em termos de features da linguagem, é possível tornar o shell mais hábil sem quebrar (muito) a compatibilidade com sh e bash. O primeiro passo é criar um mecanismo para permitir retornar valores de funções sem criar um subshell. Para isso, é necessário definir um comando para retornar valores, e uma sintaxe para chamar uma função e recuperar o valor. Eu usaria reply valor (já que return N já está em uso, para sair da função com um exit status N) e $[função args...] para isso. (Atualmente $[...] causa avaliação aritmética em bash. A sintaxe $[...] é considerada obsoleta, em favor da sintaxe do padrão POSIX, $((...)).)

O segundo passo é tornar arrays elementos de primeira classe, permitindo passá-los para funções e retorná-los, e permitindo armazená-los onde quer que se possa armazenar um valor (por exemplo, dentro de outros arrays). Acaba-se com a noção de "array variable" do bash: uma variável contém um array, não é um array. $array não retorna mais o primeiro valor do array, e sim o array inteiro. É possível passar um array literal para uma função:

função arg1 (1 2 3) arg3

Convém criar uma sintaxe para arrays associativos, possivelmente %(chave valor chave valor ...). Também convém usar um operador diferente para indexar arrays associativos e não-associativos, já que em bash, índices de arrays não-associativos sofrem avaliação aritmética:

i=0
echo ${array_comum[i]}   # Elemento da i-ésima posição
echo ${array_assoc{i}}   # Elemento cuja chave é a string "i"

(Outra alternativa seria nunca fazer avaliação aritmética sem que o programador mande explicitamente, mas isso não só quebra a compatibilidade como é inconveniente na prática.)

Word splitting implícita sobre a os valores de variáveis fora de aspas teria a morte horrível que merece. Pathname expansion sobre valores de variáveis teria as três mortes horríveis que merece.

Em bash, os elementos de uma pipeline são executados em subshells (com exceção do primeiro (ou do último se a opção lastpipe estiver ativa, outra novidade do bash 4)), o que significa que uma função usada em um pipeline não tem como alterar os valores das variáveis globais, pois as alterações que fizer serão isoladas em seu subprocesso, o que freqüemente é inconveniente. Por exemplo, código desse tipo não funciona em bash (embora seja possível contornar o problema em muitos casos):

files=()
find . -name '*.txt' | while read file; do
    files+=("$file")
done

echo "${files[@]}"   # Array continua vazio

Uma feature desejável seria que todos os itens de uma pipeline que possam ser executados internamente ao shell o sejam. Uma solução é, ao invés de criar novos processos, criar threads, e simular os redirecionamentos de entrada e saída do pipe internamente (e.g., se um comando função1 | função2 é executado, o shell executa cada função em uma thread, e faz mágica internamente para dar à função1 a ilusão de que o que ela imprime para a stdout vai para a stdout (file descriptor 1), quando no entanto os dados vão parar em um outro file descriptor usado para fazer a comunicação entre as funções (ou mesmo em uma string, evitando chamadas de sistema para leitura e escrita em file descriptors)). O mesmo mecanismo pode ser usado para executar $(função args...) sem gerar um subprocesso.

Oops! Mas o ponto de inventar a sintaxe $[...] e reply ... era justamente evitar criar um subprocesso! Uma diferença, entretanto, é que a sintaxe $(...) só serve para retornar strings (pela sua definição), enquanto $[...] serve também para retornar estruturas de dados complexas.

Dado o paralelo entre $(...) e as pipelines, ocorre a idéia interessante de termos um equivalente do pipeline para $[...], i.e., um pipeline capaz de passar dados estruturados. Com isso, poderíamos escrever "generators", a la Python e companhia. Algo do tipo:

generate_factorials() {
    local i=1 val=1
    while :; do
        yield $val # Entrega um valor para a próxima função; aqui usamos um valor simples,
                   # mas poderíamos passar qualquer estrutura de dados.
        ((i++, val*=i))
    done
}

consume() {
    local val
    while val=$[take]; do
        echo "Recebi $val"
    done
}

generate_factorials ^ consume   # Sintaxe hipotética para o "pipeline de objetos"

Hmmrgh, agora temos dois conceitos parecidíssimos mas diferentes no shell. Quem sabe se ao invés de usar um tipo especial de pipeline, usássemos o mesmo mecanismo de pipeline para as duas coisas? Conceitualmente o comando yield então escreveria o valor para a fake stdout, e take o leria da fake stdin, enquanto internamente o shell transferiria o objeto de uma função para a outra. Da mesma forma, por consistência, o comando reply escreveria o valor de retorno para a fake stdout, e $[...] o leria da fake stdin. (Não temos como unificar $(...) e $[...] porque a semântica das duas expressões é diferente: uma retorna uma string, não importa o que o subcomando faça, enquanto a outra pode retornar qualquer tipo de valor. $[...] é análogo a um take, enquanto $(...) é análogo a um read.)

A questão é: se yield escreve na fake stdout, o que é que ele escreve? A princípio, não precisaria escrever nada "real": contanto que haja um take na outra ponta, poderíamos transferir o valor do yield para o take por mágica internamente, e apenas conceitualmente escrever no file descriptor correspondente à pipeline. Porém, se ela escrevesse alguma coisa, e essa coisa representasse o valor a ser transferido, as duas pontas do pipeline não precisariam mais estar no mesmo processo! Quer dizer, da mesma forma que o nosso pipeline comum sabe distinguir se as duas pontas estão em processos diferentes ou não, e usa file descriptors de verdade ou fake stdin/stdout dependendo do caso, o nosso pipeline de objetos também poderia fazer o mesmo. Se as duas pontas estão no mesmo processo, transferimos o objeto puro e simples. Mas se as duas pontas estão em processos distintos, podemos serializar o objeto de um lado e des-serializar do outro, de modo que é possível passar uma stream de objetos para um script externo de maneira transparente. Da mesma maneira, $[...] poderia funcionar com scripts externos, lendo o valor de retorno serializado da stdout do script e des-serializando-o novamente. Assim, resolvemos parte do problema mencionado no artigo do shell es: conseguimos fazer nossos valores complexos conviverem com o mundo texto-puro do Unix.

Em parte: falta dar um jeito de podermos passar argumentos complexos para os programas externos. A princípio poderíamos passar uma representação serializada dos argumentos. Porém, precisamos arranjar um meio de permitir ao programa chamado distinguir o array (1 2 3) da string composta pelos sete caracteres (1 2 3). Uma idéia seria sempre passar as strings com aspas em volta. Mas não podemos fazer isso porque não sabemos se o programa chamado é um script ou não, e portanto não sabemos se ele está preparado para entender as aspas (e.g., o comando ls não entende a opção "-la", com aspas em volta). Somos obrigados a passar as strings literalmente. Uma solução é passar uma variável de ambiente para o programa dizendo o tipo de cada argumento; se o subprocesso for um script, ele saberá interpretar a variável e distinguir strings de representações serializadas, e se for um outro programa qualquer, ele não dará bola para a variável*, interpretará as strings como strings, e as representações serializadas como strings também; não faz sentido passar outros objetos para não-scripts, de qualquer forma.

A semântica da passagem por serialização pode ser um pouco diferente da passagem direta dentro de um mesmo processo. Se as estruturas de dados são mutáveis, as mudanças em um processo diferente não se refletirão (a princípio) no processo original (pois o subprocesso tem uma cópia, não uma referência ao objeto original). Porém, isso não há de ser problema.

Um problema com passar valores pela stdout é que o valor de uma chamada de função não é ignorado por padrão. Isto é, enquanto na maior parte das linguagens de programação o valor de retorno de uma função é descartado por padrão se não for usado, no nosso caso a chamada imprime seu valor de retorno para a stdout, e para descartá-lo temos que tomar alguma ação explícita (um >/dev/null, por exemplo). Não sei até que ponto isso é um problema.

It's text all way down (and up)

A idéia de passar objetos estruturados entre os programas não é novidade. A grande glória das pipelines é permitir que combinemos diversos programas simples de modo a realizar uma tarefa mais complexa. No Unix, grande parte dos programas lêem e emitem texto em um formato simples (campos separados por algum caractere como TAB ou :, um registro por linha). A universalidade desse formato e a grande quantidade de ferramentas para manipulá-lo permite que combinemos programas que não foram necessariamente feitos para se comunicar um com o outro. Por exemplo, se quiséssemos contar quantos usuários locais usam cada shell, poderíamos usar um comando do tipo:

# cat /etc/passwd | cut -d: -f7 | sort | uniq -c | sort -rn
     17 /bin/sh
      5 /bin/false
      2 /bin/bash
      1 /usr/sbin/nologin
      1 /bin/sync

No entanto, texto puro por vezes deixa um pouco a desejar. E se quisermos trabalhar com dados hierárquicos, ao invés de tabelas? E se quisermos usar o separador dentro de um dos campos? Esse último problema é freqüente em programas que manipulam nomes de arquivo. No Unix, um nome de arquivo pode conter qualquer caractere, com exceção do ASCII NUL (a.k.a. \0) e da /, que é usada para separar nomes de diretórios. Isso significa que nomes de arquivo podem conter espaços, asteriscos, tabs e quebras de linha, entre outros, o que atrapalha a vida de muitos scripts. Por exemplo, se você usar um comando do tipo:

find / -size +4G >lista-de-arquivos-maiores-que-4-giga.txt

você corre o risco de uma alma perversa ter criado um arquivo com um \n nome, o que vai fazer com que esse nome pareça duas entradas distintas na lista de arquivos. A maior parte das ferramentas não é preparada para lidar com terminadores de linha diferentes de \n (embora diversas ferramentas do GNU tenham opções para lidar com \0). E se ao invés de passar texto puro entre os programas pudéssemos passar dados estruturados, em um formato compreendido por todas as ferramentas?

Como disse, essa idéia não é novidade. Talvez o projeto mais famoso a explorar essa possibilidade no Unix seja o TermKit, um projeto que objetiva liberar o shell do mundo do texto puro e dos emuladores de terminal. As ferramentas do TermKit se comunicam usando JSON e headers baseados em HTTP. A idéia é que, ao invés de transmitir bytes brutos, os dados que entram e saem dos processos carreguem consigo uma indicação de seu tipo (no header), de modo que as ferramentas saibam como lidar com o conteúdo que recebem. O TermKit foca na parte de interação com o usuário, e não provê um shell completo (programável).

Há uma thread longa sobre o assunto nos fóruns do xkcd.

Outro projeto nesse sentido é o xmlsh mencionado na última seção, que utiliza XML para a comunicação entre os processos.

No mundo não-Unix, um programa interessante é o PowerShell do Windows. No caso do PowerShell, os comandos realmente passam objetos entre si (e não representações serializadas). Isso é feito com uma pequena "trapaça": os comandos capazes de lidar com objetos não executam em processos externos, mas sim são instâncias de "cmdlets", objetos que fornecem uma interface para o shell, e que são instanciados dentro do próprio shell. Um exemplo retirado do artigo é o comando:

get-childitem | sort-object extension | select extension | where { $_.extension.length -eq 4 }

get-childitem é uma versão generalizada do ls, que funciona sobre diretórios e outras estruturas hierárquicas. sort-object recebe uma stream de objetos como entrada pelo pipeline, e os ordena pelo campo passado como argumento. select é similar ao cut do Unix, mas trabalha sobre objetos. where é um comando de filtro que recebe como argumento um predicado, i.e., uma função que é executada sobre cada elemento da entrada e decide se o elemento permanece na tabela final ou não.

O que eu acharia realmente ideal seria passar objetos entre processos, não apenas como um mecanismo interno do shell. Isso era possível na Lisp Machine, primariamente porque todos processos eram executados no mesmo espaço de endereçamento (i.e., os processos na verdade são threads). (Além disso, o fato de todos os programas do sistema terem uma linguagem comum (Lisp) garante que os objetos serão entendidos por todos os programas.) Permitir isso em um ambiente com processos isolados é uma tarefa mais complicada.

Mas estamos divergindo do ponto original e começando a sair do âmbito do tranqüilamente implementável. Por enquanto eu só quero criar um shell decentemente programável. Revolucionar a maneira como os programas se comunicam é tópico para outros posts.

O que mais eu faria diferente

Até agora adicionamos features de maneira semi-compatível com o sh. Mas podemos aproveitar a oportunidade para fazer uma limpeza geral no shell. Poucos são os fãs da sintaxe do sh [citation needed], então creio que poucos se objetarão terminantemente a um shell com sintaxe diferente.

Uma coisa que eu gostaria é de seguir o exemplo do shell do Inferno e usar comandos comuns e blocos de código ao invés de estruturas de controle com sintaxe especial. Assim, o usuário poderia definir estruturas de controle novas sem necessidade de alterar a sintaxe do shell. Exemplo hipotético:

function foreach (list body) {
    local i=0
    local length=$[length $list]

    while { $i < $length } {
        $body ${list[i]}
        i += 1
    }
}

# Exemplo de uso. (Sintaxe de bloco roubada do Ruby.)
foreach (*.txt) {|file|
    echo "Eis o arquivo $file:"
    cat $file
}

Tenho outras idéias para um shell, mas acho que esse post já está ficando longo demais. Talvez eu escreva sobre isso em outra ocasião (ou se alguém demonstrar interesse). Por ora ficamos por aqui.

Conclusão

Os pontos principais deste post são:

  • O shell é uma linguagem de programação; não existe um bom motivo para ele ser uma linguagem de programação pela metade;
  • É possível adicionar capacidades que favoreçam a programabilidade do shell sem prejudicar seu uso interativo (ainda que isso force o shell a usar uma sintaxe estranha para algumas coisas, de modo a acomodar mais naturalmente o uso interativo);
  • Todos são a favor da moção.

_____

* Mas o subprocesso pode passar a variável intacta para outros processos, o que eventualmente pode fazer a variável cair em outro shell, que ficará confuso achando que a variável se refere aos argumentos que recebeu. Uma solução é incluir no valor da variável o PID do processo a quem a variável se destina. Mas ainda há o risco de o processo fazer um exec() e executar um shell com o mesmo PID atual, ou de um PID se repetir. Hmmrrgh...

1 comentário

Línguas mudam

2012-07-23 19:17 -0300. Tags: lang, rant

Ontem ocorreu a festinha de aniversário da minha irmã, e conseqüentemente uma reunião familiar. Como em mais de 50% das reuniões desse ramo da família, em algum ponto surgiu uma discussão sobre como as pessoas falam errado hoje em dia. O português "correto", dependendo da fase da lua, inclui:

21 anos de convivência me levaram a crer que discutir é inútil, embora eu ainda me arrisque a fazer um remark ou outro no meio das discussões. Vocês falam latim, pessoas? Esse tal de português correto a que vocês se referem nada mais é do que latim com dez ou quinze séculos de erros acumulados, penso eu comigo mesmo. E esse tal de latim nada mais é do que proto-indo-europeu com dez ou quinze séculos de erros acumulados. Mas eu fico na minha.

Desde pequeno eu observo que os tipos de erros cometidos por cada ramo da família são diferentes. Por exemplo, do lado paterno o para mim fazer (ao invés do para eu fazer do português "standard") é bastante comum. Do lado materno, isso praticamente não ocorre, mas em compensação há uma tendência geral a usar os pronomes retos ao invés dos oblíquos (ele viu nós). O ponto digno de observação é que os erros de cada grupo de falantes são consistentes. E uma vez que línguas nada mais são do que pura convenção, erros consistentes não são exatamente erros.

Convenção aqui é a palavra-chave: uma frase está "correta" se ela segue as convenções estabelecidas entre os falantes. Diferentes situações implicam diferentes convenções, entretanto: as convenções utilizadas para textos formais escritos são diferentes das convenções usadas pela língua formal falada, que são diferentes das convenções usadas na linguagem informal. A língua não é uma unidade uniforme, mas sim é composta por diferentes variantes, ou registros, que são usados conforme a situação. Exigir a presença das marcas de plural na língua formal escrita é perfeitamente válido; exigir que as pessoas pronunciem todas as marcas de plural em conversas informais é ridículo. (No caso da língua escrita em particular, especialmente em textos formais, o conservadorismo lingüístico é útil para garantir que textos escritos hoje possam ser compreendidos daqui a cinqüenta ou cem anos.)

Mas mesmo as variantes formais da língua sofrem variações com o tempo. Ninguém mais usa o vós, por exemplo. O pretérito mais-que-perfeito (eu fizera) está em vias de cair do cavalo também, embora algumas pessoas ainda gostem de usá-lo em textos literários. Hoje em dia há uma contenda quanto ao uso de pronome oblíquo em começo de frase (me parece correto, ao invés de parece-me correto). O pronome oblíquo inicial soa errado aos ouvidos de Portugal, e os gramatiqueiros prescritivistas brasileiros tendem a declarar a construção como inválida. No Brasil, entretanto, o pronome oblíquo inicial é comum na língua falada, mesmo nas variantes formais, e há uma tendência geral a se usar próclise sempre (i.e., colocar o pronome oblíquo sempre antes do verbo), e aparentemente essa é uma tendência já de séculos [citation needed]. Tentar forçar a língua escrita a um padrão estranho a todos os falantes da dita língua (o português do Brasil) é um tanto quanto tosco. É que nem tentar forçar os falantes de português do Brasil a escreverem estou a fazer, ao invés de estou fazendo. (Nesse caso, o português do Brasil é mais conservador, na verdade.)

Conseqüentemente, podemos concluir que daqui a uns duzentos ou trezentos anos provavelmente alguns usos do plural terão caído em desuso. "Mas como vamos viver sem o plural?", ouvem-se os gritos de horror de alguns. Bom, tecnicamente nós já estamos vivendo sem algumas marcas de plural na língua falada há muito tempo, e ninguém sofreu danos deletérios. Além disso, historicamente temos diversas instâncias de perdas de features gramaticais. Já ouviu falar do acusativo? Assim como em português nós marcamos o plural com uma flexão especial dos substantivos, adjetivos e pronomes, em latim havia uma flexão especial que se usava para marcar que uma palavra era o objeto direto de um verbo. Assim, puella poetam videt significava "a menina vê o poeta", mas puellam poeta videt significava "o poeta vê a menina". O -m final que costumava marcar o acusativo (o objeto direto) com o tempo passou de um m perfeitamente audível a uma simples nasalisação da vogal que o precede (de maneira similar, os m finais em português normalmente não são pronunciados como um m "de verdade"), e com o tempo simplesmente se perdeu. Como resultado, marca de acusativo em substantivos e adjetivos em português simplesmente não existe (embora resista em alguns pronomes, e.g., eu vs. me). "Como vamos viver sem acusativo?", ouviam-se os gritos de horror da antigüidade. Exatamente assim, respondemos nós. A marca do objeto direto por flexão deu lugar à indicação pela ordem da frase (i.e., a menina vê o poetao poeta vê a menina). E tem funcionado muito bem nos últimos dez séculos. Da mesma forma, a flexão de plural está dando lugar ao plural marcado apenas nos artigos e verbos, e em substantivos e adjetivos em contextos limitados.

"Mas então as línguas estão perdendo itens gramaticais o tempo todo? Daqui a pouco não vai sobrar nada da gramática!", ouvem-se os gritos de horror. Bom, não é bem assim. Ao mesmo tempo em que as línguas perdem features gramaticais, elas também adquirem features novas com o tempo. Por exemplo, na transição do latim para o português, a língua adquiriu um futuro do subjuntivo (quando eu fizer), que não existia em latim e é relativamente raro entre as línguas do mundo. O português também ganhou um igualmente raro infinitivo conjugado (para nós fazermos), que compartilha das mesmas formas do futuro do subjuntivo nos verbos regulares. O futuro do presente original do latim foi perdido e substituído por uma forma composta usando o verbo haver (amar hei (= hei de amar)), e posteriormente a forma composta foi readquirida como um tempo verbal (amarei). (Aliás, é por isso que a mesóclise é possível em português: o pronome pode ser intercalado no verbo porque o verbo é originalmente composto por duas palavras separadas.) O futuro está em vias de se perder de novo (amareivou amar), por sinal. É assim que funciona a porcaria.

Uma coisa que me deixa admirado nessa história de variantes lingüísticas é que muitas pessoas simplesmente não têm consciência de que usam mais de uma variante da língua. Ontem, durante a discussão, um dos presentes declarou que falava o português "correto". Só que isso não é verdade quanto a praticamente qualquer falante, se "correto" refere-se ao padrão literário da língua prescrito pelos gramatiqueiros. Ainda que algumas pessoas mais cuidadosas evitem omitir os plurais, a omissão dos -r finais de conjugações verbais (com exceção de pôr, for) é praticamente universal na língua falada, quando não se está dando ênfase ao verbo. O mesmo vale para a substituição de pronomes oblíquos de terceira pessoa por pronomes retos (eu encontrei ele na rua, ao invés de eu o encontrei na rua). Alguns "erros" são tão comuns que raramente são notados, mesmo quando a pessoa está cuidando para encontrar erros. Orações subordinadas são uma fonte particularmente freqüente de desvios:

Pronomes também têm adquirido uns usos curiosos ultimamente. Uma construção que eu vejo ser usada com bastante freqüência na língua falada são coisas do tipo o Instituto de Informática, ele fica no Campus do Vale (com um ele a mais referindo-se ao sujeito que acabou de ser mencionado). Algumas pessoas usam os pronomes de terceira pessoa como pronomes resumptivos (o cara que eu falei com ele). Alguns desses usos me dão coceira no rim, já que por alguma razão eu mesmo não os uso*. É normal, entretanto, que inovações lingüísticas se espalhem gradualmente, e portanto não tenham aceitação absoluta de imediato. Pode também acontecer de elas simplesmente se espalharem entre alguns grupos e não entre outros, produzindo dialetos divergentes.

No más.

[* Eu costumo cometer uma atrocidade diferente e dizer o cara que eu conversei com, roubando uma feature do inglês. Não acho provável que essa construção venha a adquirir uso amplo em português, entretanto.]

3 comentários

Sucker Punch

2012-06-11 21:37 -0300. Tags: film, rant

Esse fim-de-semana assisti um filmezinho chamado Sucker Punch. O filme pretende ter uma historinha "bonita" com uma moral nobre. Resumo da ópera: mãe morre e as filhas ficam com o padrasto. Padrasto fica fulo da vida porque a mulher não deixou nada para ele no testamento (pode-se vagamente assumir que a morte da mulher é responsabilidade do padrasto), e resolve abusar das filhas. A filha mais velha consegue se safar do cara, o cara tranca a guria no quarto e vai atrás da pequena. A guria escapa pela janela, conjura uma arma do éter luminífero, vai atrás do cara, dá um tiro para matá-lo, erra e mata a pequena. O cara chama a polícia, decidem internar a guria em um sanatório, e o cara suborna o rei do sanatório para fazerem uma lobotomia na guria para ela não contar nada à polícia. O rei do sanatório explica que o homem da lobotomia só aparece dali a uns dias, e enquanto isso a guria fica em tratamento no lugar lá. Essa introdução da história é apresentada em uns cinco minutos de filme, e é basicamente a única parte com alguma complexidade do enredo. A guria é levada a uma sala enorme onde uma mulher realiza algum tratamento psicoterápico maluco. Passam mais algumas cenas do sanatório. Lá pelas tantas, o filme começa a se passar em um mundo paralelo em que a guria está sendo levada para um bordel, onde ela é mantida com umas outras gurias. A sala enorme agora é alguma coisa bar-like com um palco. Muitas cenas depois, a guria vai fazer seu primeiro ensaio como dançarina, e quando começa ela a dançar, somos transportados para um outro mundo paralelo, onde ela entra em um templo pseudo-oriental onde há um mestre, que pergunta o que ela quer da vida. Ela diz que quer cair fora daquele lugar. O velho lhe entrega uma espada e umas armas, e diz que ela precisa de cinco itens para escapar. No mundo do nível anterior, a guria convence as outras a seguir um plano para tentar escapar do lugar. O plano consiste em a guria ficar dançando e distraindo a galera enquanto as outras fazem todo o trabalho sujo. Sempre que ela dança, o filme passa para o mundo de nível 3, onde as ações do plano de fuga correspondem a batalhas e explosões e tiros e dragões e what-not. No fim do filme, o quinto item necessário é a própria guria, que se sacrifica para a única outra guria que sobrou viva a essas alturas conseguir fugir. A morte da guria no nível 2 corresponde à lobotomia no nível 1. Fica claro aí que todos os eventos do nível 2 tiveram alguma correspondência no nível 1, e que uma das gurias de fato fugiu do sanatório. O cara que fez a lobotomia pergunta para a mulher do tratamento psicoterápico por que ela recomendou o procedimento, e ela responde que não recomendou. Nisso descobrem essa e outras tretas do rei do sanatório, ele vai preso, todos comemora. Em paralelo ao começo e ao fim do filme, há uma narrativinha dizendo que "ah, os anjos nos ajudam, mas não lutam por nós, somos nós quem decidimos o nosso destino, blá, blá, blá".

Em muitos aspectos, o filme é O Labirinto do Fauno com menininhas bonitas e explosões.

Essa foi a primeira impressão que o filme me passou. Mas depois de uma análise mais cuidadosa, percebi que há inúmeros paralelos entre as duas histórias. Começando pelo começo. Em ambas as histórias, a protagonista tem um padrasto cruel (no Labirinto do Fauno (LF), o padrasto é um capitão fascista que tortura e/ou mata metade do elenco e maltrata a outra metade). Em ambas as histórias, a protagonista "escapa" do mundo real para um mundo imaginário ou paralelo (no LF, um mundo mágico a la folclore europeu). Em ambas, o objetivo da protagonista é fugir (no LF, para um mundo subterrâneo, onde ela é uma suposta princesa que se perdeu no mundo dos homens). Em ambas, a saga começa com um mestre que dá instruções (no LF, o fauno).

No Sucker Punch (SP), a menina precisa obter cinco itens para fugir. O primeiro item é um mapa. No LF, a primeira coisa que o fauno dá à Ofelia (a protagonista) é um livro que diz o que ela tem que fazer e para onde tem que ir (não é exatamente um mapa, mas é close enough). O segundo item é fogo, que no mundo-nível-2 é um isqueiro. No mundo-nível-3, as gurias têm que entrar em um castelo, cortar a garganta de um dragão e pegar dois cristais que lá se encontram. No LF, a Ofelia tem que entrar em uma árvore moribunda, meter a mão na língua de um sapo gigante e pegar uma chave. No SP, as gurias têm que pegar os cristais sem acordar a mãe do dragão, mas a protagonista resolve testar os cristais dentro do castelo, faz um fogo medonho, acorda a dragoesa e a galera tem que sair correndo para escapar. Pois bem, a próxima missão da Ofelia é entrar em uma sala onde há uma mesa com um banquete preparado, abrir um cofre com a chave, pegar uma faca de lá e levar de volta antes que seu tempo acabe, mas não pode comer nem beber nada do banquete, para não acordar uma criatura muito doida que está no final da mesa. Ofelia come umas uvas da mesa, a criatura acorda e ela tem que sair correndo para escapar. O terceiro item no SP é (adivinhem só?) uma faca, que posteriormente será usada para matar o rei do bordel para conseguir o quarto item... uma chave! No fim do LF, para testar a Ofelia, o fauno a manda buscar seu irmão recém-nascido. Ao chegar com a criança para o fauno, o fauno diz que precisa derramar algumas gotas do sangue de um inocente (o bebê) para abrir o portal para o mundo mágico. Ofelia se recusa a entregar a criança, o fauno diz que então não pode levá-la ao mundo mágico, e desaparece. Nisso o capitão fascista está a perseguindo e a alcança, e lhe dá um tiro. Ofelia morre, e seu sangue é derramado na fontezinha feliz, e ela vai parar no mundo mágico, onde ela é congratulada por sua decisão nobre. No SP, o quinto e último item é a própria protagonista, que se sacrifica para a outra guria fugir.

No SP, os eventos de cada mundo paralelo possuem uma correspondência com os eventos dos outros. No LF, há algumas evidências de que o mundo mágico é o mesmo mundo real (a mandrágora entregada pelo fauno para que Ofelia pusesse debaixo da cama da mãe que está grávida e quase morrendo para curá-la de fato funciona, o capitão a encontra, e a mãe a atira no fogo, após o que a mãe adoece de novo; quando a Ofelia está fugindo do capitão no labirinto do fauno e chega a um beco sem saída, uma passagem se abre a ela, pela qual ela escapa e o capitão a perde; aparentemente Ofelia escapa do quarto pela porta desenhada a giz na parede).

Basicamente, o Sucker Punch tem um enredo bastante superficial, e boa parte da substância da história foi copiada largamente inspirada pelo Labirinto do Fauno. Na verdade, a maior parte do enredo do SP parece servir apenas para prover um ambiente onde menininhas bonitas com pouca roupa realizam coreografias interessantes, em meio a explosões e lutas com espada. Por que a guria se imagina em um bordel, onde todas as guriazinhas usam um quase nada de roupa o tempo todo? Ok, talvez seja o trauma de ter quase sofrido assédio do padrasto. Bom, mas por que diabos no outro mundo imaginário, onde ela literalmente luta para escapar, as gurias também usam roupinhas curtas, e a protagonista em particular usa algo que lembra um uniforme de colegial japonesa com menos pano? Na medida em que o intento do filme era de fato esse ("On the other hand, though it's fetishistic and personal, I like to think that my fetishes aren't that obscure. Who doesn't want to see girls running down the trenches of World War One wreaking havoc?" (palavras do diretor)), pode-se dizer que é um bom filme. Agora dizer que "one interpretation of the film is that it is a critique on geek culture's sexism and objectification of women" (segundo o mesmo diretor, segundo a Wikipédia) é demás. A menos que seja alguma espécie de psicologia reversa: o filme é o alvo de sua própria crítica. [Edit: Yes, it is. Quem diria?]

Fim de papo. Awey.

5 comentários

Checked exceptions

2012-04-18 01:03 -0300. Tags: comp, prog, rant

Passei a vida mantendo uma distância saudável do public static void fucking Java, com exceções esporádicas que ajudaram a reforçar o sentimento. Esse semestre, entretanto, estou fazendo a rica cadeira de Programação Paralela e Distribuída, cujos exercícios de laboratório devem ser todos feitos em Java. Nesses momentos de diversão incomensurável descobri mais uma das maravilhas dessa linguagem: o conceito de checked exceptions.

A idéia é genial: na declaração de um método, é possível adicionar uma cláusula throws exceção, que informa ao compilador que o método pode lançar a exceção em questão, e o programador é obrigado a tratá-la quando chamar o método, ou a declarar que o seu método também pode lançar a exceção. O resultado prático, obviamente, é que enquanto se está testando o programa acaba-se adicionando tratadores de exceção do tipo:

try { do_whatever(); } catch (Exception e) {}

Até que de fato queremos debugar um problema com do_whatever(), e queremos o bom e velho stack trace que acompanha a exceção, mas que está sendo engolido pelo catch vazio. Nesse momento, mudamos o tratador para:

try {
    do_whatever();
} catch (Exception e) {
    System.out.println(e);
    e.printStackTrace();
}

Que é, bem, o tratador de exceções padrão. Não é ótimo ter que escrever você mesmo o tratador de exceções padrão da linguagem?

Além de, de certa forma, matar o propósito das exceções (isolar o tratamento de erros do resto do código), o que os criadores da linguagem aparentemente não conceberam é que enquanto eu estou escrevendo o programa, ele não está pronto. Eu quero poder ir testando o programa à medida em que vou escrevendo, e não quero entupir o código com tratadores de exceção enquanto estou fazendo o código funcionar. De fato, o que eu quero normalmente é não tratar uma exceção, para obter um stack trace. Talvez eu sequer tenha decidido ainda qual a melhor maneira de tratar o erro. (O quê?! Você não tinha um projeto detalhado do programa antes de começar a escrevê-lo? Blasfêmia! Vá vestir sua gravata e volte para os diagramas UML.)

Lisp foi provavelmente a primeira linguagem a incorporar a idéia de que é útil executar programas incompletos, e o Common Lisp provavelmente ainda é a linguagem que faz isso com mais competência. Em (Common) Lisp, chamadas a funções inexistentes, chamadas com número errado de argumentos, tipos diferentes dos esperados, etc., geram erros de execução. Um bom compilador também gera warnings em tempo de compilação ao encontrar esses problemas, mas não impede a compilação e execução do programa. O programador pode então decidir corrigir os erros imediatamente, quando possível, ou executar o programa incompleto para fins de teste. Erros de execução em Common Lisp não encerram o programa, mas abrem o debugger, de onde se pode averiguar e alterar o estado do programa, e erros geralmente são continuáveis, i.e., é possível prosseguir com a execução de um programa após a ocorrência de um erro de execução. Além disso, as implementações oferecem um prompt interativo de onde se pode chamar as funções do programa diretamente, sem necessidade de escrever uma função main para testar funções internas do programa. O resultado é que se pode encontrar e corrigir os problemas do programa à medida em que ele é escrito, ao invés de escrever um código longo e "inteiro" e debugá-lo depois.

C, nesse e em outros quesitos, segue uma filosofia "não ajuda mas também não atrapalha". Boa parte dos erros de tipos geram warnings ao invés de erros. A linguagem não força o tratamento de erros de execução (mas também não lhe diz que eles ocorrem, além da sucinta mensagem Segmentation fault).

Hoje em dia as assim chamadas linguagens de script são mais flexíveis com programas incompletos, e muitas oferecem prompts interativos, embora estes costumem parecer afterthoughts*, especialmente comparados com o Common Lisp. Não matar o programa diante de erros e permitir continuar a execução ainda é uma idéia louca demais para essas linguagens.

Java, entretanto, destaca-se entre as demais linguagens em ativamente impedir a execução de programas incompletos, não por razões técnicas, como em C, mas por filosofia da linguagem. Ironicamente, um dos co-autores da especificação do Java é o Guy Steele, um dos criadores do Scheme, autor do Common Lisp, the Language, e um dos principais membros do comitê de padronização do Common Lisp. A vida tem dessas coisas.

* Appendix: o show de horrores

Desculpem o rant. Pretendo que eles sejam esporádicos no blog, e escrever conteúdo mais útil/interessante/informativo a maior parte do tempo. Mas sendo ranting a função primária de um blog, é impossível suprimi-la totalmente. Humanitas precisa reclamar.

E já que chegamos até aqui, vou aproveitar a atmosfera smug-lisp-weenie e fazer uma análise dos prompts interativos das linguagens de script mais populares.

Python: Python é whitespace-nazi o suficiente para reclamar se o código estiver indentado mas não estiver subordinado a nenhuma estrutura de controle (e.g., a primeira linha de código de um programa nunca pode estar indentada). O prompt, obviamente, não é exceção. Como conseqüência, não se pode copiar um trecho interno de uma função e colar diretamente no prompt para testar. Além disso, o prompt interativo prestativamente imprime ... quando espera que um comando de múltiplas linhas seja completado, o que significa que não se pode copiar o trecho de código sem alterações e colar de novo no prompt (afinal seta-pra-cima anda pelo histórico linha por linha, não expressão por expressão) ou no código-fonte.

Ruby: Não é whitespace-nazi, mas compartilha dos outros problemas.

Perl: Segundo o FAQ da linguagem, a maneira típica de usar a linguagem interativamente é entrando no debugger, com um comando do tipo perl -de 42. O debugger não tem suporte a readline (i.e., sem seta-pra-cima). O debugger não imprime o resultado das expressões. Há comandos para imprimir, mas...

  DB<1> @a = (1,2,3)

  DB<2> p @a
123
  DB<3> $a = [1,2,3]

  DB<4> p $a
ARRAY(0x209d968)

Perdeu pro GDB, hein? Lamentável.

PHP: A versão linha de comando do php tem um "modo interativo", que consiste simplesmente em ler o código da stdin as usual, mas executar as expressões à medida em que são lidas. Sem prompt, sem histórico, sem impressão das expressões por default. E sim, tem que digitar o <?php antes do código.

JavaScript: Eu ia avacalhar com o JavaScript por costume, mas depois das duas acima é complicado. O prompt do SpiderMonkey usa as conversões de string padrão do JavaScript, que transformam [[1,2,3],[4,5,6]] em "1,2,3,4,5,6", mas pelo menos é fácil de remendar. O histórico é linha-por-linha (aliás, o mísero e ignorado bash anda expressão por expressão no histórico), mas pelo menos o prompt não imprime um prefixo antes das linhas de continuação. E parece que as versões mais novas do SpiderMonkey imprimem os objetos numa sintaxe JSON-like, o que resolve o problema anterior e ganha do <main.Foo instance at 0x00234269> das linguagens acima. É, a coisa tá tão feia que o JavaScript ganhou de todo o mundo. A vida tem dessas coisas.

3 comentários

Main menu

Posts recentes

Tags

comp (72) unix (27) life (27) prog (26) random (22) about (18) mundane (18) lang (17) mind (14) web (13) img (10) rant (9) privacy (8) bash (7) esperanto (7) ramble (6) conlang (5) pldesign (5) freedom (4) home (4) lisp (4) misc (4) book (4) copyright (4) kbd (3) film (3) academia (3) security (3) politics (3) music (3) android (2) editor (2) poem (2) physics (2) network (2) worldly (2) cook (2) wrong (2) c (2) php (2) mestrado (2) perl (1) pointless (1) kindle (1) shell (1) audio (1)

Elsewhere

Quod vide


Copyright © 2012-2014 Vítor Bujés Ubatuba De Araújo
O conteúdo deste site, a menos que de outra forma especificado, pode ser utilizado livremente, com ou sem alterações, desde que seja mencionado o autor, preferivelmente com a URL do documento original.

Powered by Blognir.