Paradigmas de linguagem de programação em Python - Parte 1

 Linguagem de programação e produtividade do programador

Um software é um conjunto de instruções, que são usadas para dar instruções ao hardware do computador. O software pode ser aplicativo ou básico. O Software Aplicativo, ou app, oferece ao usuário uma tarefa laboral ou de lazer; o Software Básico, cria programas essenciais para o funcionamento do computador (por exemplo o sistema operacional).

A linguagem de programação é um software básico, já que permite que o programador escreva outros programas de computador, que por sua vez podem ser apps ou básicos.

A codificação de um programa em uma linguagem de programação se chama programa-fonte. Ele ainda não pode ser entendido e nem executado pelo hardware (o hardware só entende a linguagem binária). A linguagem de programação é um conjunto de símbolos, palavras reservadas, comandos, regras sintáticas e semânticas, etc. e tudo isso permite especificar instruções de um programa.

A linguagem de programação foi criada facilitar a criação de programas pelo programador, assim o programador não precisaria conhecer os detalhes mais íntimos das máquinas onde a programação é feita, ele simplesmente escreveria um programa por meio de um programa.

O papel da abstração nas linguagens de programação

Abstração - processo que identifica quais as qualidades e propriedades são relevantes, e despreza aquilo que é irrelevante; um modelo é a abstração da realidade. Um programa de computador é um modelo. Ele representa a solução de um problema em termos algorítmicos.

A linguagem binária foi a primeira linguagem criada para programar. Essa é na realidade a única linguagem que o computador entende, ela porém, é muito difícil de ser memorizada, utilizada e entendida por nós. As primeiras linguagens de programação não reconheciam o papel da abstração.

Nos anos 50, o Assembly (linguagem de montagem) melhorou a vida do programador, mas ainda fazia com que ele tivesse que escrever 1 linha de código para cada instrução que a máquina fosse executar, forçando ele a pensar como a máquina.

Com a intenção de aumentar a abstração das linguagens, e melhorar a performance dos programadores, surgiram as linguagens de alto nível, que são mais próximas à linguagem humana, e cada vez mais distantes das linguagens Assembly e binária.



Classificação das linguagens de programação

Classificação por nível

Essa classificação considera a proximidade da linguagem com as características da arquitetura do computador, ou com a comunicação com o homem.


Linguagem de baixo nível

Essa é a linguagem que se aproxima a linguagem da máquina. Além disso, essa linguagem se comunica diretamente com os componentes de hardware, como processador, memória e registradores. As linguagens de baixo nível estão relacionadas à arquitetura do computador.

Cada processador diferentes (por exemplo, os I3, I5 da Intel), tem um instruction set, ou seja, um conjunto de instruções específicas, e as linguagens de baixo nível utiliza esse conjunto específico para cada respectivo processador.

Essa linguagem que era nativa dos processadores, era muito complexa e difícil de ser utilizada, o que motivou o desenvolvimento da Assembly, que recebia o código escrito pelo programador, e convertia ele para a linguagem de máquina. Mesmo não sendo o ideal, já era um grande avanço.

Linguagem de alto nível

Quanto mais uma linguagem se afasta da máquina e se aproxima do ser humano, mais alto o seu nível. As instruções das linguagens de alto nível são abstratas e não estão relacionadas diretamente à arquitetura do computador.

Alguns exemplos são: ASP, C, C++, C#, Pascal, Delphi, Java, JS, PHP, Ruby, etc.

Os comandos escritos em linguagem de alto nível, são convertidos e equivalem a mais de uma instrução primária. Dessa forma o programador precisa escrever menos código para realizar as mesmas ações, o que aumenta sua produtividade.

Classificação por gerações

Não existe realmente um consenso sobre a quantidade de gerações que existem, mas usualmente é por volta de 5 a 6.

  • 1ª geração - linguagem de máquina, nativa dos computadores;
  • 2ª geração - Assembly; escrita por meio de códigos pelos programadores, e depois convertida para a linguagem da máquina; facilita o uso pelos programadores, mas ainda é necessário conhecer a arquitetura dos computadores;
  • 3ª geração - já são de alto nível; o processo de conversão para a linguagem da máquina fica mais complexo, e é realizado pelos interpretadores; são procedurais, ou seja, deve especificar-se o passo a passo da solução do problema;
  • 4ª geração - também é de alto nível, e tem aplicações e objetivos bem específicos; é uma linguagem não procedural, ou seja, o programador especifica o que deve ser feito, e não como deve ser feito;
  • 5ª geração - linguagens declarativas e não algorítmicas; são usadas para o desenvolvimento de sistemas especialistas na área de inteligência artificial, ou em sistemas de machine learning e reconhecimento de voz.

