Loading...

A VINGANÇA DOS NERDS

Original

Maio de 2002

"Estávamos atrás dos programadores de C++. Conseguimos arrastar muitos deles até metade do caminho para Lisp."

  • Guy Steele, co-autor da especificação do Java

No setor de software, há uma luta contínua entre os acadêmicos cabeçudos e outra força igualmente formidável, os chefes de cabelo espetado. Todos sabem quem é o chefe de cabelo espetado, certo? Acho que a maioria das pessoas no mundo da tecnologia não apenas reconhece esse personagem de desenho animado, mas conhece a pessoa real em sua empresa que ele representa.

O chefe de cabelo espetado combina milagrosamente duas qualidades que são comuns por si mesmas, mas raramente vistas juntas: (a) ele não sabe nada sobre tecnologia e (b) ele tem opiniões muito fortes sobre isso.

Suponha, por exemplo, que você precise escrever um software. O chefe de cabelo espetado não tem ideia de como esse software deve funcionar e não consegue distinguir uma linguagem de programação de outra, e ainda assim ele sabe em qual linguagem você deve escrevê-lo. Exatamente. Ele acha que você deve escrevê-lo em Java.

Por que ele pensa isso? Vamos dar uma olhada dentro da cabeça do chefe de cabelo espetado. O que ele está pensando é algo como isto: Java é um padrão. Eu sei que deve ser, porque leio sobre isso na imprensa o tempo todo. Como é um padrão, não vou me meter em problemas por usá-lo. E isso também significa que sempre haverá muitos programadores de Java, então, se os programadores que trabalham para mim agora saírem, como os programadores que trabalham para mim misteriosamente sempre fazem, posso facilmente substituí-los.

Bem, isso não soa tão irracional. Mas tudo isso se baseia em uma suposição não dita, e essa suposição acaba sendo falsa. O chefe de cabelo espetado acredita que todas as linguagens de programação são praticamente equivalentes. Se isso fosse verdade, ele estaria certo. Se as linguagens são todas equivalentes, claro, use qualquer linguagem que todo mundo mais está usando.

Mas nem todas as linguagens são equivalentes, e acho que posso provar isso a você sem nem mesmo entrar nas diferenças entre elas. Se você perguntasse ao chefe de cabelo espetado em 1992 em qual linguagem o software deveria ser escrito, ele teria respondido com tão pouca hesitação quanto faz hoje. O software deveria ser escrito em C++. Mas se as linguagens são todas equivalentes, por que a opinião do chefe de cabelo espetado deveria mudar? Na verdade, por que os desenvolvedores de Java deveriam ter se dado ao trabalho de criar uma nova linguagem?

Presumivelmente, se você cria uma nova linguagem, é porque acha que ela é melhor de alguma forma do que o que as pessoas já tinham. E, de fato, Gosling deixa claro no primeiro artigo sobre Java que Java foi projetado para corrigir alguns problemas com C++. Então, aí está: as linguagens não são todas equivalentes. Se você seguir a trilha pela cabeça do chefe de cabelo espetado até Java e depois voltar pela história de Java até suas origens, você acaba segurando uma ideia que contradiz a suposição com a qual você começou.

Então, quem está certo? James Gosling ou o chefe de cabelo espetado? Não surpreendentemente, Gosling está certo. Algumas linguagens são melhores, para certos problemas, do que outras. E você sabe, isso levanta algumas questões interessantes. Java foi projetado para ser melhor, para certos problemas, do que C++. Quais problemas? Quando Java é melhor e quando C++? Existem situações em que outras linguagens são melhores do que qualquer uma delas?

Uma vez que você começa a considerar essa questão, você abriu uma verdadeira caixa de Pandora. Se o chefe de cabelo espetado tivesse que pensar sobre o problema em toda a sua complexidade, isso faria seu cérebro explodir. Enquanto ele considerar todas as linguagens equivalentes, tudo o que ele precisa fazer é escolher a que parece ter mais impulso, e como isso é mais uma questão de moda do que de tecnologia, até ele provavelmente pode chegar à resposta certa. Mas se as linguagens variam, ele de repente tem que resolver duas equações simultâneas, tentando encontrar um equilíbrio ótimo entre duas coisas sobre as quais ele não sabe nada: a adequação relativa das cerca de vinte linguagens principais para o problema que ele precisa resolver e as chances de encontrar programadores, bibliotecas, etc. para cada uma. Se isso está do outro lado da porta, não é surpresa que o chefe de cabelo espetado não queira abri-la.

