Loading...

A VINGANÇA DOS NERDS

Original

May 2002

"Nós estávamos atrás dos programadores C++. Conseguimos arrastar muitos deles até a metade do caminho para Lisp."

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

No negócio de software, há uma luta contínua 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 apenas reconhece esse personagem de desenho animado, mas também conhece a pessoa real em sua empresa em que ele é baseado.

O chefe de cabeça pontuda combina miraculosamente duas qualidades que são comuns por si mesmas, mas raramente vistas juntas: (a) ele não sabe absolutamente nada sobre tecnologia, e (b) ele tem opiniões muito fortes sobre ela.

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

Por que ele pensa isso? Vamos dar uma olhada dentro do cérebro do chefe de cabeça pontuda. O que ele está pensando é algo como isso. 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 ter problemas 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 programadores que trabalham para mim misteriosamente sempre fazem, posso facilmente substituir eles.

Bem, isso não parece tão irreal. Mas é tudo baseado em uma suposição tácita, e essa suposição acaba sendo falsa. O chefe de cabeça pontuda acredita que todos as linguagens de programação são mais ou menos equivalentes. Se isso fosse verdade, ele estaria certo no alvo. Se as linguagens são todas equivalentes, claro, use o que linguagem todo mundo está usando.

Mas todas as linguagens não 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 qual linguagem o software deveria ser escrito, ele teria respondido com o mesmo pouca hesitação como ele faz hoje. O software deve ser escrito em C++. Mas se as linguagens são todas equivalentes, por que o opinião do chefe de cabeça pontuda deve mudar? Na verdade, por que os desenvolvedores do Java se deram ao trabalho de criar um novo linguagem?

Presumivelmente, se você cria uma nova linguagem, é porque você acha que é melhor de alguma forma do que o que as pessoas já tinham. E de fato, Gosling deixa claro no primeiro white paper do 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 o rastro pelo cérebro do chefe de cabeça pontuda até o Java e depois de volta pela história do 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 cabeça pontuda? Não surpreendentemente, Gosling está certo. Algumas linguagens são melhores, para certos problemas, do que outras. E você sabe, isso levanta alguns questões interessantes. Java foi projetado para ser melhor, para certos problemas, do que C++. Quais problemas? Quando o Java é melhor e quando é C++? Existem situações em que outras linguagens são melhor do que qualquer um deles?

Assim que você começa a considerar essa pergunta, você abriu um verdadeiro pote de minhocas. Se o chefe de cabeça pontuda tivesse que pensar sobre o problema em toda a sua complexidade, isso faria seu cérebro explodir. Enquanto ele considera todas as linguagens equivalentes, tudo o que ele tem que 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 obter a 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 sobre: a adequação relativa das vinte ou mais principais linguagens para o problema que ele precisa resolver e as chances de encontrar programadores, bibliotecas, etc. para cada um. Se isso é o 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 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 é o linguagem de programação nova e legal. Ou é? Se você olhar para o mundo de linguagens de programação de uma distância, parece que Java é a última coisa. (De longe o suficiente, tudo o que você pode ver é o grande outdoor piscando pago pela Sun.) Mas se você olhar para este mundo de perto, você descobre que existem graus de coolness. Dentro da subcultura hacker, existe outra linguagem chamada Perl que é considerada muito mais legal que Java. Slashdot, por exemplo, é gerado por Perl. Não acho que você encontraria esses caras usando Java Server Pages. Mas existe outro, linguagem mais nova, chamada Python, cujos usuários tendem a desprezar Perl, e mais esperando nos bastidores.

Se você olhar para essas linguagens em ordem, Java, Perl, Python, você percebe um padrão interessante. Pelo menos, você percebe isso padrão se você for um hacker Lisp. Cada um é progressivamente mais como Lisp. Python copia até mesmo recursos que muitos hackers Lisp consideram erros. Você pode traduzir programas Lisp simples 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 estão apenas agora alcançando as ideias que ele desenvolveu então.

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

Vou te dizer como. É porque Lisp não era realmente projetado para ser uma linguagem de programação, pelo menos não no sentido que queremos dizer 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 em esse 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 limpo que as máquinas de Turing era escrever uma função Lisp universal e mostrar que é mais concisa e 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 a invenção de uma notação que representa funções Lisp como dados Lisp, e tal notação foi concebida para os propósitos do artigo sem pensar que seria usado para expressar programas Lisp na prática.