Domínios da programação

Os computadores, desde que foram criados, tem sido usados para diversos fins, na ciência, nas forças armadas, por empresas públicas ou privadas, para lazer, etc. Além disso, seus usos também são diversos, desde controlar robôs que fazem a montagem de automóveis, até simples chatbots que enviam mensagens automáticas no whatsapp. Por conta desses usos tão diversos, surgem diferentes linguagens de programação com diferentes objetivos.
 

Aplicações científicas (máquinas de calcular com alta precisão)

O ENIAC foi o primeiro computador, e ele foi criado em 1946 depois de ter sido desenvolvido por 3 anos. Ele foi criado com o objetivo de realizar cálculos balísticos. Os computadores seguintes, que foram criados nos anos 40 e 50, também tinham o objetivo de realizar cálculos complexos.

As linguagens de programação dessa época eram a linguagem de máquina e a Assembly. Foi só nos anos 60, que surgiram as primeiras linguagens de alto nível, que tinham como características principais a estrutura de dados simples, os cálculos feitos com precisão e a preocupação com a eficiência.

Aplicações comerciais

Nos anos 50, iniciou-se uma demanda de tecnologia dentro de empresas, e por isso em 1960, surgiu a linguagem que seria um ícone nas aplicações comerciais de computadores de grande porte, o COBOL.

As linguagens que auxiliam o crescimento comercial tem como características a facilidade para produzir relatórios, precisão com números decimais e capacidade de especificar operações aritméticas comerciais.

As linguagens destinadas a aplicações comerciais ganharam força a partir dos anos 80, com a microcomputação, o que levou mais tecnologia aos médios e pequenos empresários.

Aplicações com inteligência artificial

As linguagens de programação que auxiliam no desenvolvimento de AIs, ganham força na atualidade. Houve uma ruptura no pensamento computacional, já que essas linguagens utilizam a computação simbólica, e não a numérica.

Em 1959 surgiu a linguagem Lisp, que foi a primeira linguagem projetada para dar apoio à computação simbólica. Em 1977, surge o Prolog, que foi a primeira linguagem para apoio da computação lógica, que é a essência dos sistemas que usam a AI para simular o comportamento humano.

Programação de sistemas

Esse tipo de programação cabe a linguagens que tenham comandos e estruturas para acessar diretamente o hardware. São usadas para desenvolver softwares básicos, como sistemas operacionais, tradutores e interpretadores de linguagens de programação.

A Assembly era usada para esse fim, até surgir a linguagem em C. A linguagem C++ também tem essa finalidade.

Programação para Web

Com o crescimento da internet, o uso de sistemas deixa um pouco de lado o ambiente desktop para o ambiente Web. Nesse contexto, temos dois ambientes diferentes de desenvolvimento: a camada de apresentação, que roda no navegador, e a camada de lógica do negócio, que roda nos servidores Web.

Na camada de apresentação, usa-se o HTML e CSS, e o JavaScript no lado cliente. Na camada de lógica do negócio, usa-se C#, PHP, ASP, .NET, Java, Ruby e Python.

Programação mobile

A quantidade de aplicativos para celular cresce grandemente a cada dia, já que grande parte da população mundial tem acesso a internet pelo celular. Esses aplicativos são, na verdade, interfaces que rodam no lado cliente.

As principais linguagens para o desenvolvimento de apps mobile são: Android - Java e Kotlin; IOS - Swift e Objective-C; Windows - C#, Visual Basic, C++, HTML, CSS, JavaScript e Java.


 Avaliação de linguagens de programação

São quatro os critérios para a avaliação das linguagens de programação, dentro de um mesmo domínio de programação, e cada critério é influenciado por alguma característica da linguagem. São eles:
  • Legibilidade 
  • Facilidade de escrita
  • Confiabilidade
  • Custo

Legibilidade

Esse critério diz respeito a facilidade que se tem para um programa ser lido e entendido pela sintaxe e construção da linguagem, sem que sejam consideradas as influências da má programação.

Quanto mais simples uma linguagem de programação, melhor será sua legibilidade. Uma linguagem que tem um grande número de construções básicas, é mais difícil do que uma que tenha poucas. As linguagens mais difíceis tendem a entrar em desuso.