A desvantagem de acreditar que todas as linguagens de programação são equivalentes é que isso não é verdade. Mas a vantagem é que isso torna sua vida muito mais simples. E acho que essa é a principal razão pela qual a ideia é tão difundida. É uma ideia confortável.

Sabemos que Java deve ser muito bom, porque é a linguagem de programação nova e legal. Ou é? Se você olhar para o mundo das linguagens de programação à distância, parece que Java é a última novidade. (De longe o suficiente, tudo o que você pode ver é o grande outdoor piscante pago pela Sun.) Mas se você olhar para esse mundo de perto, descobrirá que existem graus de "legalidade". Dentro da subcultura hacker, há outra linguagem chamada Perl que é considerada muito mais legal do que Java. Slashdot, por exemplo, é gerado por Perl. Não acho que você encontraria aqueles caras usando Java Server Pages. Mas há outra linguagem, mais nova, chamada Python, cujos usuários tendem a menosprezar Perl, e mais esperando nos bastidores.

Se você olhar para essas linguagens na ordem, Java, Perl, Python, notará um padrão interessante. Pelo menos, você notará esse padrão se for um hacker de Lisp. Cada uma é progressivamente mais parecida com Lisp. Python copia até mesmo recursos que muitos hackers de Lisp consideram erros. Você poderia traduzir programas simples de Lisp para Python linha por linha. É 2002, e as linguagens de programação quase alcançaram 1958.

Alcançando a Matemática

O que quero dizer é que Lisp foi descoberto pela primeira vez por John McCarthy em 1958, e as linguagens de programação populares só agora estão alcançando as ideias que ele desenvolveu naquela época.

Agora, como isso poderia ser verdade? A tecnologia da computação não muda muito rapidamente? Quero dizer, em 1958, os computadores eram behemoths do tamanho de uma geladeira com o poder de processamento de um relógio de pulso. Como qualquer tecnologia tão antiga poderia ser relevante, muito menos superior aos últimos desenvolvimentos?

Vou te dizer como. É porque Lisp não foi realmente projetado para ser uma linguagem de programação, pelo menos não no sentido que entendemos hoje. O que queremos dizer com uma linguagem de programação é algo que usamos para dizer a um computador o que fazer. McCarthy eventualmente pretendia desenvolver uma linguagem de programação nesse sentido, mas o Lisp que realmente acabamos tendo foi baseado em algo separado que ele fez como um exercício teórico - um esforço para definir uma alternativa mais conveniente à Máquina de Turing. Como McCarthy disse mais tarde,

Outra maneira de mostrar que Lisp era mais elegante do que máquinas de Turing era escrever uma função Lisp universal e mostrar que ela é mais breve e mais compreensível do que a descrição de uma máquina de Turing universal. Esta foi a função Lisp eval..., que calcula o valor de uma expressão Lisp.... Escrever eval exigiu inventar uma notação representando funções Lisp como dados Lisp, e tal notação foi criada para os propósitos do artigo sem pensar que seria usada para expressar programas Lisp na prática.

O que aconteceu a seguir foi que, algum tempo no final de 1958, Steve Russell, um dos alunos de pós-graduação de McCarthy, olhou para essa definição de eval e percebeu que, se a traduzisse para a linguagem de máquina, o resultado seria um interpretador Lisp.

Isso foi uma grande surpresa na época. Aqui está o que McCarthy disse sobre isso mais tarde em uma entrevista:

Steve Russell disse, olhe, por que eu não programo esse eval..., e eu disse a ele, ho, ho, você está confundindo teoria com prática, esse eval é destinado à leitura, não à computação. Mas ele foi em frente e fez isso. Ou seja, ele compilou o eval do meu artigo em código de máquina [IBM] 704, corrigindo bugs, e então anunciou isso como um interpretador Lisp, que certamente era. Então, naquele ponto, Lisp tinha essencialmente a forma que tem hoje....

De repente, em questão de semanas, acho que McCarthy encontrou seu exercício teórico transformado em uma linguagem de programação real - e uma mais poderosa do que ele havia pretendido.

Então, a explicação curta de por que essa linguagem dos anos 1950 não está obsoleta é que não era tecnologia, mas matemática, e matemática não fica obsoleta. A coisa certa para comparar Lisp não é o hardware dos anos 1950, mas, digamos, o algoritmo Quicksort, que foi descoberto em 1960 e ainda é a ordenação de propósito geral mais rápida.

