As sete linguagens ur da programação (2022)
(madhadron.com)- As diferenças entre conjuntos de padrões fundamentais são mais importantes do que a gramática individual, e as linguagens de programação se dividem em sete linguagens ur de acordo com repetição, recursão e formas de composição
- ALGOL, Lisp, ML, Self, Forth, APL e Prolog são as classificações centrais, e cada família usa uma linguagem representativa como amostra de referência para determinar a linhagem de outras linguagens
- É fácil aprender uma nova linguagem que compartilha uma linguagem ur já familiar, mas ao migrar para um arquétipo desconhecido é necessário um novo caminho de pensamento e um tempo considerável de aprendizado
- ALGOL se caracteriza pela organização de funções centrada em atribuição, condicionais e laços; Lisp, por macros e código em listas; ML, por funções de primeira classe e recursão; Self, por objetos com passagem de mensagens; Forth, por sintaxe baseada em pilha; APL, por arrays n-dimensionais; e Prolog, por fatos e estrutura de busca
- Para todo programador, dominar primeiro uma linguagem da família ALGOL é prioridade; em seguida, aprender SQL e depois estudar continuamente linguagens ur menos familiares tende a ser vantajoso no longo prazo
As sete linguagens ur da programação
- Ao escolher uma linguagem de programação, é mais importante dominar os padrões fundamentais do que as diferenças de gramática individuais; entre linguagens de famílias parecidas, estruturas básicas como percorrer arrays ou percorrer combinações quase sempre têm forma semelhante
- Famílias de linguagens diferentes variam muito em repetição, recursão e na forma de estruturar programas, e esses conjuntos de padrões fundamentais formam linguagens ur distintas
- Aprender uma nova linguagem que compartilha uma linguagem ur familiar é uma transição relativamente fácil, mas migrar para uma linguagem ur desconhecida exige bastante tempo e novos caminhos de pensamento
- As linguagens ur reconhecidas na área de software são sete: ALGOL, Lisp, ML, Self, Forth, APL e Prolog
- Cada linguagem ur é classificada tomando uma linguagem representativa específica como amostra de referência, e as demais linguagens têm sua linhagem julgada por comparação com essa amostra
-
ALGOL
- Programas são compostos por sequências de atribuições, condicionais e laços, organizadas em unidades de função
- Muitas linguagens acrescentam a isso sistemas de módulos, formas de definir novos tipos de dados, polimorfismo e estruturas alternativas de fluxo de controle como exceções ou corrotinas
- A maior parte das linguagens de programação amplamente usadas hoje pertence a essa família de linguagem ur
- A própria ALGOL inclui ALGOL 58, ALGOL 60, ALGOL W e ALGOL 68
- Assembly language, Fortran, C, C++, Python, Java, C#, Ruby, Pascal, JavaScript e Ada se conectam a essa linhagem
- É a linguagem ur mais antiga, com uma genealogia que remonta à formalização de programas de Ada Lovelace para a máquina analítica de Babbage
- Tanto a linguagem de máquina e assembly dos computadores de arquitetura Eckert-Mauchly, que levaram ao EDVAC e aos primeiros Univac, quanto as primeiras tentativas de linguagens de alto nível, de A-0 de Grace Hopper até Fortran e COBOL, seguem essa forma
- Na academia dos anos 1960, o desenvolvimento da programação estruturada tornou essas linguagens mais administráveis, e o resultado foi ALGOL 60, de onde derivou a maior parte dos membros posteriores da família
- Com o tempo, houve uma tendência de absorver recursos de outras linguagens ur
- Nos anos 1980, conceitos da família Self foram incorporados na forma de classes e usados como meio de implementar definição de tipos de dados e polimorfismo
- Depois de 2010, conceitos da família ML também passaram a aparecer
-
Lisp
- Sintaxe que combina expressões prefixadas entre parênteses e representação em listas
(+ 2 3)(defun square (x) (* x x))(* (square 3) 3)
- Como a representação em listas com itens separados por espaço e envolvidos por parênteses é embutida na linguagem, o próprio código tem forma de lista
- Macros podem receber listas, modificá-las e passar o código alterado ao compilador, criando uma estrutura em que o programador pode redefinir a semântica da linguagem
- Na maior parte da escrita de código, ela tende a funcionar como outras linguagens ur, normalmente ALGOL ou ML, mas o sistema de macros é o diferencial
- A sintaxe
loopdo Common Lisp também não é um recurso embutido da linguagem, mas algo definido por macro - Havia muitas variantes iniciais de Lisp, mas a comunidade acabou chegando a um consenso em torno de Common Lisp
- Sussman e Steele exploraram até onde era possível ir apenas com funções e criaram Scheme
- Há usos de Lisp para fins específicos, como Lush para computação numérica, AutoLISP como linguagem de script do AutoCAD e Emacs Lisp para implementar o comportamento do editor Emacs
- Mais recentemente, Clojure emergiu como o terceiro grande ramo da família Lisp
- Surgiu cerca de um ano depois de Fortran e é a segunda família de linguagens mais antiga ainda em uso hoje
- Seu ponto de partida foi uma questão matemática sobre como representar uma estrutura matemática capaz de avaliar suas próprias expressões
- John McCarthy apresentou a resposta em 1958, e ela depois foi implementada em computadores
- O Lisp inicial, por sua base matemática, não se encaixava bem nas máquinas da época; questões de memória e ciclos de CPU não existiam na matemática, e técnicas como coleta de lixo tornaram-se necessárias
- No fim dos anos 1970 e início dos 1980, houve máquinas projetadas do zero apenas para executar Lisp
- Muitos elementos dos ambientes de desenvolvimento integrados de hoje foram inventados nessas máquinas
- No mesmo período, Lisp era o principal meio da pesquisa em inteligência artificial, e quando a euforia da IA dos anos 1980 não entregou resultados, Lisp também caiu junto com a área no AI Winter
- Ainda assim sobreviveu, e com a melhora do desempenho dos computadores e a incorporação de recursos seus por outras linguagens, as dificuldades de implementação diminuíram
- Sintaxe que combina expressões prefixadas entre parênteses e representação em listas
-
ML
- Funções são valores de primeira classe, e a linguagem possui um sistema de tipos da família Hindley-Milner capaz de expressar várias funções e tagged unions
- Toda repetição é feita por recursão
sum [] = 0sum (x:xs) = x + sum xs
- Também se usa a definição de funções que encapsulam padrões de repetição e implementam o comportamento recebendo outras funções
map _ [] = []map f (x:xs) = (f x) : (map f xs)
- Algumas linguagens, como Miranda e Haskell, usam avaliação preguiçosa por padrão
- Outras expandem o sistema de tipos em várias direções
- OCaml tenta combinar-se com conceitos da linguagem ur Self
- Agda e Idris adotam sistemas de tipos dependentes que misturam valores e tipos
- 1ML combina módulos e tipos
- De ML derivaram CaML, Standard ML e OCaml
- Famílias relacionadas como Miranda, Haskell, Agda e Idris também seguem essa linha
- ML era a metalinguagem de um programa de prova de teoremas desenvolvido em Cambridge, no Reino Unido, e seu nome vem daí
- Depois saiu desse contexto e se espalhou como linguagem independente, ganhando popularidade sobretudo na Europa, especialmente no Reino Unido e na França
-
Self
- Programas são compostos por um conjunto de objetos que trocam mensagens entre si, e todo comportamento é implementado dessa forma
- Novos objetos são criados enviando mensagens a objetos existentes
- Até condicionais são realizadas por meio de uma variável que referencia o objeto true ou o objeto false
- Os dois objetos recebem uma mensagem com, como parâmetros, a função a executar quando for verdadeiro e a função a executar quando for falso
- O objeto true executa a primeira função, e o objeto false executa a segunda
- O código chamador não sabe qual objeto é, apenas envia a mensagem
- Laços funcionam do mesmo modo, e se você criar os objetos corretos e colocá-los nos lugares certos, é possível redefinir toda a semântica da linguagem
- Essas linguagens normalmente armazenam o código-fonte não em arquivos de texto, mas em um ambiente live
- O programador modifica o sistema live e salva esse estado, em vez de compilar arquivos para construir o sistema
- Exemplos importantes são Smalltalk e Self
- Muitas linguagens adotam apenas parcialmente o estilo de passagem de mensagens dessa família, e essa adoção parcial costuma ser chamada de programação orientada a objetos
- A maioria delas é baseada em Smalltalk; a única exceção é JavaScript, derivado do sistema de objetos sem classes de Self
- O sistema de objetos de Common Lisp generaliza isso para que o runtime escolha o código a executar com base não apenas em um objeto receptor de mensagem, mas em todos os parâmetros
- Erlang muda a direção: em vez de o fluxo de execução migrar entre objetos, threads de execução paralelas escutam e enviam mensagens explicitamente
- A linguagem original é Smalltalk, desenvolvida no Xerox Parc no fim dos anos 1970 e nos anos 1980
- Nos anos 1980 houve vários sistemas comerciais de Smalltalk, e a IBM usou Smalltalk no desenvolvimento da coleção de ferramentas de programação VisualAge para outras linguagens
- Hoje, Smalltalk sobrevive principalmente como o open source Pharo Smalltalk
- Houve muita pesquisa para executar Smalltalk de forma rápida e eficiente, e o auge disso foi o projeto Strongtalk
- As descobertas de Strongtalk têm importância histórica por terem servido de base ao compilador JIT HotSpot do Java
- Smalltalk herdou conceitos de valores e tipos de linguagens anteriores para implementar classes; todo objeto tinha uma classe que lhe atribuía um tipo, e a classe criava objetos desse tipo
- Self removeu o conceito de classe e passou a ser composto apenas por objetos
- Por ser uma forma mais pura, Self foi escolhido como amostra de referência dessa linguagem ur
-
Forth
- Linguagens de pilha são como o espelho invertido de Lisp e compartilham a sintaxe das calculadoras de notação polonesa reversa da Hewlett Packard
- Elas têm uma pilha de dados; ao escrever um literal como
42, ele é empilhado, e nomes de funções operam sobre a pilha sem parâmetros explícitos - Até a aritmética simples fica invertida, como em
2 3 + 5 * - A definição de funções também é muito concisa
- Na maioria das variantes de Forth,
:define uma nova palavra squareequivale a chamardupe*dupduplica o topo da pilha, e*multiplica os dois itens do topo
- Na maioria das variantes de Forth,
- É possível interceptar o parser e substituí-lo pelo próprio código, então toda a sintaxe pode ser trocada
- São comuns programas em Forth que definem pequenas linguagens, como subconjuntos de Fortran, layouts de pacotes ou até uma forma de fazer parsing direto de diagramas ASCII que representam transições de máquina de estados
- Inclui várias variantes de Forth, além de PostScript, Factor e Joy
- Joy é uma linguagem funcional pura que usa uma formalização matemática de composição em vez de pilha
- Forth foi escrito pela primeira vez em 1970 para controle de radiotelescópios
- Depois se espalhou amplamente por sistemas embarcados
- Sistemas Forth são fáceis o bastante de fazer bootstrap, e por isso existem dezenas de variantes criadas por programadores para finalidades próprias
- PostScript surgiu nos anos 1980 como uma forma flexível de descrever documentos em impressoras
- Em vários aspectos, PostScript é mais restrito que Forth, mas define na linguagem operações básicas ligadas a layout gráfico
-
APL
- Tudo na linguagem são arrays n-dimensionais
- Operadores são compostos de um ou dois símbolos e executam operações de alto nível sobre arrays inteiros
- As expressões são extremamente compactas, a ponto de a própria sequência de símbolos marcar a operação sem necessidade de dar nomes adicionais
- Por exemplo, calcular a média da variável
xtoma a forma(+⌿÷≢) x - APL, J e K são exemplos representativos
- Operações de ordem superior sobre arrays foram parcialmente exportadas para vários ambientes como MATLAB, NumPy e R
- APL começou como uma notação matemática criada por Kenneth Iverson nos anos 1960 e depois foi implementada em computadores
- Desde então manteve um público de nicho entre pessoas que fazem computação pesada
- Sua linguagem descendente K foi muito popular no ambiente financeiro
-
Prolog
- Programas são compostos por um conjunto de fatos
father(bob, ed).father(bob, jane).
- Também se usam fatos não aterrados que derivam fatos a partir de outros fatos com variáveis
grandfather(X, Y) :- father(X, Z), father(Z, Y).
- O runtime de Prolog recebe esses fatos e consultas e realiza uma busca para encontrar resultados
- Se a estrutura de definição dos fatos for escolhida adequadamente, obtém-se completude de Turing
- Em Prolog, os termos que compõem fatos são em si um tipo de dado próprio, que pode ser criado e passado ao runtime
- Nesse ponto, ele ocupa uma posição semelhante à das macros de Lisp ou da substituição do parser em Forth
- Como programas em Prolog são essencialmente busca, o ajuste de desempenho costuma se concentrar em controlar a ordem da busca e cortar cedo caminhos sem resultado, como em consultas a banco de dados
- Inclui Prolog, Mercury e Kanren
- Na prática, a maior parte da programação dessa família de linguagem ur acontece no próprio Prolog, e a comunidade é altamente unificada
- Nos anos 1970, lógicos franceses perceberam que programas podiam ser expressos em lógica de primeira ordem e começaram a tentar implementá-los
- Nos anos 1980, o projeto japonês de computadores de quinta geração apostou fortemente em Prolog, mas, com o fracasso do projeto, a reputação de Prolog também caiu
- Independentemente disso, durante décadas continuaram as pesquisas para tornar o runtime de Prolog eficiente na maioria dos casos e para acrescentar novos recursos
- Recursos como restrições numéricas foram adicionados, levando à programação lógica com restrições
- Prolog continua aparecendo em nichos
- A checagem de tipos do Java foi implementada em Prolog por vários anos
- A ferramenta inicial de busca em código-fonte do Facebook também era baseada em Prolog
- Programas são compostos por um conjunto de fatos
Como aproveitar isso
- Para a maioria dos programadores, parte ou todas essas famílias de linguagem podem parecer muito estranhas, mas vale investir algum tempo em cada uma pelos caminhos de pensamento e pelas novas possibilidades que elas abrem
- Do ponto de vista de ALGOL, duas coisas podem parecer completamente diferentes, mas sob outra perspectiva muitas vezes são uma comparação trivial
-
Prioridade
- Todo programador deve conhecer bem uma linguagem da família ALGOL
- Em seguida, recomenda-se aprender SQL, uma linguagem da família Prolog
- Ela ocupa o lugar de segunda maior utilidade ao longo da carreira, depois de ALGOL
-
Expansão posterior
- Depois de dominar essas duas famílias, compensa no longo prazo aprender, a cada ano, uma nova linguagem de uma família ur desconhecida
- As linguagens sugeridas em cada família e sua ordem são as seguintes
- Lisp: PLT Racket
- ML: Haskell
- Self: Self
- Prolog: Prolog
- Forth: gForth
- APL: K, via
ok
-
Ajustando a ordem
- Se você faz muita computação numérica, vale aprender K mais cedo
- Se você trabalha muito com programação embarcada, vale aprender gForth mais cedo
- Ainda assim, a ordem em si ou a escolha exata da linguagem não são tão importantes
- Em vez de Haskell, pode-se aprender Standard ML ou OCaml; em vez de PLT Racket, Common Lisp; em vez de gForth, Factor
-
Complementos incluídos nas notas
- Mesmo depois de aprender SQL, ainda é necessário aprender o próprio Prolog
- Porque a forma real de uso é bastante diferente de SQL
- Há também a opinião de um leitor de que, para entender Forth profundamente, uma abordagem comum é implementar você mesmo um runtime de Forth
- Menciona-se que Forth é pequeno o bastante para que uma pessoa consiga implementá-lo do zero em um tempo relativamente curto
- gForth é uma boa implementação para aprender ANS Forth
- Como material de estudo, menciona-se FORTH Fundamentals, Volume 1, de McCabe
- Outros Forths citados para explorar junto são PygmyForth, eForth e colorForth
- Mesmo depois de aprender SQL, ainda é necessário aprender o próprio Prolog
5 comentários
Interessante.
Na época da faculdade, aprendi matérias da área e fiz trabalhos com a família ALGOL, Lisp e Prolog, então isso traz boas lembranças.
Essas linguagens deixaram muita coisa nas linguagens de programação dominantes de hoje,
mas, entre elas, só Forth parece ter tido menos influência.
Mesmo sem chegar à notação prefixa, programar em notação pós-fixa é inconveniente demais.
Comentários do Hacker News
Nas aulas de PL da Tufts, cheguei a implementar versões mínimas, eu mesmo, das quatro primeiras famílias de linguagens antes de imperativa, Lisp, ML, Smalltalk, e foi bom ver que esse processo agora também virou livro-texto. É uma pena que a parte de Prolog, que existia antes, tenha ficado de fora
Se eu fosse corrigir só uma coisa na classificação deste texto, diria que Ruby não é tanto da família Algol, e sim claramente uma linguagem orientada a objetos. A influência de Smalltalk é grande, e até nos nomes da biblioteca padrão isso aparece, com coisas como
collectem vez demap. Em Ruby, tudo é objeto do começo ao fim, e também é mais natural entender chamadas de método como envio de mensagens a objetos. Ela é muito comparada com Python, mas o caminho evolutivo foi bem diferente, e hoje dá a sensação de que convergiram para ecossistemas parecidos. Para mim, Ruby parece uma alpaca aconchegante mais do que PythonHello World, mas até os tipos básicos viraram objetos. Para quem não gosta de OOP, mostrartype(42)edir(42)é uma boa forma de enfatizar que até inteiros são objetosEu colocaria mais uma categoria na árvore genealógica das linguagens: as linguagens para expressão de provas. São a família em que programas são provas via correspondência Curry-Howard, e Lean seria um exemplo representativo. Dá para tratá-las como subcategoria do funcional, mas sinto que vale um eixo próprio, porque o objetivo principal é verificação, não execução
Recentemente voltei a olhar um projeto de comparação de linguagens, e o benchmark fazia decomposição cíclica em paralelo de 3.715.891.200 signed permutations de 10 caracteres. Mais do que “linguagens arquetípicas”, eu queria encontrar, entre as implementações modernas de cada paradigma, linguagens que eu realmente escolheria para programação de pesquisa. Olhei não só desempenho, mas também quão fácil era receber ajuda de IA e quão confortável eu ficava lendo e pensando no código; com a IA, também deu para fazer uma espécie de turismo por otimizações relativamente profundas em cada linguagem. Os resultados estão aqui, e fiquei especialmente surpreso de ver F# no topo
Eu também escrevi algo parecido aqui. Concordo com Algol, Lisp, Forth, APL, Prolog, mas para linguagem funcional inovadora coloquei SASL, um pouco anterior a ML, e como representante de orientação a objetos escolhi Smalltalk, que veio antes de Self. Também incluí Fortran, COBOL, SNOBOL e Prograph, por achar que cada uma mudou o jogo à sua maneira
Eu gostaria de adicionar a esta discussão as famílias semânticas. Coisas como Verilog, Petri nets, Kahn process networks, dataflow machines, process calculi, reactive, term rewriting, constraint solver/theorem prover, probabilistic programming e afins. Também me vêm à cabeça linguagens como Unison, Darklang, temporal dataflow e DBSP, que não se encaixam perfeitamente nas 7 categorias existentes, mas na prática estão perto de produção. Pode soar como trapaça, mas a maioria delas representa modelos de computação paralelos ao modelo de máquina de von Neumann. Faz tempo que tenho vontade de escrever algo como “todas as formas de computação que conhecemos, para além de von Neumann”
1+1comoADD(1,1), eu conseguia fazer o parsing do jeito que sabia. Além disso, eu me recusava sem motivo a aprender regex, então o código ficou bastante esquisito, e ainda lembro de um colega dizendo “se o Andy diz que consegue, então não vamos mexer”. Outra pessoa do time resolveu a mesma coisa com regex em um código talvez 20 vezes menor que o meu“Concepts of programming languages”, que fiz na TU Delft, foi minha disciplina favorita em computação. Aprendemos C, Scala para a parte funcional e JavaScript para o conceito de protótipos, e isso me ajudou muito quando fui aprender Elixir alguns anos depois. Também havia uma aula em que se escrevia um agente de Unreal Tournament em GOAL, uma linguagem baseada em Prolog. Durante muito tempo eu não consegui imaginar onde usar Prolog, mas no fim acabei usando para criar um corretor ortográfico que fazia ajustes iterativos em frases ruins em papiamentu geradas por LLM
Concordo com a ideia de que é preciso aprender linguagens de categorias diferentes. Só depois de aprender OCaml as funções realmente começaram a parecer funções matemáticas para mim, e Mathematica me ensinou a olhar para expressões como entrada em si. A notação polonesa reversa do PostScript não mexeu só com aritmética simples; pareceu literalmente religar meu modo de pensar. Por outro lado, não concordo com a ideia de que tanto faz escolher Java, C#, C++, Python ou Ruby. Se o objetivo for apenas implementar quicksort, talvez pareçam parecidas, mas para quem quer realmente construir alguma coisa, a escolha da linguagem faz uma diferença de dia e noite. Se você colocar Ruby na mão de alguém que quer fazer jogos 3D, ou Java na mão de alguém que quer fazer ciência de dados exploratória ou deep learning, isso pode matar a motivação
Este texto me lembrou 7 languages in 7 weeks, do Bruce Tate. Foi por esse livro que conheci Erlang. Ainda assim, historicamente me parece um pouco forçado colocar COBOL e Fortran dentro da família Algol, embora isso também sirva para lembrar que história é, por natureza, um tipo de organização redutiva até certo ponto
Já houve uma discussão antiga no HN sobre isso. A discussão anterior ajuda a entender melhor o contexto