Loading...

SUCINTA É PODER

Original

Maio de 2002

"A quantidade de significado comprimido em um pequeno espaço por sinais algébricos é outra circunstância que facilita os raciocínios que estamos acostumados a realizar com a ajuda deles."

  • Charles Babbage, citado na palestra do Prêmio Turing de Iverson

Na discussão sobre questões levantadas por Revenge of the Nerds na lista de discussão LL1, Paul Prescod escreveu algo que ficou na minha mente.

O objetivo do Python é regularidade e legibilidade, não concisão.

À primeira vista, isso parece uma coisa bastante condenável de se afirmar sobre uma linguagem de programação. Até onde eu sei, concisão = poder. Se for assim, então substituindo, obtemos

O objetivo do Python é regularidade e legibilidade, não poder.

e isso não parece uma troca (se é que é uma troca) que você gostaria de fazer. Não está longe de dizer que o objetivo do Python não é ser eficaz como uma linguagem de programação.

Sucintude = poder? Isso me parece uma questão importante, talvez a mais importante para qualquer pessoa interessada em design de linguagem, e uma que seria útil confrontar diretamente. Não tenho certeza ainda de que a resposta seja um simples sim, mas parece uma boa hipótese para começar.

Hipótese

Minha hipótese é que concisão é poder, ou é algo próximo o suficiente para que, exceto em exemplos patológicos, você possa tratá-los como idênticos.

Parece-me que a sucinta é para isso que as linguagens de programação servem. Os computadores ficariam igualmente felizes em ouvir o que fazer diretamente em linguagem de máquina. Acho que a principal razão pela qual nos damos ao trabalho de desenvolver linguagens de alto nível é para obter alavancagem, para que possamos dizer (e, mais importante, pensar) em 10 linhas de uma linguagem de alto nível o que exigiria 1000 linhas de linguagem de máquina. Em outras palavras, o ponto principal das linguagens de alto nível é tornar o código-fonte menor.

Se o propósito das linguagens de alto nível é criar um código-fonte menor, e o poder de algo é o quão bem ele atinge seu propósito, então a medida do poder de uma linguagem de programação é o quão pequenos ela torna seus programas.

Por outro lado, uma linguagem que não torna seus programas pequenos está fazendo um trabalho ruim em relação ao que as linguagens de programação deveriam fazer, como uma faca que não corta bem ou uma impressão ilegível.

Métricas

Pequeno em que sentido? A medida mais comum de tamanho de código são as linhas de código. Mas acho que essa métrica é a mais comum porque é a mais fácil de medir. Não acho que alguém realmente acredite que seja o verdadeiro teste do comprimento de um programa. Diferentes linguagens têm diferentes convenções sobre o quanto você deve colocar em uma linha; em C, muitas linhas não têm nada além de um ou dois delimitadores.

Outro teste fácil é o número de caracteres em um programa, mas isso também não é muito bom; algumas linguagens (Perl, por exemplo) usam identificadores mais curtos do que outras.

Acho que uma medida melhor do tamanho de um programa seria o número de elementos, onde um elemento é qualquer coisa que seria um nó distinto se você desenhasse uma árvore representando o código-fonte. O nome de uma variável ou função é um elemento; um inteiro ou um número de ponto flutuante é um elemento; um segmento de texto literal é um elemento; um elemento de um padrão, ou uma diretiva de formato, é um elemento; um novo bloco é um elemento. Há casos limítrofes (-5 é dois elementos ou um?), mas acho que a maioria deles é a mesma para todas as linguagens, então eles não afetam muito as comparações.

Essa métrica precisa ser desenvolvida, e pode exigir interpretação no caso de linguagens específicas, mas acho que ela tenta medir a coisa certa, que é o número de partes que um programa tem. Acho que a árvore que você desenharia neste exercício é o que você tem que fazer na sua cabeça para conceber o programa, e então seu tamanho é proporcional à quantidade de trabalho que você tem que fazer para escrevê-lo ou lê-lo.

Projeto

Esse tipo de métrica nos permitiria comparar diferentes linguagens, mas esse não é, pelo menos para mim, seu principal valor. O principal valor do teste de sucinta é como um guia no design de linguagens. A comparação mais útil entre linguagens é entre duas variantes potenciais da mesma linguagem. O que posso fazer na linguagem para tornar os programas mais curtos?