Há uma outra linguagem que ainda sobrevive dos anos 1950, Fortran, e ela representa a abordagem oposta ao design de linguagem. Lisp era uma peça de teoria que inesperadamente se transformou em uma linguagem de programação. Fortran foi desenvolvido intencionalmente como uma linguagem de programação, mas o que agora consideraríamos uma linguagem de nível muito baixo.

Fortran I, a linguagem que foi desenvolvida em 1956, era um animal muito diferente do Fortran atual. Fortran I era praticamente uma linguagem de montagem com matemática. De certa forma, era menos poderosa do que linguagens de montagem mais recentes; não havia sub-rotinas, por exemplo, apenas ramificações. O Fortran atual é agora, indiscutivelmente, mais próximo de Lisp do que do Fortran I.

Lisp e Fortran eram os troncos de duas árvores evolutivas separadas, uma enraizada na matemática e outra enraizada na arquitetura de máquinas. Essas duas árvores têm se convergido desde então. Lisp começou poderoso e, ao longo dos próximos vinte anos, ficou rápido. As chamadas linguagens mainstream começaram rápidas e, ao longo dos próximos quarenta anos, gradualmente se tornaram mais poderosas, até que agora as mais avançadas delas estão bastante próximas de Lisp. Perto, mas ainda estão faltando algumas coisas....

O que Tornou Lisp Diferente

Quando foi desenvolvido pela primeira vez, Lisp incorporou nove novas ideias. Algumas dessas ideias agora consideramos garantidas, outras são vistas apenas em linguagens mais avançadas, e duas ainda são exclusivas de Lisp. As nove ideias são, em ordem de adoção pela mainstream,

Condicionais. Um condicional é uma construção if-then-else. Agora consideramos isso garantido, mas o Fortran I não tinha. Ele tinha apenas um goto condicional baseado na instrução de máquina subjacente.

Um tipo de função. Em Lisp, funções são um tipo de dado assim como inteiros ou strings. Elas têm uma representação literal, podem ser armazenadas em variáveis, podem ser passadas como argumentos, e assim por diante.

Recursão. Lisp foi a primeira linguagem de programação a suportá-la.

Tipagem dinâmica. Em Lisp, todas as variáveis são efetivamente ponteiros. Valores são o que têm tipos, não variáveis, e atribuir ou vincular variáveis significa copiar ponteiros, não o que eles apontam.

Coleta de lixo.

Programas compostos de expressões. Programas Lisp são árvores de expressões, cada uma das quais retorna um valor. Isso contrasta com o Fortran e a maioria das linguagens que se seguiram, que distinguem entre expressões e declarações.

Era natural ter essa distinção no Fortran I porque você não podia aninhar declarações. E assim, enquanto você precisava de expressões para a matemática funcionar, não havia sentido em fazer qualquer outra coisa retornar um valor, porque não poderia haver nada esperando por isso.

Essa limitação desapareceu com a chegada das linguagens estruturadas em blocos, mas, por então, era tarde demais. A distinção entre expressões e declarações estava entrincheirada. Ela se espalhou do Fortran para o Algol e depois para ambos os seus descendentes.

Um tipo de símbolo. Símbolos são efetivamente ponteiros para strings armazenadas em uma tabela hash. Assim, você pode testar a igualdade comparando um ponteiro, em vez de comparar cada caractere.

Uma notação para código usando árvores de símbolos e constantes.

A linguagem inteira lá o tempo todo. Não há uma verdadeira distinção entre tempo de leitura, tempo de compilação e tempo de execução. Você pode compilar ou executar código enquanto lê, ler ou executar código enquanto compila, e ler ou compilar código em tempo de execução.

Executar código em tempo de leitura permite que os usuários reprogramem a sintaxe do Lisp; executar código em tempo de compilação é a base das macros; compilar em tempo de execução é a base do uso do Lisp como uma linguagem de extensão em programas como o Emacs; e ler em tempo de execução permite que programas se comuniquem usando s-expressions, uma ideia recentemente reinventada como XML.

Quando Lisp apareceu pela primeira vez, essas ideias estavam muito distantes da prática de programação comum, que era ditada em grande parte pelo hardware disponível no final dos anos 1950. Com o tempo, a linguagem padrão, incorporada em uma sucessão de linguagens populares, evoluiu gradualmente em direção ao Lisp. As ideias 1-5 agora são amplamente difundidas. O número 6 está começando a aparecer na mainstream. Python tem uma forma de 7, embora não pareça haver nenhuma sintaxe para isso.

