Linguagem de programação: classificações
Você sabe classificar uma linguagem de programação? Qual é imperativa, funcional, lógica ou de script? Com ou sem suporte a orientação a objetos? De alto ou baixo nível? Este post explica quais são as categorias das linguagens de programação.
1. Classificações de Linguagens
“A linguagem C é uma linguagem compilada, fortemente tipada, de baixo nível e imperativa.”
Sabe explicar todas essas características da linguagem C?
Existem diferentes formas de classificar uma linguagem de programação em categorias. Particularmente gosto muito de como faz Robert Sebesta no livro Conceitos de Linguagem de Programação. Eu utilizei a terceira edição desse livro na disciplina de Linguagens de Programação durante minha graduação (há muito tempo!) e, recentemente, tive a oportunidade de ler a décima primeira edição, publicada em 2015. Fiquei impressionado ao constatar como o livro se manteve atualizado mesmo com o surgimento de várias novas linguagens. Ele continua sendo uma das grandes, se não for a maior, referência bibliográfica da área.
Linguagens de programação são normalmente divididas em quatro categorias: imperativas, funcionais, lógicas e orientadas a objetos. Entretanto, as linguagens que suportam orientação a objeto não formam uma categoria separada. Cada linguagem pode ter suporte parcial, total ou não ter suporte a orientação de objetos. Se você não sabe o que é orientação a objeto (OO), clique aqui. Vou explicar cada uma dessas categorias, mas antes quero comentar sobre as outras classificações.
1.1 Alto e baixo nível
Uma forma muita usada no dia a dia para classificar uma linguagem de programação é dizer que ela é de alto ou baixo nível. A altura, nesse caso, está relacionada com a proximidade do hardware e seu código de máquina (formado por números 0 e 1). Um computador, através de seu processador, executa instruções simples e só entende código de máquina. Uma linguagem de baixo nível tem instruções mais próximas do código de máquina e é projetada pensando mais no acesso e controle ao hardware do que na facilidade de uso para o usuário (no caso, o programador). Normalmente, as linguagens de baixo nível possuem ótimo desempenho computacional. Assembly e C são exemplos de linguagens de baixo nível, embora a segunda tenha muito mais recursos de programação que a primeira.
Uma linguagem de alto nível, por outro lado, foi projetada pensando mais no usuário, na sua facilidade de escrita, legibilidade do código, capacidade de abstração e recursos. Como consequência, a conversão do código em uma linguagem de alto nível para código de máquina é mais custosa. Ela está mais longe conceitualmente, porque existem mais camadas de abstração entre a linguagem que o usuário escreve e o código que é rodado efetivamente no hardware. Java, JavaScript e Python são exemplos de linguagens de alto nível.
Veja trechos de código em linguagens de alto e baixo nível na seção 3 deste post Linguagem de programação: o que é.
1.2 Compilada ou Interpretada
Uma linguagem de programação também pode ser dita também como compilada ou interpretada.
Existem 3 métodos de implementação possíveis para uma linguagem de programação. Método de implementação é a a forma como o código escrito em uma linguagem de programação é convertido para um formato executável. Entendeu? Vou explicar melhor.
Quem transforma o código escrito em linguagem de programação em software é outro software, equivalente a um programa que gera novos programas para computadores. Os diferentes tipos de software que fazem isso são chamados de implementadores. Eles implementam uma linguagem de programação no sentido de torná-la utilizável para que programadores possam escrever e gerar programas com ela.
1.2.1 Compilação
Os programas podem ser traduzidos para código de máquina e depois executados diretamente pelo computador. Esse método é chamado de implementação baseada em compilação, com a vantagem de ter uma execução de programas muito rápida, uma vez que o processo de tradução esteja completo. Um compilador é um software que compila o código para um formato executável. A maioria das linguagens usam implementações de compilação, uma vez que esse método é o mais rápido. A linguagem C e a linguagem C++ são exemplos de linguagens compiladas.
1.2.1 Interpretação
O segundo método é chamado de interpretação pura e representa o oposto da compilação. Os programas, escritos em linguagem de programação, são interpretados diretamente por outro software, chamado de interpretador. Não existe a etapa da tradução. O interpretador age como uma máquina virtual (pois é simulada em software) para a linguagem de programação. Esse método tem vantagens de depuração do código fonte, que significa rastrear algum erro dentro do fluxo do programa até a exata linha que ele acontece. Por outro lado, possui desvantagens como necessitar de mais espaço e possuir maior tempo de execução quando comparado ao método de compilação. Segundo Sebesta (SEBESTA, 2015), o tempo de execução de um sistema interpretado é de 10 a 100 vezes mais lento do que em métodos compilados. PHP e JavaScript são exemplos de linguagens puramente interpretadas.
1.2.3 Híbrido
O terceiro e último método é o híbrido, que representa um meio termo entre os compiladores e os interpretadores. Ele traduz os programas em linguagem de alto nível para uma linguagem intermediária projetada para facilitar a interpretação. Naturalmente, os métodos híbridos são mais rápidos do que a interpretação pura. Perl é uma linguagem com um sistema híbrido. As primeiras implementações de Java eram todas híbridas. Um programa escrito na linguagem Java é primeiro compilado para seu formato intermediário, chamado bytecode, e depois é interpretado pela JVM (Java Virtual Machine). Seu formato intermediário, bytecode, fornece portabilidade para qualquer máquina que tenha um interpretador de bytecode e um sistema de tempo de execução associado. Juntos, eles são chamados de Máquina Virtual Java (JVM). Existem atualmente sistemas que traduzem o bytecode para código de máquina de forma a possibilitar uma execução mais rápida, eliminando assim a interpretação de uma implementação Java.
Um sistema de implementação famoso é o Just-in-time (JIT). Ele inicialmente traduz os programas para uma linguagem intermediária. Então, durante a execução, compila os métodos da linguagem intermediária para linguagem de máquina quando esses são chamados. Todas as linguagens .NET são implementadas em um sistema JIT e algumas implementações de Java também.
1.3 Orientação a Objeto
As linguagens de programação podem ter suporte parcial, total ou não ter suporte para orientação a objetos.
1.3.1 Programação estruturada
Conceitualmente, um programa pode ser construído em torno do código ou em torno dos dados. Alguns programas são escritos em função “do que acontece”(código) enquanto outros são escritos em função “do que está sendo afetado” (dados). A primeira forma é chamada de modelo orientado a processos e utilizada com o paradigma de programação chamada de estruturada.
Esse enfoque caracteriza um programa como sendo uma série linear de passos, que formam o código. O modelo orientado para o processo pode ser descrito com o código atuando sobre os dados. Contudo, à medida que os programas se tornam maiores e mais complexos, os custos de software dentro de seu ciclo se tornam maiores na etapa de manutenção do que na de desenvolvimento.
1.3.2 – Programação Orientada a Objeto
Para gerenciar a crescente complexidade, foi concebido o segundo enfoque, chamado de programada orientada a objetos. A programação orientada a objetos organiza um programa em torno de seus dados, ou seja, de seus objetos, e de um conjunto de interfaces bem definidas para acesso a esses dados.
Um elemento essencial da programação orientada a objetos é a abstração. Nós, seres humanos, utilizamos a abstração como uma forma de lidar com a complexidade. Um bom exemplo disso é o carro, pois ninguém pensa no carro como um conjunto de dezenas de milhares de peças individuais e sim como um objeto bem definido e que tem um determinado comportamento. Essa abstração permite que as pessoas utilizem um carro para ir de um lugar para o outro, mesmo sem entender nada de mecânica de automóveis. O usuário pode utilizar os vários sistemas do automóvel, mesmo sem entender como eles funcionam. Para andar com o carro para trás, basta acionar a alavanca da marcha para a posição de ré, ou seja, para usar o sistema basta conhecer a interface.
Linguagens de programação orientadas a objetos são aquelas que proporcionam mecanismos que ajudam a implementar o modelo orientado a objetos. Esses mecanismos são: encapsulamento, herança e polimorfismo. Não vamos explicar esses conceitos aqui, mas se você quiser saber mais sobre eles, estão no post Orientação a objetos.
Algumas linguagens possuem todos os 3 mecanismos e são ditas que aceitam plenamente o modelo de orientação a objeto. Outras suportam apenas alguns, como encapsulamento e polimorfismo, mas não herança. Por fim, algumas não possuem nenhum suporte a orientação a objeto.
1.3.3 – Muita calma agora
Antes de programar com orientação a objetos, programei bastante do modo estruturado. Aprendi dos dois modos e sempre usei um ou outro conforme a necessidade e a situação.
Nos últimos anos, porém, vejo que muitas pessoas aprenderam a programar só com orientação a objetos. O problema é que se cria um mito do que não se conhece. Muitos pensam que a programação estruturada está ultrapassada e que a orientação a objeto é a última bolacha do pacote. O resto não presta.
Não é bem assim que funciona. Marijn Haverbeke, autor do livro Javascript Eloquent (HAVERBEKE, 2014), cita que, em algumas rodas de desenvolvedores, o filme dos objetos está queimado. Ele também cita um trecho que faz uma crítica a orientação a objeto que vou colar aqui:
“O problema com as linguagens orientadas a objeto é que elas têm tudo implícito no ambiente que elas carregam consigo. Você queria banana, mas o que você teve foi um gorila segurando a banana e toda a floresta.” Joe Armstrong, entrevistado em Coders at Work
A moral da história é que cada modo de programação pode ser útil de acordo com o contexto. Vale lembrar que, em última instância, é o processador que está executando várias instruções simples. As camadas de complexidade e abstração entre a execução do processador e a linguagem de alto nível podem variar bastante conforme a linguagem e a metodologia de projeto. Pode ser orientada a processos, a dados, a objetos, mas no final, o que vale é o software fazer rodar no equipamento a funcionalidade para o qual foi programado. A análise da metodologia deve ser feita quando fixado critérios bem definidos, como desempenho, manutenção, confiabilidade e outros.
Caso tenha boiado geral aqui e queira saber mais sobre isso, leia o post Linguagem de Programação: paradigmas.
2. Linguagens imperativas
A maioria das linguagens populares das últimas décadas foram projetadas considerando a arquitetura de computadores chamada de arquitetura de von Neumann. Elas são chamadas de linguagens imperativas. Vamos entender o que é essa arquitetura.
2.1 Arquitetura Von Newmann
Em um computador von Neumann, tanto os dados quanto os programas são armazenados na mesma memória. A unidade central de processamento (CPU), que executa instruções, é separada
da memória. Logo, instruções e dados devem ser transmitidos da memória para a CPU. Resultados de operações na CPU devem ser retornados para a memória. Praticamente todos os computadores digitais construídos desde os anos 1940 têm sido baseados nessa arquitetura. A estrutura geral de um computador von Neumann é mostrada na figura abaixo.
2.2 Principais elementos
Por causa da arquitetura de von Neumann, os recursos centrais das linguagens imperativas são as variáveis, as sentenças de atribuição, e a forma iterativa de repetição nessa arquitetura.
Sentença de atribuição, para quem não sabe, é um comando que usa o sinal de igual. Por exemplo:
x = 10;
O termo imperativa vem do fato do programador dar ordens do que fazer ao computador. As ordens na verdade são comandos sequenciais que devem ser executados. Faça isso, depois isso, teste isso e assim por diante. Os comandos mudam o estado do programa e suas variáveis.
Uma linguagem imperativa também pode ser chamada de procedural devido ao paradigma de programação procedural (orientado a procedimento). Contudo, uma linguagem imperativa não deve ser confundida com uma linguagem não orientada a objeto. Várias linguagens imperativas oferecem suporte parcial ou completo para orientação a objeto. C++ e Java são dois exemplos de linguagens imperativas e com pleno suporte a orientação a objetos. A diferença é que Java só aceita orientação a objetos, enquanto C++ aceita ambos paradigmas de programação.
2.3 Linguagens visuais
As linguagens visuais são uma subcategoria das imperativas. A mais popular é o Visual BASIC .NET (VB.NET). Essas linguagens (ou suas implementações) incluem capacidades para geração de segmentos de códigos que podem ser copiados de um lado para outro. As linguagens visuais fornecem uma maneira simples de gerar interfaces gráficas de usuário para os programas. Por exemplo, em VB.NET, o código para produzir uma tela com um controle de formulário, como um botão ou uma caixa de texto, pode ser criado com um simples clique. Tais capacidades estão agora disponíveis em todas as linguagens .NET: C#, Visual BASIC .NET, JScript (versão da Microsoft de JavaScript), J# (a versão da Microsoft de Java) ou C++ gerenciado.
2.4 Linguagens de script
Segundo Sebesta, alguns autores se referem às linguagens de script como uma categoria separada de linguagens de programação. Entretanto, linguagens nessa categoria são mais unidas entre si por seu método de implementação, interpretação parcial ou completa, do que por um projeto de linguagem comum. As linguagens de scripting, dentre elas Perl, JavaScript e Ruby, são imperativas em todos os sentidos.
2.5 Conclusão
A estrutura de uma linguagem de programação imperativa foi projetada pensando em um arquitetura de máquina em vez de pensar no que é melhor usuário. Mesmo assim, muitos acreditam que as linguagens imperativas são mais naturais do que as funcionais no sentido de uso. A ideia de dar ordens ao computador por meio de comandos é intuitiva para o ser humano.
Exemplos de linguagens imperativas: C, C++, C#, Java, Cobol, PHP, Perl, JavaScript, Ruby, Python e várias outras.
3. Linguagens funcionais
Apesar do estilo imperativo de programação ser considerado aceitável pela maioria dos programadores, sua forte dependência da arquitetura é vista por alguns como uma restrição desnecessária nos possíveis processos de desenvolvimento de software. Até agora, entretanto, apenas uma minoria de programas tem sido escrita em linguagens não imperativas. O paradigma de programação funcional, baseado em funções matemáticas, é a base de projeto para um dos estilos de linguagem não imperativos mais importantes. Esse estilo de programação é suportado por linguagens de programação funcional (ou linguagens aplicativas).
3.1 Elementos
As linguagens funcionais são organizadas em torno de chamadas à função em vez de sentenças de atribuição, como nas imperativas. Por exemplo, considere o algoritmo abaixo que chama 4 funções para controlar as ações de um robô de limpeza.
Ande(1)
Vire(esquerda)
Ande(2)
Aspire(15)
As funções também podem ser combinadas entre si de maneira simples.
Nem as sentenças de atribuição nem as variáveis abundantes em programas escritos em linguagens imperativas são necessárias em programas escritos em linguagens funcionais. As computações são feitas basicamente pela aplicação de funções para parâmetros informados.
3.2 Prós e contras
A simplicidade e a elegância são as principais razões pela qual as linguagens funcionais surgem como principal alternativa às complexas linguagens não funcionais como C++. Outros fatores, como a eficiência, entretanto, têm prevenido que as linguagens funcionais sejam mais utilizadas.
Assim como as linguagens imperativas, as linguagens funcionais podem oferecer suporte à programação orientada a objetos.
3.3 Representantes
Inteligência Artificial (IA) é uma ampla área de aplicações computacionais caracterizada pelo uso de computações simbólicas em vez de numéricas. Computações simbólicas são aquelas nas quais símbolos, compostos de nomes em vez de números, são manipulados. Esse tipo de programação permite criar e executar segmentos de código durante a execução, o que é bem conveniente em algumas situações.
A primeira linguagem de programação amplamente utilizada para aplicações de IA foi a linguagem funcional LISP. A maioria das aplicações de IA desenvolvidas antes de 1990 foi escrita em LISP ou em uma de suas parentes próximas. LISP começou como uma linguagem puramente funcional, mas rapidamente adquiriu alguns recursos imperativos importantes que aumentaram sua eficiência de execução. Ela ainda é a mais importante das linguagens funcionais, ao menos no sentido de que foi a única a atingir uso disseminado.
Outra linguagem funcional famosa é a Scheme, que na verdade é um dialeto de LISP. Como uma pequena linguagem com uma sintaxe e semântica simples, Scheme é bastante adequada para aplicações educacionais, como cursos sobre programação funcional e introduções gerais à programação.
Peter Norving, grande estudioso e referência da área de Inteligência Artifical, recomenda (disponível aqui) aprender Scheme ou Pyton como a primeira linguagem de programação.
4. Linguagens lógicas
Vimos que linguagens imperativas são baseadas em variáveis e sinais de atribuição. As funcionais, baseadas em funções. Uma linguagem de programação lógica é baseada em regras.
4.1 – Elementos
Ao invés do cálculo numérico, existe o cálculo de predicados, que é uma notação lógica formal para comunicar processos para o computador. A programação lógica não obedece um procedimento com um resultado bem definido. Ela aceita informações e processos de inferência para computador os resultados. Um exemplo famoso é a a relação de parentesco. Imagine uma função assim:
mae(Leandro, Leda)
mae(Leda, Luzia)
A primeira linha significa que a mãe de Leandro é Leda. A segunda, que Luzia é mãe de Leda. Se entrarmos com uma regra que define uma avó, temos:
avo(X, Z) :- mae(X,Y), mae(Y,Z)
Com as duas primeiras informações e com a regra dada em notação lógica matemática, o computador consegue computar quem é a avó de Leandro, por exemplo.
Essa abordagem para o desenvolvimento de software é radicalmente diferente daquelas usadas nas outras três categorias de linguagens e requer um tipo completamente diferente de linguagem.
4.2 – Representantes
Assim como as linguagens funcionais, as linguagens lógicas possuem a IA como maior área de utilização. Prolog é a linguagem de programação lógica mais usada. Na verdade, Prolog representa para linguagens lógicas o que LISP representa para as funcionais. É a grande linguagem lógica existente e a maioria das linguagens lógicas atuais derivam de Prolog. O nome da linguagem vem de programação lógica.
5. Qual devo aprender?
Peter Norving recomenda que um programador aprenda, pelo menos, 6 linguagens de programação com focos diferentes:
- Uma com foco em abstração de classes, como Java ou C++
- Uma com foco em abstração de função, como Lisp ou Haskell
- Uma com foco em abstração sintática, como LISP
- Uma com suporte para especificações declarativas, como Prolog ou templates C++
- Uma com foco em paralelismo (Clojure ou Go)
É claro que, como ele é um mestre da área de IA, puxou a resposta para a área dele.
Nossa sugestão de qual aprender está no post Ranking de linguagens de programação.
Depois deste post, ficou mais fácil aprender uma linguagem de programação? Escreva para mim no leandro@mentalguild.com.br e me conte se esse texto lhe ajudou.
Referências
[1] SEBESTA, Robert W. Concepts of programming languages. 11ª ed. New Jersey: Pearson, 2015.
[2] HAVERBEKE, Marijn. Eloquent JavaScript. 2ª ed. San Francisco: No Starch Press, 2014. Disponível em: <eloquentjavascript.net>. Acesso em: 12 mar. 2018.