O que aconteceu a seguir foi que, em algum momento 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 ele 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, 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 é para leitura, não para computação. Mas ele foi em frente e fez isso. Ou seja, ele compilou o eval no meu artigo em código de máquina [IBM] 704, corrigindo bugs, e depois anunciou isso como um interpretador Lisp, o 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 teórico exercício transformado em uma linguagem de programação real - e um mais poderoso do que ele pretendia.

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

Há outra linguagem ainda sobrevivendo dos anos 1950, o Fortran, e ela representa a abordagem oposta ao design de linguagem. O Lisp era uma peça 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 consideraríamos agora uma linguagem de muito baixo nível.

Fortran I, a linguagem que foi desenvolvida em 1956, era um animal muito diferente do Fortran atual. O Fortran I era basicamente linguagem assembly com matemática. De certa forma, era menos poderoso do que linguagens assembly mais recentes; não havia sub-rotinas, por exemplo, apenas desvios. O Fortran atual é agora, argumentavelmente, mais próximo do 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áquina. 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 mainstream começaram rápidas e, nos quarenta anos seguintes, gradualmente ficaram mais poderosas, até que agora as mais avançadas delas estão bem perto do Lisp. Perto, mas ainda faltam algumas coisas...

O que tornava o Lisp diferente

Quando foi desenvolvido pela primeira vez, o Lisp incorporava nove novas ideias. Algumas delas agora damos como certas, outras só são vistas em linguagens mais avançadas, e duas ainda são exclusivas do Lisp. As nove ideias são, em ordem de sua adoção pela corrente principal,

Condicionais. Um condicional é uma construção if-then-else. Nós damos isso como certo agora, mas o Fortran I não tinha isso. Ele tinha apenas um goto condicional baseado no comando de máquina subjacente.

Um tipo de função. No Lisp, as funções são um tipo de dados 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 são o 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 que o sucederam, que distinguem entre expressões e instruções.

Era natural ter essa distinção no Fortran I porque você não podia aninhar instruções. E, embora você precisasse de expressões para que a matemática funcionasse, não havia sentido em 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 estruturadas em blocos, mas 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 está lá o tempo todo. Não há uma distinção real entre tempo de leitura, tempo de 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 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 os programas se comuniquem usando s-expressões, 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 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 difundidas. O número 6 está começando a aparecer na corrente principal. O 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 acaso, porque Steve Russell implementou algo que McCarthy nunca pretendia ser implementado. E, no entanto, essas ideias acabam sendo responsáveis pela aparência estranha do Lisp e por seus recursos mais distintos. 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 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 comum no Lisp. A maneira mais comum de fazer isso é com algo chamado macro.

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

Macros (no sentido Lisp) ainda são, até onde eu sei, exclusivas do Lisp. Isso ocorre 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 esse incremento final de poder, você não pode mais afirmar ter inventado uma nova linguagem, mas apenas um novo dialeto do Lisp.

Menciono isso principalmente como uma piada, mas é bem 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 dela. Na verdade, essa é a qualidade definidora 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 realmente represente um tipo de limite que as linguagens mainstream estão se aproximando assintoticamente - isso significa que você realmente deve usá-lo para escrever software? Quanto você perde usando uma linguagem menos poderosa? Não é mais sábio, às vezes, não estar na vanguarda da inovação? E a popularidade não é, em certa medida, sua própria justificativa? Não está certo, por exemplo, o chefe de cabelos pontudos querer usar uma linguagem para a qual ele possa contratar programadores facilmente?

Existem, é claro, projetos em que a escolha da linguagem de programação não importa muito. Como regra geral, quanto mais exigente a aplicação, mais alavancagem você obtém usando uma linguagem poderosa. Mas muitos projetos não são exigentes. A maior parte da programação provavelmente consiste em escrever pequenos programas de colagem, e para pequenos programas de colagem 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 o Visual Basic.

Você também pode escrever pequenos programas de colagem em Lisp (eu o uso como uma calculadora de mesa), mas o maior ganho para linguagens como Lisp está na outra ponta do espectro, onde você precisa escrever programas sofisticados para resolver problemas difíceis diante de uma competição feroz. Um bom exemplo é o programa de pesquisa de tarifas aéreas que o ITA Software licencia para o Orbitz. Esses caras entraram em um mercado já dominado por dois grandes concorrentes estabelecidos, Travelocity e Expedia, e parecem tê-los humilhado tecnologicamente.