Quanto ao número 8, esta pode ser a mais interessante de todas. As ideias 8 e 9 só se tornaram parte do Lisp por acidente, porque Steve Russell implementou algo que McCarthy nunca pretendia que fosse implementado. E ainda assim, essas ideias acabam sendo responsáveis tanto pela aparência estranha do Lisp quanto por suas características mais distintivas. Lisp parece estranho não tanto porque tem uma sintaxe estranha, mas porque não tem sintaxe; você expressa programas diretamente nas árvores de análise que são construídas nos bastidores quando outras linguagens são analisadas, e essas árvores são feitas de listas, que são estruturas de dados do Lisp.

Expressar a linguagem em suas próprias estruturas de dados acaba sendo um recurso muito poderoso. As ideias 8 e 9 juntas significam que você pode escrever programas que escrevem programas. Isso pode parecer uma ideia bizarra, mas é algo cotidiano no Lisp. A maneira mais comum de fazer isso é com algo chamado macro.

O termo "macro" não significa em Lisp o que significa em outras linguagens. Uma macro Lisp pode ser qualquer coisa, desde uma abreviação até um compilador para uma nova linguagem. Se você realmente quer entender o Lisp, ou apenas expandir seus horizontes de programação, eu aprenderei mais sobre macros.

Macros (no sentido Lisp) ainda são, até onde sei, exclusivas do Lisp. Isso é em parte porque, para ter macros, você provavelmente precisa fazer sua linguagem parecer tão estranha quanto o Lisp. Também pode ser porque, se você adicionar aquele último incremento de poder, não poderá mais afirmar que inventou uma nova linguagem, mas apenas um novo dialeto do Lisp.

Menciono isso principalmente como uma piada, mas é bastante verdade. Se você definir uma linguagem que tenha car, cdr, cons, quote, cond, atom, eq, e uma notação para funções expressas como listas, então você pode construir todo o resto do Lisp a partir disso. Essa é, de fato, a qualidade definidora do Lisp: foi para tornar isso possível que McCarthy deu ao Lisp a forma que tem.

Onde as Linguagens Importam

Então, suponha que o Lisp represente um tipo de limite que as linguagens mainstream estão se aproximando assintoticamente - isso significa que você deve realmente usá-lo para escrever software? Quanto você perde ao usar uma linguagem menos poderosa? Não é mais sábio, às vezes, não estar na vanguarda da inovação? E a popularidade não é, até certo ponto, sua própria justificativa? O chefe de cabelo espetado não está certo, por exemplo, ao querer usar uma linguagem para a qual ele pode facilmente contratar programadores?

Existem, é claro, projetos onde a escolha da linguagem de programação não importa muito. Como regra, quanto mais exigente a aplicação, mais vantagem você obtém ao usar uma linguagem poderosa. Mas muitos projetos não são exigentes. A maior parte da programação provavelmente consiste em escrever pequenos programas de cola, e para pequenos programas de cola você pode usar qualquer linguagem com a qual já esteja familiarizado e que tenha boas bibliotecas para o que você precisa fazer. Se você só precisa alimentar dados de um aplicativo Windows para outro, claro, use Visual Basic.

Você pode escrever pequenos programas de cola em Lisp também (eu o uso como uma calculadora de mesa), mas a maior vantagem para linguagens como Lisp está na outra extremidade do espectro, onde você precisa escrever programas sofisticados para resolver problemas difíceis diante de uma concorrência feroz. Um bom exemplo é o programa de busca de tarifas aéreas que a ITA Software licencia para a Orbitz. Esses caras entraram em um mercado já dominado por dois grandes concorrentes entrincheirados, Travelocity e Expedia, e parecem ter acabado de humilhá-los tecnologicamente.

O núcleo da aplicação da ITA é um programa de 200.000 linhas em Common Lisp que busca muitas ordens de magnitude mais possibilidades do que seus concorrentes, que aparentemente ainda estão usando técnicas de programação da era de mainframe. (Embora a ITA também esteja, de certa forma, usando uma linguagem de programação da era de mainframe.) Nunca vi nenhum código da ITA, mas de acordo com um de seus principais hackers, eles usam muitas macros, e não me surpreende ouvir isso.

Forças Centrípetas

Não estou dizendo que não há custo em usar tecnologias incomuns. O chefe de cabelo espetado não está completamente errado em se preocupar com isso. Mas, como ele não entende os riscos, tende a ampliá-los.

Posso pensar em três problemas que poderiam surgir do uso de linguagens menos comuns. Seus programas podem não funcionar bem com programas escritos em outras linguagens. Você pode ter menos bibliotecas à sua disposição. E você pode ter dificuldades para contratar programadores.

