A VINGANÇA DOS NERDS
OriginalMaio de 2002
"Estávamos atrás dos programadores C++. Conseguimos arrastar muitos deles até a metade do caminho para Lisp."
- Guy Steele, coautor 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 cabelo pontiagudo. Todo mundo sabe quem é o chefe de cabelo pontiagudo, 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 na qual ele é modelado.
O chefe de cabelos espetados 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 ela.
Suponha, por exemplo, que você precise escrever um software. O chefe de cabelos espetados não tem ideia de como esse software tem que funcionar, e não consegue diferenciar uma linguagem de programação da 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 do cérebro do chefe de cabelo espetado. O que ele está pensando é algo assim. Java é um padrão. Eu sei que deve ser, porque leio sobre isso na imprensa o tempo todo. Como é um padrão, não terei 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 os programadores que trabalham para mim misteriosamente sempre fazem, posso facilmente substituí-los.
Bem, isso não parece tão irracional. Mas é tudo baseado em uma suposição tácita, 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 todas as linguagens são equivalentes, claro, use qualquer linguagem que todos os outros estejam usando.
Mas todas as linguagens não 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 deve ser escrito em C++. Mas se todas as linguagens são equivalentes, por que a opinião do chefe de cabelo espetado deveria mudar? Na verdade, por que os desenvolvedores do Java deveriam ter se incomodado em criar uma nova linguagem?
Presumivelmente, se você cria uma nova linguagem, é porque você acha que ela é melhor de alguma forma do que as pessoas já tinham. E, de fato, Gosling deixa claro no primeiro white paper do Java que o 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 através do cérebro do chefe de cabelo espetado até o Java e depois voltar 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 cabelo espetado? Não é de surpreender que Gosling esteja 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 lata de minhocas. Se o chefe de cabelo espetado tivesse que pensar sobre o problema em sua complexidade total, isso faria seu cérebro explodir. Enquanto ele considera todas as linguagens equivalentes, tudo o que ele tem a fazer é escolher aquela que parece ter mais força, 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 sobre as quais 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 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 eu 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 nova e bacana linguagem de programação. Ou não é? Se você olhar para o mundo das linguagens de programação de longe, parece que Java é a última moda. (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, verá que há graus de bacana. Dentro da subcultura hacker, há outra linguagem chamada Perl que é considerada muito mais bacana que Java. O Slashdot, por exemplo, é gerado pelo Perl. Não acho que você encontraria esses caras usando Java Server Pages. Mas há outra linguagem mais nova, chamada Python, cujos usuários tendem a menosprezar o Perl, e mais esperando nos bastidores.
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 Lisp. Cada uma é progressivamente mais parecida com Lisp. Python copia até mesmo recursos que muitos hackers Lisp consideram erros. Você poderia traduzir programas Lisp simples para Python linha por linha. Estamos em 2002, e as linguagens de programação quase alcançaram 1958.
Recuperando o atraso com a matemática
O que quero dizer é que o Lisp foi descoberto por John McCarthy em 1958, e as linguagens de programação populares só agora estão acompanhando 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 gigantes do tamanho de uma geladeira com o poder de processamento de um relógio de pulso. Como uma tecnologia tão antiga poderia ser relevante, muito menos superior aos últimos desenvolvimentos?
Vou lhe dizer como. É porque o Lisp não foi 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 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 para a Máquina de Turing. Como McCarthy disse mais tarde,
Outra maneira de mostrar que Lisp era mais organizado que máquinas de Turing era escrever uma função Lisp universal e mostrar que ela é mais breve e mais compreensível que a descrição de uma máquina de Turing universal. Esta era 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 concebida para os propósitos do artigo sem pensar que seria usada para expressar programas Lisp na prática.
O que aconteceu depois foi que, em algum momento no final de 1958, Steve Russell, um dos alunos de pós-graduação de McCarthy, olhou para esta definição de eval e percebeu que se ele 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 é para leitura, não para computação. Mas ele foi em frente e fez. Ou seja, ele compilou o eval no meu artigo em código de máquina [IBM] 704, corrigindo bugs, e então anunciou isso como um interpretador Lisp, o que certamente era. Então naquele ponto o Lisp tinha essencialmente a forma que tem hoje...
De repente, em questão de semanas, creio eu, McCarthy viu seu exercício teórico transformado em uma linguagem de programação real — e mais poderosa do que ele pretendia.
Então, a explicação curta de por que essa linguagem dos anos 1950 não é obsoleta é que ela não era tecnologia, mas matemática, e matemática não fica obsoleta. A coisa certa para comparar Lisp não é hardware dos anos 1950, mas, digamos, o algoritmo Quicksort, que foi descoberto em 1960 e ainda é a classificação de propósito geral mais rápida.
Há uma outra linguagem ainda sobrevivente da década de 1950, Fortran, e ela representa a abordagem oposta ao design de linguagem. Lisp era um pedaço de teoria que inesperadamente se transformou em uma linguagem de programação. Fortran foi desenvolvida intencionalmente como uma linguagem de programação, mas o que hoje 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 basicamente uma linguagem assembly com matemática. De certa forma, era menos poderosa do que as linguagens assembly mais recentes; não havia subrotinas, por exemplo, apenas ramificações. O Fortran atual está agora indiscutivelmente mais próximo do Lisp do que do Fortran I.
Lisp e Fortran eram os troncos de duas árvores evolucionárias separadas, uma enraizada na matemática e outra enraizada na arquitetura de máquinas. Essas duas árvores têm convergido desde então. Lisp começou poderoso e, nos vinte anos seguintes, tornou-se rápido. As chamadas linguagens tradicionais começaram rápido e, nos quarenta anos seguintes, gradualmente se tornaram mais poderosas, até que agora as mais avançadas delas estão bem próximas do Lisp. Perto, mas ainda faltam algumas coisas...
O que tornou o Lisp diferente
Quando foi desenvolvido pela primeira vez, o Lisp incorporou nove novas ideias. Algumas delas nós agora tomamos como certas, outras são vistas apenas em linguagens mais avançadas, e duas ainda são exclusivas do Lisp. As nove ideias são, em ordem de adoção pelo mainstream,
Condicionais. Um condicional é uma construção if-then-else. Nós os tomamos como garantidos agora, mas o Fortran I não os tinha. Ele tinha apenas um goto condicional baseado de perto 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 tem 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 Fortran e a maioria das linguagens subsequentes, que distinguem entre expressões e instruções.
Era natural ter essa distinção em Fortran I porque você não podia aninhar instruções. E então, 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 já 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. 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 toda está lá o tempo todo. Não há 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-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 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 no mainstream. Python tem uma forma de 7, embora não pareça haver nenhuma sintaxe para ele.
Quanto ao número 8, este 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 pretendeu 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 distintas. 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 sintática 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 soar como uma ideia bizarra, mas é uma coisa cotidiana em 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 Lisp, ou apenas expandir seus horizontes de programação, eu aprenderia 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 tem que fazer sua linguagem parecer tão estranha quanto Lisp. Também pode ser porque, se você adicionar esse incremento final de poder, não poderá mais alegar 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 disso. Essa é de fato a qualidade definidora do Lisp: foi para fazer isso que McCarthy deu ao Lisp a forma que ele tem.
Onde as línguas importam
Então suponha que Lisp represente um tipo de limite que as linguagens tradicionais estão abordando assintoticamente-- isso significa que você deve realmente usá-lo para escrever software? Quanto você perde usando uma linguagem menos poderosa? Não é mais sensato, à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 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 importa muito. Como regra, quanto mais exigente o aplicativo, mais alavancagem você obtém ao usar uma linguagem poderosa. Mas muitos projetos não são nem um pouco exigentes. A maioria 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 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 a maior vitória 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 busca de tarifas aéreas que a ITA Software licencia para a Orbitz. Esses caras entraram em um mercado já dominado por dois grandes e entrincheirados concorrentes, 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 busca muitas ordens de magnitude a mais de possibilidades do que seus concorrentes, que aparentemente ainda usam técnicas de programação da era do mainframe. (Embora o ITA também esteja, de certa forma, usando uma linguagem de programação da era do mainframe.) Nunca vi nenhum código do ITA, mas, de acordo com um de seus principais hackers, eles usam muitas macros, e não estou surpreso em 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 enganado em se preocupar com isso. Mas, como ele não entende os riscos, ele tende a ampliá-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 problemas para contratar programadores.
Quão problemático é cada um deles? A importância do primeiro varia dependendo se você tem controle sobre todo o sistema. Se você está escrevendo um 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 seu aplicativo na mesma linguagem do SO. Mas se você controla todo o sistema e tem o código-fonte de todas as partes, como o ITA presumivelmente tem, você pode usar qualquer linguagem que quiser. Se surgir alguma incompatibilidade, você mesmo pode consertá-la.
Em aplicativos baseados em servidor, você pode se safar usando as tecnologias mais avançadas, e eu 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 falar dessas 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 sai do desktop e vai para os servidores (um futuro ao qual até a Microsoft parece resignada), haverá cada vez menos pressão para usar tecnologias intermediárias.
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á aquém de qualquer coisa que você provavelmente chamaria de aplicativo. Se uma empresa se considera no ramo 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, eu acho que é uma pista falsa. Quantos hackers você precisa contratar, afinal? Certamente agora todos nós 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á sediada na cidade errada para desenvolver software.
Na verdade, escolher uma linguagem mais poderosa provavelmente diminui o tamanho da equipe necessária, 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 serão mais inteligentes.
Não estou dizendo que você não sofrerá muita pressão para usar o que é percebido como tecnologias "padrão". Na Viaweb (agora Yahoo Store), levantamos algumas sobrancelhas entre VCs e potenciais adquirentes usando Lisp. Mas também levantamos sobrancelhas usando caixas Intel genéricas como servidores em vez de servidores de "força industrial" como Suns, por usar uma então obscura variante Unix de código aberto chamada FreeBSD em vez de um sistema operacional comercial real como Windows NT, por ignorar um suposto padrão de e-commerce chamado SET do qual ninguém mais se lembra, e assim por diante.
Você não pode deixar que os ternos tomem decisões técnicas por você. Isso alarmou alguns potenciais compradores que usamos 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ê começar 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á com o quão confortavelmente ortodoxas foram suas escolhas tecnológicas.
O custo de ser mediano
Quanto você perde usando uma linguagem menos poderosa? Na verdade, há alguns dados por aí sobre isso.
A medida mais conveniente de poder é provavelmente o tamanho do código . O ponto das linguagens de alto nível é dar a você 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 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 seu programa inteiro na linguagem base — na verdade, é assim que a maioria dos algoritmos de compressão funcionam. Um programa bottom-up também deve ser mais fácil de modificar, porque em muitos casos a camada de linguagem não precisará mudar nada.
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, levaria 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 tendeu a confirmar o que ele disse.
Então, quanto 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 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 ter alguma fé nisso; o software da ITA inclui muito C e C++, bem como 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 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 que se eles passassem apenas três meses desenvolvendo algo novo, levaria cinco anos até que você também o tivesse.
E sabe de uma coisa? 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, há limites para o que os programadores podem fazer. Se você está tentando resolver um problema difícil com uma linguagem de nível muito baixo, você chega a um ponto em que há muita coisa para manter na 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. Na verdade, do jeito que as coisas funcionam na maioria das empresas, qualquer projeto de desenvolvimento que levaria cinco anos provavelmente nunca será concluído.
Admito que esse é um caso extremo. Os hackers do ITA parecem ser extraordinariamente inteligentes, e C é uma linguagem de nível bem baixo. Mas em um mercado competitivo, mesmo um diferencial de dois ou três para um seria o 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 então a maioria deles não pensa. Porque, você sabe, quando se trata disso, o chefe de cabelo espetado não se importa se sua empresa leva uma surra, desde que ninguém possa provar que é culpa dele. O plano mais seguro para ele pessoalmente é ficar perto do centro do rebanho.
Em 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 seja "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, grosso modo, é não fazer nada estranho. E em contabilidade isso 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 as respostas erradas.
A tecnologia frequentemente deve ser de ponta. 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 a média. Quando uma decisão faz com que você desenvolva software em uma fração da taxa de concorrentes mais agressivos, "melhores práticas" é um nome impróprio.
Então aqui temos duas informações que eu acho muito valiosas. Na verdade, eu sei disso por experiência própria. Número 1, as línguas 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. A ITA é um exemplo dessa receita em ação. Se você quer vencer em um negócio de software, apenas assuma o problema mais difícil que puder encontrar, use a linguagem mais poderosa que puder obter e espere que os chefes de cabelos espetados dos 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 gere acumuladores-- uma função que pega um número n, e retorna uma função que pega 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 ni)))
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 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ê tem que criar uma nova variável s.
Em Javascript, o exemplo é, novamente, um pouco mais longo, porque o Javascript mantém a distinção entre instruções e expressões, então você precisa de instruções return 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 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. Como 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 dado function, 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 que você acaba tendo:
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 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 o Python evoluir o resto do caminho para o Lisp, eles sempre podem simplesmente...)
Em linguagens OO, você pode, até certo ponto, simular um closure (uma função que se refere a variáveis definidas em escopos envolventes) definindo uma classe com um método e um campo para substituir cada variável de um escopo envolvente. 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 léxico, e não funcionará se mais de uma função se referir à mesma variável, mas é o suficiente em casos simples como esse.
Os especialistas em Python parecem concordar que esta é 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 estes porque não gostaria que os defensores do Python dissessem que eu estava deturpando a linguagem, mas ambos me parecem 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 do cabeçalho de uma lista. E o uso desses nomes de campos especiais e reservados, especialmente call , parece um pouco hack.
Na rivalidade entre Perl e Python, a alegação dos hackers do Python parece ser que o Python é uma alternativa mais elegante ao Perl, mas o que este caso mostra é que o poder é a elegância máxima: 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 código a seguir é 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. Depois de muitas trocas de e-mail com hackers Java, eu diria que escrever uma versão polimórfica adequada que se comporte como os exemplos anteriores está em algum lugar entre terrivelmente estranho e impossível. Se alguém quiser escrever uma, eu ficaria muito curioso para ver, mas eu pessoalmente já estou sem tempo.
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 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.
Parece piada, mas acontece com tanta frequência, em graus variados, em grandes projetos de programação que há um nome para o fenômeno: Décima Regra de Greenspun:
Qualquer programa C ou Fortran suficientemente complicado contém uma implementação lenta, ad hoc, informalmente especificada e cheia de bugs, de metade do Common Lisp.
Se você tenta 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) você mesmo se tornará um compilador humano para uma. Vemos isso já começando a acontecer no exemplo do Python, onde estamos efetivamente 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 problema. O formato de um programa deve refletir apenas o problema que ele 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 — frequentemente, de 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 o 4K de RAM estava em uma caixa separada pesando outras 4000 libras. O Sub-Zero 690, um dos maiores refrigeradores domésticos, pesa 656 libras.
Steve Russell também escreveu o primeiro jogo de computador (digital), Spacewar, em 1962.
Se você quiser enganar um chefe de cabelos espetados para deixá-lo escrever software em Lisp, você pode tentar dizer a ele que é XML.
Aqui está o gerador de acumulador em outros dialetos Lisp:
Scheme: (define (foo n) (lambda (i) (set! n (+ ni)) n)) Goo: (df foo (n) (op incf n _))) Arc: (def foo (n) [++ n _])
A triste história de Erann Gat sobre as "melhores práticas do setor" no JPL me inspirou a abordar essa frase geralmente mal aplicada.
Peter Norvig descobriu que 16 dos 23 padrões em Padrões de Design eram " invisíveis ou mais simples " em Lisp.
Obrigado às muitas pessoas que responderam minhas perguntas sobre vários idiomas e/ou leram rascunhos disto, 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 nenhuma opinião expressa.
Relacionado:
Muitas pessoas responderam a esta palestra, então criei uma página adicional para lidar com as questões que elas levantaram: Re: A Vingança dos Nerds .
Também desencadeou uma discussão extensa e frequentemente ú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 no LL1 me levaram a tentar me aprofundar no assunto do poder da linguagem em Sucinctness is Power .
Um conjunto maior de implementações canônicas do benchmark do gerador de acumulador é reunido em sua própria página.
Tradução em japonês ,tradução em espanhol , tradução em chinês