ortogonalidade em uma linguagem, se refere a um conjunto relativamente pequeno de construções primitivas, que pode ser combinado em um número de maneiras para construir as estruturas de controle e de dados de uma linguagem de programação.

Ou seja, a ortogonalidade se refere a possibilidade de combinar entre si, sem restrições, as construções básicas da linguagem para construir estruturas de dados de controle. Uma linguagem ortogonal, tende a ser mais fácil de aprender e tem menos exceções.

A falta de ortogonalidade, faz com que a linguagem tenha muitas exceções à regra. Quanto menos exceções, maior o grau de regularidade no projeto da linguagem, e mais fácil de ler, entender e aprender.

desvio incondicional limita a legibilidade dos programas, pois essa instrução pode levar o controle do código a qualquer ponto do programa, limitando o entendimento e , consequentemente, a legibilidade do código escrito na linguagem. As linguagem mais modernas não implementam esse desvio, então o projeto de estruturas de controle é menos relevante.

Outra propriedade que aumenta a legibilidade do código escrito, é a facilidade oferecida para definir tipos de estrutura de dados. Por exemplo, uma linguagem que permite definir registros e vetores, mas não permite que um vetor tenha registros como seus elementos, tem a legibilidade afetada.

Facilidade de escrita

Se a escrita não é fácil, há dificuldade para quem escreve e para quem lê o código. Uma das características que facilitam a escrita é a simplicidade e ortogonalidade. Quanto mais simples, mais fácil de escrever um programa na linguagem. O ideal são linguagens com poucas construções primitivas.

Uma linguagem que possui um grande número de construções, pode fazer com que o programador não use todas, deixando de lado as mais eficientes e elegantes. A expressividade também contribui para a facilidade da escrita dos códigos, pois ajuda o programador a escrever de uma forma mais conveniente.

Quanto mais abstrata uma linguagem é, mais fácil ela se torna. Essa abstração pode vir de processos, como conceito de subprograma, ou de dados, como uma lista encadeada.

Confiabilidade

Ser confiável, significa que um programa se comporta conforme suas especificações, sob qualquer condição, e todas as vezes que for executado. A verificação de tipos, é uma verificação para saber se existem erros de tipo. A verificação de tipos em tempo de compilação é desejável, já em tempos de execução é dispendiosa e mais flexível.

tratamento de exceção garante sua correta execução, o que aumenta a confiabilidade. A linguagem deve permitir a identificação de eventos indesejáveis, e especificar respostas adequadas a cada evento. O comportamento do programa se torna previsível com a possibilidade de tratamento das exceções.

Aliasing é o fato de ter mais de um nome, referenciando a mesma célula de memória. Isso afeta a confiabilidade. Restringir Aliasing aumenta a confiabilidade. A legibilidade afeta tanto na fase de codificação, quanto na fase de manutenção. Programas de difícil leitura, são também difíceis de serem escritos.

Custo

Os custos de uma linguagem variam por conta da escrita do programa, do compilador, da execução do programa, da manutenção do código, da implementação da linguagem e de treinamento. Python, por exemplo, é uma linguagem com alta legibilidade, facilidade de escrita, confiabilidade, seu custo não é elevado, além de ser open source, o que faz dela uma linguagem fácil de ser aprendida.

Os paradigmas e suas características

Outra forma de classificar as linguagens de programação, é por meio de paradigmas. Um paradigma agrupa linguagens com características semelhantes e que surgiram na mesma época, e eles são agrupados em imperativos e declarativos, de acordo com a forma com que os programas são estruturados e descritos. 

Paradigma imperativo

Ele agrega em si, três outros paradigmas, o estruturado, o concorrente e o orientado a objeto. O que eles tem em comum, é o fato de especificarem passo a passo o que deve ser feito para a solução do problema. As linguagens que se classificam dentro do paradigma imperativo, são dependentes da arquitetura do computador, já que especificam em seus programas, como a computação é realizada.

Paradigma estruturado

Nele temos as principais linguagens da década de 70 e 80, que seguiram os princípios da programação estruturada, que são:
  • Não usar desvios incondicionais;
  • Desenvolver programas por refinamento sucessivo, motivando o desenvolvimento de rotinas e a visão do programa partindo do geral para o particular, ou seja, o programa vai se refinando à medida que se conhece melhor o problema e seus detalhes;
  • Desenvolver programas usando três tipos de estruturas, sequenciais, condicionais e repetição;
  • Basear-se na arquitetura de Von Neumann, onde programas e dados residem na memória, instruções e dados trafegam da memória para a CPU (e vice versa), e resultados das operações trafegam da CPU para a memória.