O núcleo da aplicação do ITA é um programa Common Lisp de 200.000 linhas que pesquisa muitas ordens de magnitude mais possibilidades do que seus concorrentes, que aparentemente ainda estão usando técnicas de programação da era dos mainframes. (Embora o ITA também esteja, em certo sentido, usando uma linguagem de programação da era dos mainframes.) Nunca vi nenhum código do 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 cabelos pontudos 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 podem surgir ao usar 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 problemas para contratar programadores.

Quão problemático é cada um desses problemas? A importância do primeiro varia dependendo se você tem controle sobre todo o sistema. Se você está escrevendo software que precisa ser executado na máquina de um usuário remoto em cima de um sistema operacional fechado e com bugs (não menciono nomes), pode haver vantagens em escrever seu aplicativo na mesma linguagem do sistema operacional. Mas se você controla todo o sistema e tem o código-fonte de todas as partes, como a ITA provavelmente tem, você pode usar as linguagens que quiser. Se alguma incompatibilidade surgir, você pode corrigi-la sozinho.

Em aplicativos baseados em servidor, você pode se safar usando 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 até ouvimos falar de novas linguagens como Perl e Python. Não estamos ouvindo falar dessas linguagens porque as pessoas estão usando-as para escrever aplicativos Windows, mas porque as pessoas estão usando-as em servidores. E à medida que o software muda para fora da área de trabalho e para os servidores (um futuro ao qual até a Microsoft parece resignada), haverá menos e menos pressão para usar tecnologias intermediárias.

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, está aquém de qualquer coisa que você chamaria de aplicativo. Se uma empresa se considera no ramo de software e está escrevendo um aplicativo que será um de seus produtos, ele provavelmente envolverá vários hackers e levará pelo menos seis meses para ser escrito. Em um projeto desse porte, 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 é um peixe fora d'água. 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 Lisp, então sua empresa provavelmente está 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) hackers que trabalham em linguagens mais avançadas provavelmente são mais inteligentes.

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

Você não pode deixar os ternos tomarem decisões técnicas por você. Será que alarmou alguns potenciais compradores que usamos Lisp? Alguns, levemente, 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 capital de risco ou potenciais compradores. Projete seu produto para agradar os usuários. Se você conquistar os usuários, tudo mais seguirá. 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 Médio

Quanto você perde ao usar uma linguagem menos poderosa? Na verdade, existem alguns dados por aí sobre isso.

A medida mais conveniente de poder é provavelmente o tamanho do código. O objetivo das linguagens de alto nível é fornecer abstrações maiores - tijolos maiores, por assim dizer, para que você não precise de tantos para construir uma parede de um determinado tamanho. Então, 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 seu aplicativo 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 - na verdade, é assim que a maioria dos algoritmos de compressão funciona. Um programa de baixo para cima também deve ser mais fácil de modificar, 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 do seu comprimento. Se seu programa fosse 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, um prejuízo líquido. Fred Brooks descreveu esse fenômeno em seu famoso livro O Mito do Homem-Mês, 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 para Lisp versus C, por exemplo, ficaram 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 for assim, então podemos confiar nisso; o software da ITA inclui muito C e C++ além de Lisp, então eles estão falando por experiência própria.

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 tirar mais proveito 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 que você. Se você gastasse um ano em um novo recurso, eles seriam capazes de duplicá-lo em menos de três semanas. Enquanto que se eles gastassem apenas três meses desenvolvendo algo novo, levaria cinco anos para você também ter isso.

E sabe de uma coisa? Esse é o cenário ideal. Quando você fala sobre proporções de tamanho de código, você está implicitamente assumindo que você realmente pode 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 muito para manter na 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. Na verdade, da forma como as coisas funcionam na maioria das empresas, qualquer projeto de desenvolvimento que levasse cinco anos provavelmente nunca seria concluído.

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

Uma Receita