Quão problemático é cada um desses? A importância do primeiro varia dependendo de você ter controle sobre todo o sistema. Se você está escrevendo software que precisa rodar na máquina de um usuário remoto em cima de um sistema operacional fechado e cheio de bugs (não menciono nomes), pode haver vantagens em escrever sua aplicação na mesma linguagem que o SO. Mas se você controla todo o sistema e tem o código-fonte de todas as partes, como a ITA presumivelmente tem, você pode usar qualquer linguagem que quiser. Se surgir alguma incompatibilidade, você pode consertá-la você mesmo.

Em aplicações baseadas em servidor, você pode se dar ao luxo de usar as tecnologias mais avançadas, e acho que essa é a principal causa do que Jonathan Erickson chama de "renascimento das linguagens de programação." É por isso que ouvimos até mesmo sobre novas linguagens como Perl e Python. Não estamos ouvindo sobre essas linguagens porque as pessoas as estão usando para escrever aplicativos Windows, mas porque as pessoas as estão usando em servidores. E à medida que o software se desloca do desktop para os servidores (um futuro ao qual até a Microsoft parece resignada), haverá cada vez menos pressão para usar tecnologias medianas.

Quanto às bibliotecas, sua importância também depende da aplicação. Para problemas menos exigentes, a disponibilidade de bibliotecas pode superar o poder intrínseco da linguagem. Onde está o ponto de equilíbrio? Difícil dizer exatamente, mas onde quer que esteja, está longe de qualquer coisa que você provavelmente chamaria de aplicação. Se uma empresa se considera no negócio de software e está escrevendo uma aplicação que será um de seus produtos, então provavelmente envolverá vários hackers e levará pelo menos seis meses para ser escrita. Em um projeto desse tamanho, linguagens poderosas provavelmente começam a superar a conveniência de bibliotecas pré-existentes.

A terceira preocupação do chefe de cabelo espetado, a dificuldade de contratar programadores, acho que é uma cortina de fumaça. Afinal, quantos hackers você precisa contratar? Certamente, agora todos sabemos que o software é melhor desenvolvido por equipes de menos de dez pessoas. E você não deve ter problemas para contratar hackers nessa escala para qualquer linguagem que alguém já tenha ouvido falar. Se você não consegue encontrar dez hackers de Lisp, então sua empresa provavelmente está baseada na cidade errada para desenvolver software.

Na verdade, escolher uma linguagem mais poderosa provavelmente diminui o tamanho da equipe que você precisa, porque (a) se você usar uma linguagem mais poderosa, provavelmente não precisará de tantos hackers, e (b) hackers que trabalham em linguagens mais avançadas tendem a ser mais inteligentes.

Não estou dizendo que você não terá muita pressão para usar o que são percebidas como tecnologias "padrão". Na Viaweb (agora Yahoo Store), levantamos algumas sobrancelhas entre VCs e potenciais adquirentes ao usar Lisp. Mas também levantamos sobrancelhas ao usar caixas Intel genéricas como servidores em vez de servidores "de força industrial" como Suns, por usar uma variante de Unix de código aberto então obscura chamada FreeBSD em vez de um verdadeiro sistema operacional comercial como o Windows NT, por ignorar um suposto padrão de comércio eletrônico chamado SET que ninguém agora se lembra, e assim por diante.

Você não pode deixar os executivos tomarem decisões técnicas por você. Isso alarmou alguns potenciais adquirentes que usamos Lisp? Alguns, levemente, mas se não tivéssemos usado Lisp, não teríamos conseguido escrever o software que os fez querer nos comprar. O que parecia uma anomalia para eles era, de fato, causa e efeito.

Se você iniciar uma startup, não projete seu produto para agradar VCs ou potenciais adquirentes. Projete seu produto para agradar os usuários. Se você conquistar os usuários, todo o resto seguirá. E se você não conquistar, ninguém se importará quão confortavelmente ortodoxas foram suas escolhas tecnológicas.

O Custo de Ser Medíocre

Quanto você perde ao usar uma linguagem menos poderosa? Na verdade, há alguns dados sobre isso.

A medida mais conveniente de poder é provavelmente o tamanho do código. O objetivo das linguagens de alto nível é lhe dar abstrações maiores - tijolos maiores, por assim dizer, para que você não precise de tantos para construir uma parede de um determinado tamanho. Portanto, quanto mais poderosa a linguagem, mais curto o programa (não simplesmente em caracteres, é claro, mas em elementos distintos).