As linguagens Pascal e C caracterizam bem esse paradigma. O Python, que é multiparadigma, tem o estilo básico do paradigma estruturado.

Paradigma orientado a objetos

Conforme o crescimento do tamanho do código e complexidade dos programas, o paradigma estruturado começou a apresentar limitações nos sistemas que passaram a ter dificuldade de manutenção e reuso de programas e rotinas padronizadas.

Surge então, a orientação a objetos como uma solução, o que permite, através de propriedades como abstração, encapsulamento, herança e polimorfismo, uma maior organização, reaproveitamento e extensibilidade de código, e consequentemente, programas mais fáceis de serem escritos e mantidos.

O principal foco desse paradigma foi possibilitar o desenvolvimento mais rápido e confiável. As classes são abstrações que definem uma estrutura que encapsula dados e um conjunto de operações possíveis de serem usados, chamados métodos.

O paradigma orientado a objetos, por sua vez, usa os conceitos do paradigma estruturado na especificação dos comandos de métodos. Por isso, ele é considerado uma evolução do paradigma estruturado. Python, Smalltalk, C++, Java, Delphi, são linguagens que caracterizam o paradigma orientado a objetos. Python é orientado a objeto, pois tudo nessa linguagem é objeto.

Paradigma concorrente

É caracterizado quando processos executam simultaneamente, e concorrem aos recursos de hardware. Essas são características cada vez mais comuns em sistemas de informação. Esse paradigma pode usar apenas um processador ou vários.

Quando usam um processador, os processos concorrem ao uso do processador e recursos. Quando usam mais de um, caracterizamos o paralelismo na medida em que podem ser executados em diferentes processadores, os quais podem estar em uma mesma máquina, ou distribuídos em mais de um computador.

Ada e Java, são as linguagens que melhor caracterizam esse paradigma, pois possibilitam suporte à concorrência.

Paradigma declarativo

Diferente do imperativo, no declarativo o programador diz o que o programa deve fazer, ao invés de descrever como o programa deve fazer. O ´programador declara, de forma abstrata, a solução do problema. 

Essas linguagens não são dependentes de determinada arquitetura de computador. As variáveis são incógnitas, tal qual na Matemática e não células de memória. O paradigma declarativo agrega os funcionais e lógicos.

Paradigma funcional

Abrange linguagens que operam tão somente funções que recebem um conjunto de valores e retornam um valor. O resultado que a função retorna é a solução do problema. O programa se resume em chamadas de funções, que por sua vez podem usar outras funções.

Uma função pode invocar outra, ou o resultado de uma função pode ser argumento para outra função. Usa-se também chamadas recursivas de funções. Naturalmente, esse paradigma gera programas menores, com pouco código.

As linguagens típicas desse paradigma são LISP, HASKELL e ML. Python não é uma linguagem funcional nativa, porém ela sofreu influência desse paradigma ao permitir recursividade, uso de funções anônimas, além de ser uma linguagem com uma enorme biblioteca de funções.

Paradigma lógico

Um programa lógico, expressa a solução da maneira como o ser humano raciocina sobre um problema. Quando um novo questionamento é feito, através de um mecanismo inteligente de inferência, deduz novos fatos a partir dos existentes.

A execução dos programas escritos em linguagens de programação lógica, portanto, um mecanismo de dedução automática. O Prolog é a linguagem do paradigma lógico mais conhecida.

O paradigma lógico é usado no desenvolvimento de linguagens de acesso a banco de dados, sistemas especialistas (AI), tutores inteligentes, etc. O Python, não tem características para implementar programas que atendam ao paradigma lógico.

Identificação de métodos de implementação das linguagens

A não ser que seja escrito em linguagem de máquina, o que hoje em dia é muito pouco provável, todo programa precisa ser convertido para a linguagem de máquina, antes de ser executado. 

Essa conversão precisa de um programa que receba o código-fonte escrito na linguagem e que gere o respectivo código em uma linguagem que o computador conseguirá ler. É esse programa, que faz a tradução, que determina como os programas são implementados e como serão executados.

Existem duas formas de fazer essa conversão, pela tradução ou interpretação. É algo fundamental saber e entender qual é o processo de conversão usado na respectiva linguagem de programação.

Tradução

Nesse processo de conversão, um programa que é escrito em linguagem de alto nível é traduzido para uma versão equivalente em linguagem de máquina antes de ser executado. A tradução pode ser executada em várias fases, que podem ser combinadas e executadas simultaneamente. Esse processo é chamado erroneamente de compilação, que é na verdade uma de suas fases.