Se a carga conceitual de um programa é proporcional à sua complexidade, e um dado programador pode tolerar uma carga conceitual fixa, então isso é o mesmo que perguntar, o que posso fazer para permitir que os programadores façam o máximo? E isso me parece idêntico a perguntar, como posso projetar uma boa linguagem?

(Aliás, nada torna mais patentemente óbvio que a velha máxima "todas as linguagens são equivalentes" é falsa do que projetar linguagens. Quando você está projetando uma nova linguagem, você está constantemente comparando duas linguagens — a linguagem se eu fizesse x, e se eu não fizesse — para decidir qual é melhor. Se essa fosse realmente uma pergunta sem sentido, você poderia muito bem jogar uma moeda.)

Visar a concisão parece uma boa maneira de encontrar novas ideias. Se você consegue fazer algo que torna muitos programas diferentes mais curtos, provavelmente não é uma coincidência: você provavelmente descobriu uma nova abstração útil. Você pode até mesmo ser capaz de escrever um programa para ajudar a pesquisar padrões repetidos no código-fonte. Entre outras linguagens, aquelas com reputação de concisão seriam as que você deve procurar para novas ideias: Forth, Joy, Icon.

Comparação

A primeira pessoa a escrever sobre essas questões, até onde eu sei, foi Fred Brooks no Mythical Man Month . Ele escreveu que os programadores pareciam gerar aproximadamente a mesma quantidade de código por dia, independentemente da linguagem. Quando li isso pela primeira vez, no começo dos meus vinte anos, foi uma grande surpresa para mim e pareceu ter implicações enormes. Isso significava que (a) a única maneira de escrever software mais rápido era usar uma linguagem mais sucinta e (b) alguém que se desse ao trabalho de fazer isso poderia deixar os concorrentes que não o fizessem para trás.

A hipótese de Brooks, se for verdadeira, parece estar no cerne do hacking. Nos anos seguintes, prestei muita atenção a qualquer evidência que conseguisse obter sobre a questão, de estudos formais a anedotas sobre projetos individuais. Não vi nada que o contradissesse.

Ainda não vi evidências que me parecessem conclusivas, e não espero ver. Estudos como a comparação de linguagens de programação de Lutz Prechelt, embora gerem o tipo de resultados que eu esperava, tendem a usar problemas que são muito curtos para serem testes significativos. Um teste melhor de uma linguagem é o que acontece em programas que levam um mês para serem escritos. E o único teste real, se você acredita como eu que o principal propósito de uma linguagem é ser boa para pensar (em vez de apenas dizer a um computador o que fazer depois que você pensou nisso) é quais coisas novas você pode escrever nela. Então, qualquer comparação de linguagens em que você tem que atender a uma especificação predefinida está testando ligeiramente a coisa errada.

O verdadeiro teste de uma linguagem é o quão bem você pode descobrir e resolver novos problemas, não o quão bem você pode usá-la para resolver um problema que outra pessoa já formulou. Esses dois são critérios bem diferentes. Na arte, meios como bordado e mosaico funcionam bem se você sabe de antemão o que quer fazer, mas são absolutamente ruins se você não sabe. Quando você quer descobrir a imagem enquanto a faz — como você tem que fazer com algo tão complexo quanto uma imagem de uma pessoa, por exemplo — você precisa usar um meio mais fluido como lápis, tinta ou tinta a óleo. E, de fato, a maneira como tapeçarias e mosaicos são feitos na prática é fazer uma pintura primeiro e depois copiá-la. (A palavra "cartoon" foi originalmente usada para descrever uma pintura destinada a esse propósito).

O que isso significa é que nunca é provável que tenhamos comparações precisas do poder relativo das linguagens de programação. Teremos comparações precisas, mas não precisas. Em particular, estudos explícitos com o propósito de comparar linguagens, porque provavelmente usarão pequenos problemas, e necessariamente usarão problemas predefinidos, tenderão a subestimar o poder das linguagens mais poderosas.

Relatórios de campo, embora necessariamente sejam menos precisos do que estudos "científicos", provavelmente serão mais significativos. Por exemplo, Ulf Wiger da Ericsson fez um estudo que concluiu que Erlang era 4-10x mais sucinto do que C++, e proporcionalmente mais rápido para desenvolver software em:

Comparações entre projetos de desenvolvimento interno da Ericsson indicam produtividade de linha/hora similar, incluindo todas as fases do desenvolvimento de software, independentemente de qual linguagem (Erlang, PLEX, C, C++ ou Java) foi usada. O que diferencia as diferentes linguagens então se torna o volume do código-fonte.

