Explore como a abordagem simbólica de John McCarthy e as ideias de design do Lisp — listas, recursão e coleta de lixo — influenciaram a IA e a programação moderna.

Isto não é um passeio de museu sobre “velha IA”. É uma lição de história prática para quem constrói software — programadores, líderes técnicos e responsáveis por produto — porque as ideias de John McCarthy moldaram como pensamos sobre para que linguagens de programação servem.
Lisp não foi apenas uma nova sintaxe. Foi uma aposta de que o software poderia manipular ideias (não apenas números) e que escolhas de design de linguagem poderiam acelerar pesquisa, iteração de produto e ecossistemas inteiros de ferramentas.
Uma forma útil de ler o legado de McCarthy é como uma pergunta que ainda importa hoje: com que direta podemos transformar intenção em um sistema executável — sem nos afogarmos em boilerplate, atrito ou complexidade acidental? Essa pergunta ecoa desde o REPL do Lisp até os fluxos modernos de “chat-para-app”.
John McCarthy é lembrado não só por ajudar a lançar a IA como campo de pesquisa, mas por insistir em um tipo específico de IA: sistemas que pudessem manipular ideias, não apenas calcular respostas. Em meados dos anos 1950, ele organizou o Dartmouth Summer Research Project (onde o termo “inteligência artificial” foi proposto) e seguiu influenciando trabalhos no MIT e depois em Stanford. Mas sua contribuição mais duradoura pode ser a pergunta que ele continuava a fazer: e se o próprio raciocínio pudesse ser expresso como um programa?
A maioria dos primeiros sucessos da computação foi numérica: tabelas balísticas, simulações de engenharia, otimização e estatística. Esses problemas se encaixam naturalmente na aritmética.
McCarthy mirou em algo diferente. O raciocínio humano frequentemente lida com conceitos como “se”, “porque”, “pertence a”, “é um tipo de” e “todas as coisas que cumprem estas condições”. Esses conceitos não se representam naturalmente como valores de ponto flutuante.
A abordagem de McCarthy tratou o conhecimento como símbolos (nomes, relações, categorias) e o pensamento como transformações baseadas em regras sobre esses símbolos.
Uma maneira de visualizar em alto nível: abordagens numéricas respondem “quanto?” enquanto abordagens simbólicas tentam responder “o que é?” e “o que se segue do que sabemos?”.
Uma vez que você acredita que o raciocínio pode ser programável, precisa de uma linguagem que represente confortavelmente expressões como regras, sentenças lógicas e relações aninhadas — e depois as processe.
Lisp foi construído para esse objetivo. Em vez de forçar ideias em estruturas de dados rígidas e pré-definidas, Lisp tornou natural representar código e conhecimento numa forma similar. Essa escolha não foi estilismo acadêmico — foi uma ponte prática entre descrever um pensamento e executar um procedimento, exatamente a ponte que McCarthy queria que a IA atravessasse.
Quando McCarthy e os primeiros pesquisadores de IA diziam “simbólico”, não se referiam a matemática misteriosa. Um símbolo é simplesmente um rótulo com significado: um nome como cliente, uma palavra como faminto, ou uma tag como IF e THEN. Símbolos importam porque permitem que um programa trabalhe com ideias (categorias, relações, regras) em vez de apenas números crus.
Uma forma simples de visualizar: planilhas são ótimas quando seu mundo é colunas e aritmética. Sistemas simbólicos são ótimos quando seu mundo é regras, categorias, exceções e estrutura.
Em muitos programas, a diferença entre 42 e "idade" não é o tipo de dado — é o que o valor representa. Um símbolo dá algo que você pode comparar, armazenar e combinar sem perder significado.
Isso torna natural representar coisas como “Paris é uma cidade” ou “se a bateria está baixa, encontre um carregador”.
Para fazer algo útil com símbolos, você precisa de estrutura. O Lisp popularizou uma estrutura muito simples: a lista. Uma lista é apenas um grupo ordenado de itens, e esses itens podem, por sua vez, ser listas. Com essa única ideia você pode representar sentenças, formulários e conhecimento em forma de árvore.
Aqui está um pequeno exemplo conceitual (mostrado em estilo Lisp):
(sentence (subject robot) (verb needs) (object power))
Lê quase como inglês: uma sentença feita de sujeito, verbo e objeto. Por ser estruturada, um programa pode extrair (subject robot) ou substituir (object power) por outra coisa.
Uma vez que a informação está em estruturas simbólicas, tarefas clássicas de IA tornam-se abordáveis:
IF um padrão casa, THEN conclua algo novo.A mudança-chave é que o programa não está apenas calculando; está manipulando pedaços significativos de conhecimento numa forma que pode inspecionar e transformar.
As decisões de design do Lisp não ficaram apenas na academia. Elas influenciaram como as pessoas construíram ferramentas e a velocidade com que podiam explorar ideias:
Essas características tendem a produzir ecossistemas onde experimentação é barata, protótipos viram produtos mais rapidamente e equipes se adaptam quando os requisitos mudam.
Lisp começou com um problema de design muito prático: como escrever programas que trabalham com símbolos tão naturalmente quanto trabalham com números?
McCarthy não buscava construir uma “calculadora melhor”. Ele queria uma linguagem onde uma expressão como (is (parent Alice Bob)) pudesse ser armazenada, inspecionada, transformada e raciocinada tão facilmente quanto (+ 2 3).
A prioridade foi tornar a informação simbólica fácil de representar e manipular. Isso levou a um foco em listas e estruturas em árvore, porque elas mapeiam bem para coisas que humanos usam para expressar significado: sentenças, regras lógicas, categorias aninhadas e relações.
Outro objetivo foi manter o núcleo da linguagem pequeno e consistente. Quando uma linguagem tem menos “casos especiais”, você gasta menos tempo memorizando regras e mais tempo compondo ideias. Lisp abraçou um conjunto reduzido de blocos que podiam ser combinados em abstrações maiores.
Uma visão-chave foi que programas e dados podem compartilhar a mesma estrutura. Simplificando: se seus dados são uma lista aninhada, seu programa pode ser uma lista aninhada também.
Isso significa que você pode:
Lisp também popularizou uma mentalidade: linguagens não precisam ser “tamanho único”. Podem ser projetadas em torno de um domínio de problema — como raciocínio, busca e representação de conhecimento — e ainda assim influenciar a programação de uso geral por décadas.
S-expressions (abreviação de symbolic expressions) são a ideia assinatura do Lisp: uma forma única e consistente de representar código e dados como listas aninhadas.
À primeira vista, uma S-expression é apenas parênteses ao redor de itens — alguns itens são átomos (como nomes e números) e alguns itens são listas. A regra “listas dentro de listas” é o ponto central.
Porque a estrutura é uniforme, programas Lisp são construídos dos mesmos blocos até o fim. Uma chamada de função, um pedaço parecido com configuração e um trecho de estrutura de programa podem todos ser expressos como uma lista.
Essa consistência traz benefícios imediatos:
Mesmo que você nunca escreva em Lisp, esta é uma lição de design importante: quando um sistema é construído a partir de uma ou duas formas previsíveis, você gasta menos tempo lutando contra casos extremos e mais tempo construindo.
S-expressions incentivam composição porque pedaços pequenos e legíveis se combinam naturalmente em outros maiores. Quando seu programa é “apenas listas aninhadas”, combinar ideias muitas vezes significa aninhar uma expressão dentro de outra ou montar listas a partir de partes reutilizáveis.
Isso puxa você para um estilo modular: escreva operações pequenas que façam uma coisa e empilhe-as para expressar uma intenção maior.
A desvantagem óbvia é a estranheza. Para muitos novatos, a sintaxe com muitos parênteses parece estranha.
Mas a vantagem é previsibilidade: uma vez que você entende as regras de aninhamento, consegue ver de forma confiável a estrutura de um programa — e ferramentas também. Essa clareza é uma grande razão pela qual as S-expressions tiveram consequências muito além do próprio Lisp.
Recursão é mais fácil de entender com uma metáfora do dia a dia: arrumar um quarto bagunçado fazendo quartos menores a partir dele. Você não tenta resolver tudo de uma vez. Pega um item, coloca onde deve, e repete a mesma ação no que resta. Os passos são simples; o poder vem de repeti-los até não sobrar nada para fazer.
Lisp abraça essa ideia porque grande parte de seus dados é naturalmente construída a partir de listas: uma lista tem “o primeiro elemento” e “o restante”. Essa forma se encaixa perfeitamente no pensamento recursivo.
Para processar uma lista, você trata o primeiro elemento e depois aplica a mesma lógica ao resto da lista. Quando a lista está vazia, você para — esse é o momento claro de “nada mais a fazer” que torna a recursão bem definida em vez de misteriosa.
Imagine que você quer a soma de uma lista de números.
Pronto. A definição lê como inglês, e a estrutura do programa espelha a ideia.
A IA simbólica costuma representar expressões como estruturas em árvore (um operador com subexpressões). Recursão é uma forma natural de “percorrer” essa árvore: avalie a parte esquerda do mesmo jeito que a direita e continue até alcançar um valor simples.
Esses padrões ajudaram a moldar programação funcional posterior: funções pequenas, casos-base claros e transformações de dados fáceis de raciocinar. Mesmo fora do Lisp, o hábito de dividir o trabalho em “faça um passo, depois repita no restante” leva a programas mais limpos e menos efeitos colaterais ocultos.
Programadores antigos frequentemente precisavam gerenciar memória manualmente: alocar espaço, rastrear quem “possui” e lembrar de liberar no momento certo. Esse trabalho não só desacelera o desenvolvimento — cria uma classe de bugs difícil de reproduzir e fácil de enviar: vazamentos que degradam desempenho e ponteiros pendentes que derrubam um programa muito depois do erro original.
John McCarthy introduziu a coleta de lixo para o Lisp como uma forma de deixar o programador focar no significado em vez da burocracia.
Em alto nível, a coleta de lixo (GC) encontra automaticamente pedaços de memória que não são mais alcançáveis pelo programa em execução — valores que nada poderá usar novamente — e recupera esse espaço.
Em vez de perguntar “liberamos cada objeto exatamente uma vez?”, o GC muda a pergunta para “este objeto ainda é acessível?”. Se o programa não pode alcançá-lo, ele é considerado lixo.
Para trabalhos de IA simbólica, programas Lisp frequentemente criam muitas listas, árvores e resultados intermediários de curta duração. Gerenciar memória manualmente transformaria experimentação em uma batalha constante contra limpeza de recursos.
O GC muda a experiência do dia a dia:
A ideia chave é que um recurso de linguagem pode ser um multiplicador de equipe: menos horas gastas debugando corrupção misteriosa significam mais tempo para melhorar a lógica real.
A escolha de McCarthy não ficou só no Lisp. Muitos sistemas posteriores adotaram GC (e variações dele) porque o trade-off frequentemente compensa: Java, C#, Python, runtimes JavaScript e Go dependem de coleta de lixo para tornar o desenvolvimento em larga escala mais seguro e rápido — mesmo quando desempenho é prioridade.
Em Lisp, uma expressão é um pedaço de código escrito numa forma consistente (frequentemente uma lista). Avaliação é simplesmente o processo de decidir o que essa expressão significa e o que ela produz.
Por exemplo, quando você escreve algo como “adicione estes números” ou “chame esta função com estes inputs”, o avaliador segue um pequeno conjunto de regras para transformar essa expressão em um resultado. Pense nele como o árbitro da linguagem: decide o que fazer a seguir, em que ordem e quando parar.
A jogada-chave de McCarthy não foi só inventar uma nova sintaxe — foi manter o “motor de significado” compacto e regular. Quando o avaliador é construído a partir de algumas regras claras, duas coisas boas acontecem:
Essa consistência é uma razão pela qual Lisp virou um playground de ideias em IA simbólica: pesquisadores podiam testar novas representações e estruturas de controle rapidamente, sem esperar a equipe do compilador redesenhar a linguagem.
Macros são a forma do Lisp de automatizar formas repetitivas de estrutura de código, não apenas valores repetitivos. Onde uma função evita repetir cálculos, uma macro evita repetir estruturas — padrões comuns como “faça X, mas também registre”, ou “defina uma mini-linguagem para regras”.
O efeito prático é que Lisp pode crescer novas conveniências de dentro. Muitas ferramentas modernas ecoam essa ideia — sistemas de templates, geradores de código e recursos de metaprogramação — porque apoiam o mesmo objetivo: experimentação mais rápida e intenção mais clara.
Se você estiver curioso sobre como essa mentalidade influenciou fluxos de trabalho cotidianos, veja /blog/the-repl-and-fast-feedback-loops.
Uma grande parte do apelo do Lisp não foi apenas a linguagem — foi como você trabalhava com ela. Lisp popularizou o REPL: Read–Eval–Print Loop. Em termos práticos, é como ter uma conversa com o computador. Você digita uma expressão, o sistema a executa imediatamente, imprime o resultado e espera sua próxima entrada.
Em vez de escrever um programa inteiro, compilá-lo, executá-lo e então caçar o que quebrou, você pode testar ideias um pequeno passo por vez. Pode definir uma função, testá-la com alguns inputs, ajustá-la e testar de novo — tudo em segundos.
Esse ritmo incentiva experimentação, o que foi crucial para o trabalho inicial em IA onde muitas vezes você não sabia a abordagem correta de antemão.
Feedback rápido transforma “grandes apostas” em “cheques pequenos”. Para pesquisa, facilita explorar hipóteses e inspecionar resultados intermediários.
Para prototipagem de produto, reduz o custo da iteração: você valida comportamento com dados reais rapidamente, percebe casos de borda mais cedo e refina sem esperar longos ciclos de build.
Isto também explica por que ferramentas modernas de vibe-coding são atraentes: essencialmente comprimem o laço de feedback. Por exemplo, Koder.ai usa uma interface de chat (com arquitetura baseada em agentes por trás) para transformar intenção de produto em código web, backend ou mobile funcionando rapidamente — muitas vezes fazendo o ciclo “tentar → ajustar → tentar de novo” parecer mais próximo de um REPL do que de um pipeline tradicional.
A ideia de REPL aparece hoje em:
Diferentes ferramentas, mesmo princípio: encurtar a distância entre pensar e ver.
Equipes se beneficiam mais de fluxos REPL-like quando estão explorando requisitos incertos, construindo funcionalidades pesadas em dados, desenhando APIs ou depurando lógica complicada. Se o trabalho envolve aprendizado rápido — sobre usuários, dados ou casos extremos — feedback interativo não é luxo; é multiplicador.
Lisp não “venceu” tornando-se a sintaxe diária de todo mundo. Venceu plantando ideias que se tornaram normais em muitos ecossistemas.
Conceitos que o Lisp tratava como padrão — funções como valores, uso intenso de operações de ordem superior e preferência por construir programas compondo pequenas partes — aparecem amplamente hoje. Mesmo linguagens que não lembram Lisp encorajam transformações map/filter, hábitos de imutabilidade e pensamento recursivo (às vezes via iteradores ou folds).
A mudança mental é: trate transformações de dados como pipelines e comportamento como algo que você pode passar por aí.
Lisp tornou fácil representar programas como dados. Essa mentalidade aparece hoje em como construímos e manipulamos ASTs (árvores sintáticas abstratas) para compiladores, formatadores, linters e code generators. Quando você trabalha com uma AST, está fazendo um primo próximo de “código como dados”, mesmo se as estruturas forem objetos JSON, nós tipados ou grafos de bytecode.
A mesma abordagem simbólica alimenta automação prática: formatos de configuração, sistemas de template e pipelines de build dependem de representações estruturadas que ferramentas podem inspecionar, transformar e validar.
Linguagens da família Lisp (num sentido amplo: Lisps contemporâneos e ferramentas inspiradas em Lisp) continuam influenciando como times desenham DSLs internas — mini-linguagens focadas para testes, deploy, manipulação de dados ou UI.
Fora do Lisp, sistemas de macros, bibliotecas de metaprogramação e frameworks de geração de código visam o mesmo resultado: estender a linguagem para caber o problema.
Uma conclusão prática: preferências de sintaxe mudam, mas as ideias duráveis — estrutura simbólica, funções compostas e extensibilidade — continuam rendendo frutos por décadas e bases de código.
Lisp tem uma reputação que oscila entre “brilhante” e “ilegível”, frequentemente baseada em impressões de segunda mão em vez da experiência do dia a dia. A verdade é mais mundana: Lisp faz escolhas que são poderosas no contexto certo e inconvenientes em outros.
Para novatos, a sintaxe uniforme do Lisp pode parecer que você está olhando o “interior” de um programa em vez da superfície polida. Esse desconforto é real, especialmente se você está acostumado com linguagens onde a sintaxe separa visualmente construtos.
Historicamente, porém, a estrutura do Lisp é também o ponto: código e dados compartilham a mesma forma, o que torna programas mais fáceis de transformar, gerar e analisar. Com bom suporte de editor (indentação, navegação estrutural), código Lisp costuma ser lido pela forma, não pela contagem de parênteses.
Um estereótipo comum é que Lisp é intrinsecamente lento. Historicamente, algumas implementações realmente ficavam atrás de linguagens de baixo nível, e recursos dinâmicos podem acrescentar overhead.
Mas não é preciso tratar “Lisp” como um único perfil de desempenho. Muitas implementações possuem compilação, declarações de tipo e otimizações sérias. A abordagem mais útil é perguntar: quanta controle você precisa sobre layout de memória, latência previsível ou vazões brutas — e a implementação de Lisp em questão atende a essas necessidades?
Outra crítica justa é encaixe de ecossistema. Dependendo do dialeto Lisp e do domínio, bibliotecas, ferramentas e pools de contratação podem ser menores que stacks mainstream. Isso pode importar mais que a elegância da linguagem se você precisa entregar rapidamente com uma equipe ampla.
Em vez de julgar Lisp por estereótipos, avalie suas ideias subjacentes independentemente: estrutura uniforme, desenvolvimento interativo e macros como ferramenta para construir abstrações de domínio. Mesmo que você nunca entregue um sistema em Lisp, esses conceitos podem afiar como você pensa sobre design de linguagem — e como escreve código em qualquer linguagem.
McCarthy não nos deixou apenas uma linguagem histórica — deixou um conjunto de hábitos que ainda tornam software mais fácil de mudar, explicar e estender.
Prefira núcleos simples a superfícies engenhosas. Um pequeno conjunto de blocos ortogonais é mais fácil de aprender e mais difícil de quebrar.
Mantenha formas de dados uniformes. Quando muitas coisas compartilham a mesma representação (como listas/árvores), ferramentas ficam mais simples: printers, debuggers, serializadores e transformadores podem ser reutilizados.
Trate programas como dados (e dados como programas) quando ajudar. Se você pode inspecionar e transformar estruturas, consegue construir refactors, migrações e geradores de código mais seguros.
Automatize o trabalho chato. Coleta de lixo é o exemplo clássico, mas o ponto mais amplo é: invista em automação que previna classes inteiras de erros.
Otimize para laços de feedback. Avaliação interativa (estilo REPL) incentiva pequenos experimentos, verificação rápida e melhor intuição sobre comportamento.
Faça da extensão um objetivo de design de primeira classe. Macros do Lisp são uma resposta; em outros ecossistemas isso pode ser plugins, templates, DSLs ou transformações em tempo de compilação.
Antes de escolher uma biblioteca ou arquitetura, pegue uma funcionalidade real — digamos “regras de desconto” ou “roteamento de tickets de suporte” — e desenhe-a como árvore. Depois reescreva essa árvore como listas aninhadas (ou JSON). Pergunte: quais são os nós, quais são as folhas, e que transformações você precisa?
Mesmo sem usar Lisp, você pode adotar a mentalidade: construa representações tipo AST, use geração de código para cola repetitiva e padronize pipelines baseados em dados (parse → transformar → avaliar). Muitos times colhem os benefícios apenas tornando representações intermediárias explícitas.
Se você gosta do princípio REPL mas entrega features em stacks mainstream, também pode pegar o espírito nas ferramentas: laços de iteração apertados, snapshots/rollback e planejamento explícito antes da execução. Koder.ai, por exemplo, inclui modo de planejamento mais snapshots e rollback para manter a iteração rápida mais segura — um eco operacional do “mudar rápido, mas manter controle” do Lisp.
A influência duradoura de McCarthy é esta: programação fica mais poderosa quando tornamos o raciocínio em si programável — e mantemos o caminho da ideia ao sistema executável o mais direto possível.
O pensamento simbólico representa conceitos e relações diretamente (por exemplo, “cliente”, “é-um”, “depende-de”, “se…então…”), e então aplica regras e transformações sobre essas representações.
É mais útil quando seu problema está cheio de estrutura, exceções e significado (motores de regras, planejamento, compiladores, configuração, lógica de fluxo de trabalho), e não apenas de aritmética.
McCarthy defendeu que o raciocínio pode ser expresso como programas — não apenas cálculos.
Essa perspectiva moldou:
Listas são uma maneira mínima e flexível de representar “coisas feitas de partes”. Como elementos de uma lista podem ser listas, você obtém naturalmente estruturas em árvore.
Isso facilita:
S-expressions dão uma forma uniforme para código e dados: listas aninhadas.
Essa uniformidade tende a simplificar sistemas porque:
Uma macro automatiza padrões de estrutura de código, não apenas cálculos repetitivos.
Use macros quando você quer:
Se você só precisa de lógica reaproveitável, uma função geralmente é a escolha correta.
A coleta de lixo (GC) recupera automaticamente memória que não é mais acessível, reduzindo categorias inteiras de bugs (ponteiros pendentes, liberações duplicadas).
É especialmente útil quando seu programa cria muitas estruturas de curta duração (listas/árvores/ASTs), porque permite prototipar e refatorar sem projetar um esquema manual de propriedade de memória desde o início.
Um REPL encurta o ciclo “pensar → tentar → observar”. Você pode definir uma função, executá-la, ajustá-la e executar novamente imediatamente.
Para adotar o mesmo benefício em stacks não-Lisp:
Leitura relacionada: /blog/the-repl-and-fast-feedback-loops
Muitas práticas modernas reutilizam as mesmas ideias subjacentes:
map/filter, composição)Mesmo que você nunca entregue um sistema em Lisp, provavelmente usa hábitos que descendem dessa tradição no dia a dia.
Principais trade-offs e equívocos:
A abordagem prática é avaliar compatibilidade com domínio e restrições, não reputação.
Experimente este exercício de 10 minutos:
Frequentemente isso revela onde padrões “código-como-dados”, motores de regras ou configs tipo DSL simplificam o sistema.