As fases que compõem o tradutor, começando na leitura do programa e terminando com o código executável, são a compilaçãomontagemcarga e ligação.
  • compilador - analisa o código-fonte, e estando tudo certo, o converte para um código Assembly;
  • montador - traduz o código Assembly para o código de máquina intermediário, que não é executável pelo computador, o código-objeto pode ser relocável, ou seja, carregado em qualquer posição de memória ou absoluto, carregado em um endereço de memória específico;
  • ligador - ele liga o código-objeto relocável com as rotinas bibliotecas usadas nos códigos-fontes, e essa ligação gera o código executável;
  • carregador - ele é quem torna o código-objeto em relocável.

Compilador

Esse é o elemento central do processo de tradução, e é responsável pelo custo de compilação. Em função de relevância, muitas vezes o processo como um todo é erroneamente chamado de compilação, uma vez que o ambiente integrado das linguagens atuais já integra todos os componentes (montador, compilador, carregador e ligador).

Essas são as fases da compilação:
  • Análise léxica - identifica os tokens (elementos de linguagem), desconsidera partes do código-fonte, como espaços em branco e comentários e gera a tabela de símbolos, com todos esses tokens, que são identificadores de variáveis, de procedimentos, de funções, comandos, etc;
  • Análise sintática - verifica se os tokens são estruturas sintáticas (como expressões e comandos) válidas, aplicando as regras gramaticais definidas no projeto da linguagem;
  • Análise semântica - verifica se as estruturas sintáticas fazem sentido, por exemplo, verifica se um identificador de variável ou constante é usado adequadamente;
  • Gerador de código intermediário, otimizador de código e gerador de código - geram o programa-alvo ou programa-objeto, contém toda a informação para gerar o código-objeto, e o otimizador elimina redundâncias do código intermediário e torna o objeto mais enxuto e eficiente;
  • Tratador de erros - em todas as fases existem erros, e um com compilador apresenta uma boa tratativa de erros;
  • Gerenciador da tabela de símbolos - mantém a tabela de símbolos atualizada a cada passo do compilador.

Interpretação

O processo de interpretação consiste em programa-fonteinterpretador e resultados. Na conversão por interpretação, cada comando do programa-fonte é traduzido e executado imediatamente. O interpretador traduz um comando de cada vez e chama uma rotina para completar a sua execução.

O interpretador é um programa que executa repetidamente a seguinte sequência:
  1. Obter a próxima instrução do código;
  2. Interpretar a instrução, conversão para comandos em linguagem de máquina;
  3. Executar a instrução.

Principais características do interpretador

  • Atua a cada vez que o programa precisa ser executado; 
  • Não produz programa-objeto persistente; 
  • Não traduz instruções que nunca são executadas; 
  • O resultado da conversão é instantâneo: resulta da execução do comando ou exibição do erro;
  • Útil ao processo de depuração de código devido a mensagem de erros em tempo de execução;
  • Execução mais lenta do que outros processos de tradução, pois toda vez que o mesmo programa é executado, os mesmos passos de interpretação são executados;
  • Consome menos memória;
  • Código fonte é portátil: não é gerado um código de máquina, pode executar o comando em alto nível ou gerar um código intermediário, se a máquina virtual foi desenvolvida para diferentes plataformas temos a portabilidade do código-fonte.

Sistemas híbridos

O processo híbrido da implementação de uma linguagem de programação, combina a execução rápida dos tradutores com a portabilidade dos interpretadores. Assim é gerado um código intermediário mais facilmente interpretável, porém não preso a uma plataforma.

Esse código intermediário não é específico para uma plataforma, o que possibilita aos programas já compilados, serem portados em plataformas diferentes, sem alterar nem fazer nada. Para cada plataforma desejada, devemos ter um interpretador desse código. Python e Java implementaram essa solução.

Sistema de implementação de Python 

O Python utiliza um sistema híbrido, uma combinação de interpretador e tradutor. O compilador converte o código-fonte do Python em um código intermediário, que roda na máquina virtual, a Python Virtual Machine (PVM).  

O interpretador, então, converte para código de máquina, em tempo de execução. O compilador traduz o programa inteiro em código de máquina e o executa, o que gera um arquivo que pode ser executado. O compilador gera um relatório de erros e o interpretador interrompe o processo na medida em que localiza o erro. CPython é uma implementação do Python, um pacote com um compilador e um interpretador Python, além de outras ferramentas para programar em Python.

Comentários