Este é o tipo de possibilidade que o chefe de cabelos espetados nem quer pensar. E a maioria deles não pensa. Porque, você sabe, no final das contas, o chefe de cabelos espetados não se importa se sua empresa leva um chute no traseiro, 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 objetivo é 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 esse termo foi originalmente usado para descrever métodos de contabilidade e assim por diante. O que significa, aproximadamente, é não faça nada estranho. E na contabilidade, essa provavelmente é uma boa ideia. Os termos "de ponta" e "contabilidade" não soam bem juntos. Mas quando você importa esse critério para decisões sobre tecnologia, você começa a obter respostas erradas.

A tecnologia muitas vezes deve ser de ponta. Em linguagens de programação, como Erann Gat apontou, o que "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 nome errado.

Então, aqui temos duas informações que acho muito valiosas. Na verdade, sei disso por experiência própria. Número 1, as linguagens variam em poder. Número 2, a maioria dos gerentes ignora isso deliberadamente. Entre eles, esses dois fatos são literalmente uma receita para ganhar dinheiro. 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 puder encontrar, usar a linguagem mais poderosa que puder obter e esperar que os chefes de cabelos espetados de seus concorrentes voltem à média.

Apêndice: Poder

Como 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 é um pouco mais longo do 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ê tem que criar uma nova variável s.

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


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

(Para ser justo, Perl também mantém essa distinção, mas lida com ela da 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 Python não suporta totalmente variáveis ​​lexicais, você tem que criar uma estrutura de dados para armazenar o valor de n. E embora Python tenha um tipo de dados 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 um nomeado função 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 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 farão isso um dia. (Mas se eles não quiserem esperar que o Python evolua para o resto do caminho para o Lisp, eles sempre poderiam apenas...)

Em linguagens OO, você pode, em certa medida, simular um fechamento (uma função que se refere a variáveis ​​definidas em escopos de fechamento) definindo uma classe com um método e um campo para substituir cada variável de um fechamento escopo. Isso faz com que o programador faça o tipo de código análise 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 é o suficiente em casos simples como este.

Os especialistas em Python parecem concordar que esta é a maneira preferida de resolver o problema em Python, escrevendo ou


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 não quero que o Python defensores para dizer que eu estava deturpando a linguagem, mas ambos me parecem mais complexos do que o primeiro versão. Você está fazendo a mesma coisa, configurando um lugar separado para armazenar o acumulador; é apenas um campo em um objeto em vez da cabeça de uma lista. E o uso desses especiais, nomes de campo reservados, especialmente call, parece um pouco de um hack.

Na rivalidade entre Perl e Python, a alegação do hackers do Python parece ser que que Python é uma alternativa mais elegante ao Perl, mas o que este caso mostra é que o poder é a elegância final: 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ê realmente pode resolver este 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 fica aquém da especificação porque só funciona para inteiros. Após muitas trocas de e-mail com hackers do Java, Eu diria que escrever uma versão devidamente polimórfica que se comporta como os exemplos anteriores está em algum lugar entre muito estranho e impossível. Se alguém quiser escrever um, eu ficaria muito curioso para ver, mas eu pessoalmente estou com o tempo esgotado.

Não é literalmente verdade que você não pode resolver isso 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 um deles. Então, como você faria isso? No caso limite, escrevendo um Lisp intérprete na linguagem menos poderosa.

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

Qualquer programa C ou Fortran suficientemente complicado contém um ad hoc implementação lenta e com erros informalmente especificada de metade de 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 intérprete de fato para um, ou (c) você mesmo se tornar um compilador humano para um. Nós já vemos isso começando a acontecer no exemplo Python, onde estamos na verdade, 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ências 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 o expansões de alguma macro que preciso escrever.

Notas

A CPU IBM 704 tinha aproximadamente o tamanho de uma geladeira, mas muito mais pesada. A 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 (digital) computador jogo, Spacewar, em 1962.

Se você quiser enganar um chefe de cabelos espetados para que ele deixe você escrever software em Lisp, você pode 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 "melhor prática da indústria" na JPL me inspirou a abordar esta 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 às muitas pessoas que responderam às 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 são culpados 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 têm levantado: Re: Revenge of the Nerds.

Também desencadeou uma discussão extensa e muitas vezes útil sobre o LL1 lista de discussão. Veja em particular 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.

Um conjunto maior de implementações canônicas do benchmark do gerador de acumulador são coletadas juntas em sua própria página.

Tradução japonesa, espanhol Tradução, Tradução chinesa