Como uma linguagem mais poderosa permite que você escreva programas mais curtos? Uma técnica que você pode usar, se a linguagem permitir, é algo chamado programação de baixo para cima. Em vez de simplesmente escrever sua aplicação na linguagem base, você constrói em cima da linguagem base uma linguagem para escrever programas como o seu, e então escreve seu programa nela. O código combinado pode ser muito mais curto do que se você tivesse escrito todo o seu programa na linguagem base - de fato, é assim que a maioria dos algoritmos de compressão funciona. Um programa de baixo para cima deve ser mais fácil de modificar também, porque em muitos casos a camada de linguagem não precisará mudar.

O tamanho do código é importante, porque o tempo que leva para escrever um programa depende principalmente de seu comprimento. Se seu programa seria três vezes mais longo em outra linguagem, levará três vezes mais tempo para escrever - e você não pode contornar isso contratando mais pessoas, porque além de um certo tamanho, novas contratações são na verdade uma perda líquida. Fred Brooks descreveu esse fenômeno em seu famoso livro The Mythical Man-Month, e tudo o que vi tende a confirmar o que ele disse.

Então, quão mais curtos são seus programas se você os escreve em Lisp? A maioria dos números que ouvi para Lisp versus C, por exemplo, tem sido em torno de 7-10x. Mas um artigo recente sobre a ITA na revista New Architect disse que "uma linha de Lisp pode substituir 20 linhas de C", e como este artigo estava cheio de citações do presidente da ITA, presumo que eles obtiveram esse número da ITA. Se sim, então podemos confiar um pouco nisso; o software da ITA inclui muito de C e C++ além de Lisp, então eles estão falando por experiência.

Meu palpite é que esses múltiplos nem são constantes. Acho que eles aumentam quando você enfrenta problemas mais difíceis e também quando você tem programadores mais inteligentes. Um hacker realmente bom pode extrair mais de ferramentas melhores.

Como um ponto de dados na curva, de qualquer forma, se você fosse competir com a ITA e escolhesse escrever seu software em C, eles seriam capazes de desenvolver software vinte vezes mais rápido do que você. Se você passasse um ano em um novo recurso, eles seriam capazes de duplicá-lo em menos de três semanas. Enquanto se eles passassem apenas três meses desenvolvendo algo novo, levaria cinco anos antes que você também o tivesse.

E sabe de uma coisa? Esse é o melhor cenário. Quando você fala sobre razões de tamanho de código, está implicitamente assumindo que pode realmente escrever o programa na linguagem mais fraca. Mas, de fato, existem limites sobre o que os programadores podem fazer. Se você está tentando resolver um problema difícil com uma linguagem que é muito de baixo nível, você chega a um ponto onde há apenas muita coisa para manter em sua cabeça ao mesmo tempo.

Então, quando digo que levaria cinco anos para o concorrente imaginário da ITA duplicar algo que a ITA poderia escrever em Lisp em três meses, quero dizer cinco anos se nada der errado. De fato, da maneira como as coisas funcionam na maioria das empresas, qualquer projeto de desenvolvimento que levaria cinco anos provavelmente nunca será concluído.

Admito que este é um caso extremo. Os hackers da ITA parecem ser incomumente inteligentes, e C é uma linguagem de nível bastante baixo. Mas em um mercado competitivo, mesmo uma diferença de dois ou três para um seria suficiente para garantir que você sempre estaria atrás.

Uma Receita

Esse é o tipo de possibilidade que o chefe de cabelo espetado nem quer pensar. E assim, a maioria deles não pensa. Porque, você sabe, quando chega a hora, o chefe de cabelo espetado não se importa se sua empresa levar uma surra, desde que ninguém possa provar que é culpa dele. O plano mais seguro para ele pessoalmente é ficar perto do centro do rebanho.

Dentro de grandes organizações, a frase usada para descrever essa abordagem é "melhores práticas da indústria". Seu propósito é proteger o chefe de cabelo espetado da responsabilidade: se ele escolher algo que é "melhor prática da indústria", e a empresa perder, ele não pode ser culpado. Ele não escolheu, a indústria escolheu.

Acredito que esse termo foi usado originalmente para descrever métodos contábeis e assim por diante. O que significa, grosso modo, é não faça nada estranho. E na contabilidade isso provavelmente é uma boa ideia. Os termos "na vanguarda" e "contabilidade" não soam bem juntos. Mas quando você importa esse critério para decisões sobre tecnologia, começa a obter as respostas erradas.

