VINGANÇA DOS NERDS
OriginalMaio de 2002
"Estávamos atrás dos programadores C++. Conseguimos arrastar muitos deles até quase o Lisp."
- Guy Steele, co-autor da especificação do Java
No mundo dos negócios de software, há uma luta constante entre os acadêmicos de cabeça pontuda e outra força igualmente formidável, os chefes de cabeça pontuda. Todo mundo sabe quem é o chefe de cabeça pontuda, certo? Acho que a maioria das pessoas no mundo da tecnologia não só reconhece esse personagem de desenho animado, mas conhece a pessoa real em sua empresa que ele é modelado.
O chefe de cabeça pontuda combina milagrosamente duas qualidades que são comuns por si só, mas raramente vistas juntas: (a) ele não sabe absolutamente 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 cabeça pontuda 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 que linguagem você deve escrevê-lo. Exatamente. Ele acha que você deve escrevê-lo em Java.
Por que ele pensa isso? Vamos dar uma olhada na mente do chefe de cabeça pontuda. O que ele está pensando é algo assim. Java é um padrão. Eu sei que deve ser, porque li sobre isso na imprensa o tempo todo. Como é um padrão, não vou me meter em encrenca por usá-lo. E isso também significa que sempre haverá muitos programadores Java, então, se os programadores que trabalham para mim agora saírem, como os programadores que trabalham para mim misteriosamente sempre fazem, eu posso facilmente substituí-los.
Bem, isso não parece tão irrazoável. Mas tudo se baseia em uma suposição não declarada, e essa suposição acaba sendo falsa. O chefe de cabeça pontuda 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 o que todo mundo mais está usando.
Mas nem todas as linguagens são equivalentes, e acho que posso provar isso para você sem nem mesmo entrar nas diferenças entre elas. Se você perguntasse ao chefe de cabeça pontuda em 1992 em que linguagem o software deveria ser escrito, ele responderia com a mesma falta de hesitação que hoje. O software deve ser escrito em C++. Mas se as linguagens são todas equivalentes, por que a opinião do chefe de cabeça pontuda deveria mudar? Na verdade, por que os desenvolvedores do Java se dariam ao trabalho de criar uma nova linguagem?
Presumivelmente, se você criar uma nova linguagem, é porque você 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 o Java que o Java foi projetado para resolver alguns problemas com o C++. Então aí está: as linguagens não são todas equivalentes. Se você seguir o rastro pela mente do chefe de cabeça pontuda até o Java e depois de volta pela história do Java até suas origens, você acaba tendo uma ideia que contradiz a suposição com a qual você começou.
Então, quem está certo? James Gosling ou o chefe de cabeça pontuda? Não é surpreendente que Gosling esteja certo. Algumas linguagens são melhores, para determinados problemas, do que outras. E você sabe, isso levanta algumas questões interessantes. O Java foi projetado para ser melhor, para certos problemas, do que o C++. Quais problemas? Quando o Java é melhor e quando o C++ é melhor? 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 um verdadeiro pandora. Se o chefe de cabeça pontuda tivesse que pensar sobre o problema em toda a sua complexidade, isso faria seu cérebro explodir. Desde que ele considere todas as linguagens equivalentes, tudo o que ele precisa fazer é escolher a que parece ter mais momentum, e como isso é mais uma questão de moda do que de tecnologia, até mesmo 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 ideal entre duas coisas que ele não sabe nada: a adequação relativa das vinte ou mais linguagens principais para o problema que ele precisa resolver e as chances de encontrar programadores, bibliotecas, etc. para cada uma. Se é isso que está do outro lado da porta, não é surpresa que o chefe de cabeça pontuda 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 o Java deve ser bastante bom, porque é a linguagem de programação nova e na moda. Ou será? Se você olhar para o mundo das linguagens de programação de longe, parece que o Java é a última novidade. (De longe, tudo o que você pode ver é o grande outdoor piscando pago pelo Sun.) Mas se você olhar de perto para esse mundo, você descobrirá que existem graus de modernidade. Dentro da subcultura hacker, existe outra linguagem chamada Perl que é considerada muito mais moderna do que o Java. O Slashdot, por exemplo, é gerado em Perl. Eu não acho que você encontraria aqueles caras usando Java Server Pages. Mas existe outra linguagem mais nova, chamada Python, cujos usuários tendem a olhar com desdém para o Perl, e mais esperando nas asas.
Se você olhar para essas linguagens em ordem, Java, Perl, Python, você notará um padrão interessante. Pelo menos, você notará esse padrão se for um hacker de Lisp. Cada uma delas é progressivamente mais parecida com o Lisp. O Python copia até mesmo recursos que muitos hackers de Lisp consideram erros. Você poderia traduzir programas simples de Lisp para Python linha por linha. Estamos em 2002 e as linguagens de programação quase alcançaram 1958.
Acompanhando a Matemática
O que eu quero dizer é que o 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 pode ser verdade? A tecnologia de computadores não é algo que muda muito rapidamente? Quero dizer, em 1958, os computadores eram behemoths do tamanho de geladeiras com o poder de processamento de um relógio de pulso. Como qualquer tecnologia tão antiga pode ser relevante, quanto mais superior aos últimos desenvolvimentos?
Eu vou te contar como. É porque o Lisp não foi realmente projetado para ser uma linguagem de programação, pelo menos não no sentido que usamos hoje. O que entendemos por 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 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 o Lisp era mais elegante do que as máquinas de Turing era escrever uma função Lisp universal e mostrar que ela é mais breve e compreensível do que a descrição de uma máquina de Turing universal. Essa 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 essa notação foi desenvolvida para os fins do artigo, sem pensar que seria usada para expressar programas Lisp na prática.
O que aconteceu a seguir foi que, em algum momento do 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 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: "Olha, 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 ao cálculo". 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 erros, e então anunciou isso como um interpretador Lisp, o que certamente era. Então, nesse ponto, o Lisp tinha essencialmente a forma que tem hoje....
De repente, em questão de semanas, eu acho, McCarthy descobriu que seu exercício teórico havia se transformado em uma linguagem de programação real - e mais poderosa do que ele havia pretendido.
Então a explicação resumida de por que essa linguagem dos anos 1950 não está obsoleta é que não era tecnologia, mas matemática, e a matemática não fica ultrapassada. A coisa certa a se comparar com o Lisp não é o hardware dos anos 1950, mas, digamos, o algoritmo Quicksort, que foi descoberto em 1960 e ainda é o método de ordenação de propósito geral mais rápido.
Há outra linguagem ainda sobrevivente dos anos 1950, o Fortran, e ela representa a abordagem oposta ao design de linguagem. O Lisp era um pedaço de teoria que inesperadamente se transformou em uma linguagem de programação. O Fortran foi desenvolvido intencionalmente como uma linguagem de programação, mas o que agora consideraríamos muito de baixo nível.
O Fortran I, a linguagem que foi desenvolvida em 1956, era um animal muito diferente do Fortran atual. O Fortran I era praticamente linguagem de montagem com matemática. Em alguns aspectos, era menos poderoso que linguagens de montagem mais recentes; não havia subrotinas, por exemplo, apenas ramificações. O Fortran atual agora é argumentavelmente mais próximo do Lisp do que do Fortran I.
O Lisp e o 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 convergido desde então. O Lisp começou poderoso e, nos vinte anos seguintes, ficou rápido. As chamadas linguagens convencionais começaram rápidas e, nos quarenta anos seguintes, gradualmente ficaram mais poderosas, até que agora as mais avançadas delas estão bastante próximas do Lisp. Próximas, mas ainda faltam algumas coisas...
O Que Tornou o Lisp Diferente
Quando foi desenvolvido pela primeira vez, o Lisp incorporava nove novas ideias. Algumas delas agora damos como certas, outras são vistas apenas em linguagens mais avançadas, e duas ainda são exclusivas do Lisp. As nove ideias, em ordem de sua adoção pela corrente principal, são:
Condicionais. Um condicional é uma construção if-then-else. Agora damos isso como certo, mas o Fortran I não tinha. Tinha apenas um goto condicional estreitamente baseado na instrução de máquina subjacente.
Um tipo de função. No Lisp, as funções são um tipo de dados 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. O Lisp foi a primeira linguagem de programação a suportá-la.
Tipagem dinâmica. No Lisp, todas as variáveis são efetivamente ponteiros. Os valores é que têm tipos, não as 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. Os 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 subsequentes, que distinguem entre expressões e instruções.
Era natural ter essa distinção no Fortran I porque não se podia aninhar instruções. E, portanto, embora você precisasse de expressões para a matemática funcionar, não havia motivo para fazer qualquer outra coisa retornar um valor, porque não poderia haver nada esperando por ele.
Essa limitação desapareceu com a chegada das linguagens de estrutura de blocos, mas então já era tarde demais. A distinção entre expressões e instruções estava enraizada. Ela se espalhou do Fortran para o Algol e depois para seus descendentes.
Um tipo de símbolo. Os símbolos são efetivamente ponteiros para strings armazenadas em uma tabela hash. Então 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á distinção real entre leitura, compilação e tempo de execução. Você pode compilar ou executar código durante a leitura, ler ou executar código durante a compilação 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 dos macros; compilar em tempo de execução é a base do uso do Lisp como linguagem de extensão em programas como o Emacs; e ler em tempo de execução permite que os programas se comuniquem usando s-expressions, uma ideia recentemente reinventada como XML.
Quando o 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 da década de 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 generalizadas. O número 6 está começando a aparecer no mainstream.
O Python tem uma forma do 7, embora não pareça haver sintaxe para isso.
Quanto ao número 8, esse pode ser o mais interessante do lote. As ideias 8 e 9 só se tornaram parte do Lisp por acidente, porque Steve Russell implementou algo que McCarthy nunca teve a intenção de implementar. E no entanto, essas ideias acabam sendo responsáveis pela aparência estranha do Lisp e por suas características mais distintivas. O 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 revela-se ser um recurso muito poderoso. As ideias 8 e 9 juntas significam que você pode escrever programas que escrevem programas. Isso pode soar como uma ideia bizarra, mas é uma coisa comum no Lisp. A maneira mais comum de fazer isso é com algo chamado de macro.
O termo "macro" não significa no Lisp o que significa em outras linguagens. Um macro Lisp pode ser desde uma abreviação até um compilador para uma nova linguagem. Se você quiser realmente entender o Lisp, ou apenas expandir seus horizontes de programação, eu aprenderia mais sobre macros.
Os macros (no sentido do Lisp) ainda são, até onde eu sei, exclusivos do Lisp. Isso se deve em parte ao fato de que, para ter macros, você provavelmente tem que fazer sua linguagem parecer tão estranha quanto o Lisp. Pode também ser porque, se você adicionar esse último incremento de poder, você não pode mais reivindicar ter inventado uma nova linguagem, mas apenas um novo dialeto do Lisp.
Eu 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. Esse é de fato o atributo definitório do Lisp: foi para fazer isso que McCarthy deu ao Lisp a forma que ele tem.
Onde as Linguagens Importam
Então, suponha que o Lisp represente um tipo de limite que as linguagens principais estão se aproximando assintoticamente - isso significa que você deve realmente usá-lo para escrever software? Quanto você perde usando uma linguagem menos poderosa? Não é mais sábio, às vezes, não estar na ponta da inovação? E a popularidade não é até certo ponto sua própria justificativa? O chefe de cabelos espetados não está certo, por exemplo, em querer usar uma linguagem para a qual ele pode facilmente contratar programadores?
Existem, é claro, projetos em que a escolha da linguagem de programação não faz muita diferença. Como regra geral, quanto mais exigente for a aplicação, mais alavancagem você obtém ao usar uma linguagem poderosa. Mas muitos projetos não são nada exigentes. A maior parte da programação provavelmente consiste em escrever pequenos programas de colagem, e para esses pequenos programas de colagem você pode usar qualquer linguagem com a qual você já esteja familiarizado e que tenha boas bibliotecas para o que você precisa fazer. Se você só precisa alimentar dados de um aplicativo do Windows para outro, claro, use o Visual Basic.
Você pode escrever pequenos programas de colagem em Lisp também (eu o uso como uma calculadora de desktop), mas o maior ganho para linguagens como o Lisp está no outro extremo do espectro, onde você precisa escrever programas sofisticados para resolver problemas difíceis em meio a uma concorrência feroz. Um bom exemplo é o programa de pesquisa de tarifas aéreas que a ITA Software licencia para a Orbitz. Esses caras entraram em um mercado já dominado por dois grandes concorrentes estabelecidos, a Travelocity e a Expedia, e parecem tê-los simplesmente humilhado tecnologicamente.
O núcleo do aplicativo da ITA é um programa em Common Lisp de 200.000 linhas que pesquisa muito mais possibilidades do que seus concorrentes, que aparentemente ainda estão usando técnicas de programação da era dos mainframes. (Embora a ITA também esteja, de certa forma, usando uma linguagem de programação da era dos mainframes.) Nunca vi nenhum código da ITA, mas de acordo com um de seus principais hackers, eles usam muitos 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 cabelos espetados não está completamente enganado em se preocupar com isso. Mas, como ele não entende os riscos, tende a magnificá-los.
Posso pensar em três problemas que podem 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 dificuldade em contratar programadores.
Quão grande é cada um desses problemas? A importância do primeiro varia dependendo de você ter controle sobre todo o sistema. Se você estiver escrevendo software que precisa ser executado na máquina de um usuário remoto em cima de um sistema operacional com bugs e fechado (não menciono nomes), pode haver vantagens em escrever seu aplicativo na mesma linguagem do sistema operacional. Mas se você controlar todo o sistema e tiver o código-fonte de todas as partes, como a ITA provavelmente faz, você pode usar as linguagens que quiser. Se surgir alguma incompatibilidade, você pode corrigi-la você mesmo.
Em aplicativos baseados 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 da linguagem de programação". É por isso que ouvimos falar de novas linguagens como Perl e Python. Não estamos ouvindo sobre essas linguagens porque as pessoas as estão usando para escrever aplicativos do Windows, mas porque as pessoas as estão usando em servidores. E à medida que o software se desloca [1] do desktop e para os servidores (um futuro até mesmo a Microsoft parece resignada a aceitar), haverá cada vez menos pressão para usar tecnologias do meio do caminho.
Quanto às bibliotecas, sua importância também depende do aplicativo. 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, é inferior a qualquer coisa que você provavelmente chamaria de aplicativo. Se uma empresa se considera no negócio de software e está escrevendo um aplicativo que será um de seus produtos, então provavelmente envolverá vários hackers e levará pelo menos seis meses para ser escrito. 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 cabelos espetados, a dificuldade de contratar programadores, acho que é uma falácia. Afinal, quantos hackers você precisa contratar? Com certeza, agora todos nós sabemos que o software é melhor desenvolvido por equipes de menos de dez pessoas. E você não deve ter problemas em contratar hackers nessa escala para qualquer linguagem que alguém já tenha ouvido falar. Se você não conseguir encontrar dez hackers de Lisp, então sua empresa provavelmente está localizada na cidade errada para desenvolver software.
Na verdade, escolher uma linguagem mais poderosa provavelmente diminui o tamanho da equipe de que você precisa, porque (a) se você usar uma linguagem mais poderosa, provavelmente não precisará de tantos hackers, e (b) os hackers que trabalham em linguagens mais avançadas provavelmente são mais inteligentes.
Não estou dizendo que você não vai receber muita pressão para usar o que são percebidas como tecnologias "padrão". Na Viaweb (agora Yahoo Store), levantamos algumas sobrancelhas entre os VCs e possíveis compradores ao usar Lisp. Mas também levantamos sobrancelhas ao usar caixas genéricas da Intel 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 sistema operacional comercial real como o Windows NT, por ignorar um suposto padrão de comércio eletrônico chamado SET que ninguém lembra mais, e assim por diante.
Você não pode deixar os executivos tomarem decisões técnicas por você. Será que assustou alguns potenciais compradores o fato de usarmos Lisp? Alguns, um pouco, mas se não tivéssemos usado Lisp, não teríamos sido capazes de escrever o software que os fez querer nos comprar. O que parecia uma anomalia para eles era, na verdade, causa e efeito.
Se você iniciar uma startup, não projete seu produto para agradar investidores de risco ou potenciais compradores. Projete seu produto para agradar os usuários. Se você conquistar os usuários, tudo o mais virá. E se você não o fizer, ninguém se importará com o quão confortavelmente ortodoxas foram suas escolhas tecnológicas.
O Custo de Ser Mediano
Quanto você perde usando uma linguagem menos poderosa? Existem alguns dados sobre isso.
A medida de poder mais conveniente 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 bottom-up. Em vez de simplesmente escrever seu aplicativo na linguagem base, você constrói sobre a 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 bottom-up também deve ser mais fácil de modificar, pois em muitos casos a camada de linguagem não precisará mudar.
O tamanho do código é importante, porque o tempo necessário 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 ser escrito - e você não pode contornar isso contratando mais pessoas, porque além de um certo tamanho, novos contratados são realmente um prejuízo líquido. 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, quanto mais curtos são seus programas se você os escrever em Lisp? A maioria dos números que ouvi sobre Lisp versus C, por exemplo, tem sido em torno de 7-10x. Mas um artigo recente sobre a ITA na New Architect magazine 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, eu presumo que eles obtiveram esse número da ITA. Se for o caso, então podemos confiar nele; o software da ITA inclui muito C e C++ além de Lisp, então eles estão falando por experiência.
Meu palpite é que esses múltiplos nem mesmo 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ê gastasse um ano em um novo recurso, eles poderiam duplicá-lo em menos de três semanas. Enquanto se eles gastassem apenas três meses desenvolvendo algo novo, levaria cinco anos para você ter isso também.
E você sabe o que? Esse é o melhor cenário possível. Quando você fala sobre proporções de tamanho de código, você está implicitamente assumindo que você pode realmente escrever o programa na linguagem mais fraca. Mas, na verdade, existem limites para 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 em que há simplesmente demais para manter na cabeça de uma só vez.
Então, quando eu 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, eu quero dizer cinco anos se nada der errado. Na verdade, da maneira como as coisas funcionam na maioria das empresas, qualquer projeto de desenvolvimento que levaria cinco anos provavelmente nunca será concluído.
Eu admito que este é um caso extremo. Os hackers da ITA parecem ser incomumente inteligentes, e C é uma linguagem bastante de baixo nível. Mas em um mercado competitivo, mesmo uma diferença de dois ou três para um seria o suficiente para garantir que você sempre ficaria para trás.
Uma Receita
Este é o tipo de possibilidade que o chefe de cabelos espetados nem mesmo quer pensar. E então a maioria deles não pensa. Porque, você sabe, quando chega a hora, o chefe de cabelos espetados não se importa se sua empresa for espancada, 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 é "melhor prática da indústria". Seu propósito é proteger o chefe de cabelos espetados 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 este termo tenha sido originalmente usado para descrever métodos contábeis e assim por diante. O que isso significa, aproximadamente, é não faça nada estranho. E na contabilidade, provavelmente é uma boa ideia. Os termos "vanguarda" e "contabilidade" não soam bem juntos. Mas quando você importa esse critério para decisões sobre tecnologia, você começa a obter as respostas erradas.
A tecnologia muitas vezes deve ser de vanguarda. Em linguagens de programação, como Erann Gat apontou, o que a "melhor prática da indústria" realmente lhe dá não é o melhor, mas apenas a média. Quando uma decisão faz com que você desenvolva software a uma fração da taxa de concorrentes mais agressivos, "melhor prática" é um termo inadequado.
Então, aqui temos duas peças de informação que acho muito valiosas. De fato, eu 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 eles, 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 assumir o problema mais difícil que você puder encontrar, usar a linguagem mais poderosa que você puder conseguir e esperar que os chefes de cabelos espetados de seus concorrentes voltem à média.
Apêndice: Poder
Como ilustração do que eu 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 que a versão Lisp porque você tem que extrair os parâmetros manualmente em Perl.
Em Smalltalk, o código é um pouco mais longo que em Lisp:
foo: n
|s|
s := n.
^[:i| s := s+i. ]
porque, embora em geral as variáveis lexicais funcionem, você não pode fazer uma atribuição a um parâmetro, então você precisa criar uma nova variável s.
Em JavaScript, o exemplo é, novamente, um pouco 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 ela de maneira típica do Perl, permitindo que você omita os 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ê precisa criar uma estrutura de dados para armazenar o valor de n. E embora o Python tenha um tipo de dados de função, não há uma 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 que você acaba com:
def foo(n):
s = [n]
def bar(i):
s[0] += i
return s[0]
return bar
Os usuários do Python podem legitimamente perguntar por que eles 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 vão, um dia.
(Mas se eles não quiserem esperar que o Python evolua o resto
do caminho para o Lisp, eles sempre poderiam apenas...)
Em linguagens OO, você pode, em certa medida, simular
um closure (uma função que se refere a variáveis definidas em
escopos de envolvimento) definindo uma classe com um método
e um campo para substituir cada variável de um escopo
de envolvimento. Isso faz o programador fazer o tipo de análise de código
que seria feita pelo compilador em uma linguagem
com suporte completo 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 esta é a
maneira preferida de resolver o problema em Python, escrevendo
either
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
Eu incluo esses porque eu não queria que os defensores do Python
dissessem que eu estava representando mal a linguagem,
mas ambos me parecem mais complexos 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,
reservados, especialmente __call__, parece
um pouco de um truque.
Na rivalidade entre Perl e Python, a reivindicação dos
hackers do Python parece ser que
que o Python é uma alternativa mais elegante ao Perl, mas o que
este caso mostra é que o poder é a elegância definitiva:
o programa Perl é mais simples (tem menos elementos), mesmo que o
sintaxe é 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 este problema.
Ken Anderson diz que o seguinte código é aproximadamente tão próximo
quanto 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 fica aquém da especificação porque só funciona para
inteiros. Após muitas trocas de e-mails com hackers Java,
eu diria que escrever uma versão devidamente polimórfica
que se comporta como os exemplos anteriores está entre
maldosamente desajeitado e impossível. Se alguém quiser
escrever um, eu ficaria muito curioso para vê-lo, mas pessoalmente
eu desisti.
Não é literalmente verdade que você não pode resolver este
problema em outras linguagens, é claro. O fato
de que todas essas linguagens são Turing-equivalentes 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 parece uma piada, mas acontece tão frequentemente
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 lenta,
especificada informalmente e cheia de bugs, da 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 um, ou (c) se tornará um compilador humano para um.
Vemos isso já
começando a acontecer no exemplo do Python, onde estamos
simulando efetivamente o código que um compilador
geraria para implementar uma variável lexical.
Esta 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 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, pelo menos para mim, 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**
O CPU IBM 704 tinha aproximadamente o tamanho de uma geladeira, mas muito mais pesado. O CPU pesava 3150 libras e os 4K de RAM estavam em uma caixa separada pesando outras 4000 libras. O 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 acumulador 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 _])
O triste relato de Erann Gat sobre a "melhor prática da indústria" no JPL me inspirou a abordar essa frase amplamente mal aplicada.
Peter Norvig descobriu que 16 dos 23 padrões em *Design Patterns* eram "[invisíveis ou mais simples](http://www.norvig.com/design-patterns/)" em Lisp.
Obrigado às muitas pessoas que responderam às minhas perguntas sobre vários idiomas e/ou leram rascunhos deste texto, 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 criei uma página adicional para lidar com as questões que eles levantaram: [Re: Revenge of the Nerds](https://paulgraham.com/icadmore.html).
Também desencadeou uma discussão extensa e muitas vezes útil na lista de discussão [LL1](http://www.ai.mit.edu/~gregs/ll1-discuss-archive-html/threads.html). Veja particularmente o e-mail de Anton van Straaten sobre compressão semântica.
Alguns dos e-mails no LL1 me levaram a tentar aprofundar o assunto do poder da linguagem em [Succinctness is Power](https://paulgraham.com/power.html).
Um conjunto maior de implementações canônicas do [benchmark do gerador de acumulador](https://paulgraham.com/accgen.html) são coletados em sua própria página.
[Tradução para o japonês](http://www.shiro.dreamhost.com/scheme/trans/icad-j.html), [Tradução para o espanhol](http://kapcoweb.com/p/docs/translations/revenge_of_the_nerds/revenge_of_the_nerds-es.html), [Tradução para o chinês](http://flyingapplet.spaces.live.com/blog/cns!F682AFBD82F7E261!375.entry)