O estudo também lida explicitamente com um ponto que estava apenas implícito no livro de Brooks (já que ele mediu linhas de código depurado): programas escritos em linguagens mais poderosas tendem a ter menos bugs. Isso se torna um fim em si mesmo, possivelmente mais importante do que a produtividade do programador, em aplicações como switches de rede.

O teste de sabor

No final das contas, acho que você tem que seguir seu instinto. Qual é a sensação de programar na linguagem? Acho que a maneira de encontrar (ou projetar) a melhor linguagem é se tornar hipersensível a quão bem uma linguagem permite que você pense, então escolher/projetar a linguagem que parece melhor. Se algum recurso da linguagem for estranho ou restritivo, não se preocupe, você saberá sobre isso.

Essa hipersensibilidade terá um custo. Você descobrirá que não suporta programação em linguagens desajeitadas. Acho insuportavelmente restritivo programar em linguagens sem macros, assim como alguém acostumado com tipagem dinâmica acha insuportavelmente restritivo ter que voltar a programar em uma linguagem onde você tem que declarar o tipo de cada variável e não pode fazer uma lista de objetos de tipos diferentes.

Não sou o único. Conheço muitos hackers Lisp com quem isso aconteceu. Na verdade, a medida mais precisa do poder relativo das linguagens de programação pode ser a porcentagem de pessoas que conhecem a linguagem e que aceitarão qualquer trabalho em que consigam usar essa linguagem, independentemente do domínio da aplicação.

Restritividade

Acho que a maioria dos hackers sabe o que significa uma linguagem parecer restritiva. O que acontece quando você sente isso? Acho que é a mesma sensação que você tem quando a rua que você quer pegar está bloqueada, e você tem que fazer um longo desvio para chegar onde queria ir. Há algo que você quer dizer, e a linguagem não deixa.

O que realmente está acontecendo aqui, eu acho, é que uma linguagem restritiva é aquela que não é sucinta o suficiente. O problema não é simplesmente que você não consegue dizer o que planejou. É que o desvio que a linguagem faz você tomar é mais longo. Tente este experimento mental. Suponha que houvesse algum programa que você quisesse escrever, e a linguagem não deixasse você expressá-lo da maneira que planejou, mas em vez disso o forçasse a escrever o programa de alguma outra forma que fosse mais curta. Para mim, pelo menos, isso não pareceria muito restritivo. Seria como se a rua que você queria pegar estivesse bloqueada, e o policial no cruzamento o direcionasse para um atalho em vez de um desvio. Ótimo!

Acho que a maior parte (noventa por cento?) do sentimento de restrição vem de ser forçado a fazer o programa que você escreve na linguagem mais longo do que aquele que você tem na cabeça. Restrição é principalmente falta de sucinta. Então, quando uma linguagem parece restritiva, o que isso (principalmente) significa é que ela não é sucinta o suficiente, e quando uma linguagem não é sucinta, ela parecerá restritiva.

Legibilidade

A citação com a qual comecei menciona duas outras qualidades, regularidade e legibilidade. Não tenho certeza do que é regularidade, ou qual vantagem, se houver, um código regular e legível tem sobre um código meramente legível. Mas acho que sei o que significa legibilidade, e acho que também está relacionado à concisão.

Temos que ter cuidado aqui para distinguir entre a legibilidade de uma linha individual de código e a legibilidade do programa inteiro. É a segunda que importa. Concordo que uma linha de Basic provavelmente será mais legível do que uma linha de Lisp. Mas um programa escrito em Basic terá mais linhas do que o mesmo programa escrito em Lisp (especialmente quando você cruza para Greenspunland). O esforço total de ler o programa Basic certamente será maior.

esforço total = esforço por linha x número de linhas

Não tenho tanta certeza de que a legibilidade seja diretamente proporcional à sucinta quanto tenho de que o poder é, mas certamente a sucinta é um fator (no sentido matemático; veja a equação acima) na legibilidade. Então pode nem ser significativo dizer que o objetivo de uma linguagem é a legibilidade, não a sucinta; poderia ser como dizer que o objetivo era a legibilidade, não a legibilidade.