A tecnologia muitas vezes deve estar na vanguarda. Em linguagens de programação, como Erann Gat apontou, o que "melhores práticas da indústria" realmente lhe dá não é o melhor, mas apenas o médio. Quando uma decisão faz com que você desenvolva software a uma fração da taxa de concorrentes mais agressivos, "melhores práticas" é um termo inadequado.

Então, aqui temos duas informações que acho muito valiosas. De fato, sei disso por minha própria experiência. Número 1, as linguagens variam em poder. Número 2, a maioria dos gerentes ignora deliberadamente isso. Entre elas, esses dois fatos são literalmente uma receita para ganhar dinheiro. A ITA é um exemplo dessa receita em ação. Se você quiser vencer em um negócio de software, basta enfrentar o problema mais difícil que puder encontrar, usar a linguagem mais poderosa que conseguir e esperar que os chefes de cabelo espetado de seus concorrentes voltem à média.

Apêndice: Poder

Como uma ilustração do que quero dizer sobre o poder relativo das linguagens de programação, considere o seguinte problema. Queremos escrever uma função que gera acumuladores - uma função que recebe um número n e retorna uma função que recebe outro número i e retorna n incrementado por i.

(Isso é incrementado por, não mais. Um acumulador tem que acumular.)

Em Common Lisp isso seria


(defun foo (n)
(lambda (i) (incf n i)))

e em Perl 5,


sub foo {
my ($n) = @_;
sub {$n += shift}
}

que tem mais elementos do que a versão Lisp porque você tem que extrair parâmetros manualmente em Perl.

Em Smalltalk, o código é ligeiramente mais longo do que em Lisp


foo: n
|s|
s := n.
^[:i| s := s+i. ]

porque, embora em geral variáveis lexicais funcionem, você não pode fazer uma atribuição a um parâmetro, então você tem que criar uma nova variável s.

Em Javascript, o exemplo é, novamente, ligeiramente mais longo, porque o Javascript mantém a distinção entre declarações e expressões, então você precisa de declarações de retorno explícitas para retornar valores:


function foo(n) {
return function (i) {
return n += i } }

(Para ser justo, o Perl também mantém essa distinção, mas lida com isso de maneira típica do Perl, permitindo que você omita retornos.)

Se você tentar traduzir o código Lisp/Perl/Smalltalk/Javascript para Python, você encontrará algumas limitações. Porque o Python não suporta totalmente variáveis lexicais, você tem que criar uma estrutura de dados para manter o valor de n. E embora o Python tenha um tipo de dado de função, não há representação literal para um (a menos que o corpo seja apenas uma única expressão), então você precisa criar uma função nomeada para retornar. Isso é o que você acaba com:


def foo(n):
s = [n]
def bar(i):
s[0] += i
return s[0]
return bar

Os usuários de Python podem legitimamente perguntar por que não podem simplesmente escrever


def foo(n):
return lambda i: return n += i

ou mesmo


def foo(n):
lambda i: n += i

e meu palpite é que eles provavelmente farão, um dia. (Mas se não quiserem esperar que o Python evolua o resto do caminho para Lisp, sempre poderiam...)

Em linguagens OO, você pode, em certa medida, simular um fechamento (uma função que se refere a variáveis definidas em escopos externos) definindo uma classe com um método e um campo para substituir cada variável de um escopo externo. Isso faz com que o programador faça o tipo de análise de código que seria feita pelo compilador em uma linguagem com suporte total para escopo lexical, e não funcionará se mais de uma função se referir à mesma variável, mas é suficiente em casos simples como este.

Especialistas em Python parecem concordar que essa é a maneira preferida de resolver o problema em Python, escrevendo


def foo(n):
class acc:
def __init__(self, s):
self.s = s
def inc(self, i):
self.s += i
return self.s
return acc(n).inc

ou


class foo:
def __init__(self, n):
self.n = n
def __call__(self, i):
self.n += i
return self.n

Incluo isso porque não gostaria que os defensores do Python dissessem que eu estava distorcendo a linguagem, mas ambos parecem para mim mais complexos do que a primeira versão. Você está fazendo a mesma coisa, configurando um lugar separado para manter o acumulador; é apenas um campo em um objeto em vez da cabeça de uma lista. E o uso desses nomes de campo especiais e reservados, especialmente call, parece um pouco uma gambiarra.

Na rivalidade entre Perl e Python, a alegação dos hackers de Python parece ser que Python é uma alternativa mais elegante ao Perl, mas o que este caso mostra é que o poder é a verdadeira elegância: o programa Perl é mais simples (tem menos elementos), mesmo que a sintaxe seja um pouco mais feia.

E quanto a outras linguagens? Nas outras linguagens mencionadas nesta palestra - Fortran, C, C++, Java e Visual Basic - não está claro se você pode realmente resolver esse problema. Ken Anderson diz que o seguinte código é o mais próximo que você pode chegar em Java:


public interface Inttoint {
public int call(int i);
}


public static Inttoint foo(final int n) {
return new Inttoint() {
int s = n;
public int call(int i) {
s = s + i;
return s;
}};
}

Isso não atende à especificação porque funciona apenas para inteiros. Depois de muitas trocas de e-mail com hackers de Java, eu diria que escrever uma versão polimórfica adequada que se comporte como os exemplos anteriores está em algum lugar entre extremamente complicado e impossível. Se alguém quiser escrever uma, eu ficaria muito curioso para ver, mas pessoalmente já desisti.

Não é literalmente verdade que você não pode resolver esse problema em outras linguagens, é claro. O fato de que todas essas linguagens são equivalentes a Turing significa que, estritamente falando, você pode escrever qualquer programa em qualquer uma delas. Então, como você faria isso? No caso limite, escrevendo um interpretador Lisp na linguagem menos poderosa.

Isso soa como uma piada, mas acontece com tanta frequência em graus variados em grandes projetos de programação que há um nome para o fenômeno, a Décima Regra de Greenspun:

Qualquer programa C ou Fortran suficientemente complicado contém uma implementação ad hoc informalmente especificada e cheia de bugs de metade do Common Lisp.

Se você tentar resolver um problema difícil, a questão não é se você usará uma linguagem poderosa o suficiente, mas se você (a) usará uma linguagem poderosa, (b) escreverá um interpretador de fato para uma, ou (c) se tornará você mesmo um compilador humano para uma. Já começamos a ver isso acontecer no exemplo do Python, onde estamos, de fato, simulando o código que um compilador geraria para implementar uma variável lexical.

Essa prática não é apenas comum, mas institucionalizada. Por exemplo, no mundo OO, você ouve muito sobre "padrões". Eu me pergunto se esses padrões não são às vezes evidência do caso (c), o compilador humano, em ação. Quando vejo padrões em meus programas, considero isso um sinal de problemas. A forma de um programa deve refletir apenas o problema que precisa resolver. Qualquer outra regularidade no código é um sinal, para mim pelo menos, de que estou usando abstrações que não são poderosas o suficiente - muitas vezes que estou gerando manualmente as expansões de alguma macro que preciso escrever.

Notas

A CPU IBM 704 era do tamanho de uma geladeira, mas muito mais pesada. A CPU pesava 3150 libras, e os 4K de RAM estavam em uma caixa separada pesando mais 4000 libras. A Sub-Zero 690, uma das maiores geladeiras domésticas, pesa 656 libras.

Steve Russell também escreveu o primeiro jogo de computador (digital), Spacewar, em 1962.

Se você quiser enganar um chefe de cabelo espetado para deixá-lo escrever software em Lisp, você poderia tentar dizer a ele que é XML.

Aqui está o gerador de acumuladores em outros dialetos Lisp:


Scheme: (define (foo n)
(lambda (i) (set! n (+ n i)) n))
Goo:    (df foo (n) (op incf n _)))
Arc:    (def foo (n) [++ n _])

A triste história de Erann Gat sobre "melhores práticas da indústria" na JPL me inspirou a abordar essa frase geralmente mal aplicada.

Peter Norvig descobriu que 16 dos 23 padrões em Design Patterns eram "invisíveis ou mais simples" em Lisp.

Obrigado a muitas pessoas que responderam minhas perguntas sobre várias linguagens e/ou leram rascunhos disso, incluindo Ken Anderson, Trevor Blackwell, Erann Gat, Dan Giffin, Sarah Harlin, Jeremy Hylton, Robert Morris, Peter Norvig, Guy Steele e Anton van Straaten. Eles não têm culpa por quaisquer opiniões expressas.

Relacionado:

Muitas pessoas responderam a esta palestra, então eu criei uma página adicional para lidar com as questões que elas levantaram: Re: A Vingança dos Nerds.

Isso também desencadeou uma discussão extensa e muitas vezes útil na lista de discussão LL1. Veja particularmente o e-mail de Anton van Straaten sobre compressão semântica.

Alguns dos e-mails na LL1 me levaram a tentar aprofundar o assunto do poder da linguagem em A Concisão é Poder.

Um conjunto maior de implementações canônicas do benchmark do gerador acumulador está reunido em sua própria página.

Tradução em Japonês, Tradução em Espanhol, Tradução em Chinês