O que legibilidade por linha significa, para o usuário que encontra a linguagem pela primeira vez, é que o código-fonte não parecerá ameaçador . Então legibilidade por linha pode ser uma boa decisão de marketing, mesmo que seja uma má decisão de design. É isomórfico à técnica muito bem-sucedida de deixar as pessoas pagarem em parcelas: em vez de assustá-las com um alto preço inicial, você diz a elas o baixo pagamento mensal. Planos de parcelamento são uma perda líquida para o comprador, no entanto, como a mera legibilidade por linha provavelmente é para o programador. O comprador fará muitos desses pagamentos baixos, baixos; e o programador lerá muitas dessas linhas legíveis individualmente.

Essa troca é anterior às linguagens de programação. Se você está acostumado a ler romances e artigos de jornal, sua primeira experiência de leitura de um artigo de matemática pode ser desanimadora. Pode levar meia hora para ler uma única página. E, no entanto, tenho certeza de que a notação não é o problema, embora possa parecer que é. O artigo de matemática é difícil de ler porque as ideias são difíceis. Se você expressasse as mesmas ideias em prosa (como os matemáticos tinham que fazer antes de desenvolverem notações sucintas), elas não seriam mais fáceis de ler, porque o artigo cresceria até o tamanho de um livro.

Até que ponto?

Várias pessoas rejeitaram a ideia de que sucinta = poder. Acho que seria mais útil, em vez de simplesmente argumentar que elas são a mesma coisa ou não, perguntar: até que ponto sucinta = poder? Porque claramente sucinta é uma grande parte do que as linguagens de alto nível servem. Se não é só para isso que servem, então para que mais servem, e quão importantes, relativamente, são essas outras funções?

Não estou propondo isso só para tornar o debate mais civilizado. Eu realmente quero saber a resposta. Quando, se alguma vez, uma linguagem é sucinta demais para seu próprio bem?

A hipótese com a qual comecei foi que, exceto em exemplos patológicos, eu achava que a concisão poderia ser considerada idêntica ao poder. O que eu quis dizer foi que em qualquer linguagem que alguém projetasse, elas seriam idênticas, mas que se alguém quisesse projetar uma linguagem explicitamente para refutar essa hipótese, provavelmente conseguiria. Nem disso eu tenho certeza, na verdade.

Idiomas, não programas

Devemos deixar claro que estamos falando sobre a sucinta de linguagens, não de programas individuais. Certamente é possível que programas individuais sejam escritos de forma muito densa.

Eu escrevi sobre isso em On Lisp . Uma macro complexa pode ter que economizar muitas vezes seu próprio comprimento para ser justificada. Se escrever uma macro cabeluda pode economizar dez linhas de código toda vez que você a usa, e a macro em si tem dez linhas de código, então você obtém uma economia líquida em linhas se usá-la mais de uma vez. Mas isso ainda pode ser uma má jogada, porque as definições de macro são mais difíceis de ler do que o código comum. Você pode ter que usar a macro dez ou vinte vezes antes que ela produza uma melhoria líquida na legibilidade.

Tenho certeza de que toda linguagem tem tais compensações (embora eu suspeite que as apostas aumentam conforme a linguagem se torna mais poderosa). Todo programador deve ter visto código que alguma pessoa inteligente tornou marginalmente mais curto usando truques de programação duvidosos.

Então não há argumento sobre isso-- pelo menos, não de mim. Programas individuais certamente podem ser sucintos demais para seu próprio bem. A questão é, uma linguagem pode ser? Uma linguagem pode obrigar os programadores a escrever código curto (em elementos) às custas da legibilidade geral?

Uma razão pela qual é difícil imaginar uma linguagem sendo muito sucinta é que se houvesse uma maneira excessivamente compacta de expressar algo, provavelmente também haveria uma maneira mais longa. Por exemplo, se você achasse que programas Lisp usando muitas macros ou funções de ordem superior eram muito densos, você poderia, se preferisse, escrever código que fosse isomórfico ao Pascal. Se você não quiser expressar fatorial no Arc como uma chamada para uma função de ordem superior

 (rec zero 1 * 1-)

você também pode escrever uma definição recursiva:

 (rfn fact (x) (if (zero x) 1 (* x (fact (1- x)))))

Embora eu não consiga pensar em nenhum exemplo de cabeça, estou interessado na questão de se uma linguagem pode ser muito sucinta. Existem linguagens que forçam você a escrever código de uma forma que seja confusa e incompreensível? Se alguém tiver exemplos, eu ficaria muito interessado em vê-los.

(Lembrete: o que estou procurando são programas que sejam muito densos de acordo com a métrica de "elementos" esboçada acima, não apenas programas que sejam curtos porque delimitadores podem ser omitidos e tudo tem um nome de um caractere.)