Previous Up Next
Programação de Computadores em C

Capítulo 2  Paradigmas de Programação

O número de linguagens de programação existentes atualmente chega a ser da ordem de alguns milhares. Esse número impressionante reflete o esforço no sentido de projetar linguagens que facilitem sempre mais a atividade de programação, tornando-a cada vez mais produtiva. Outro objetivo importante no projeto dessas linguagens é o de favorecer a construção de programas mais eficientes, isto é, que originem programas executáveis rapidamente e que usam relativamente pouca quantidade de memória de um computador, e seguros, isto é, menos sujeitos a erros que possam ocasionar um comportamento da execução do programa diferente daquele que é esperado.

Apesar dessa grande diversidade de linguagens de programação, a maioria delas apresenta, essencialmente, o mesmo conjunto de comandos básicos, embora esses possam apresentar formas diferentes em diferentes linguagens ( o termo “instrução” é em geral usado para linguagens de mais baixo nível, enquanto o termo “comando”, em princípio equivalente, é mais usado para linguagens de alto nível). Esse conjunto é constituído de:

Em uma linguagem com essas características, um programa consiste em uma seqüência de comandos que descreve, em essência, como devem ser modificados os valores armazenados na memória do computador, de maneira que uma determinada tarefa seja realizada. Esse paradigma de programação é denominado paradigma imperativo e linguagens baseadas nesse paradigma são chamadas de linguagens imperativas.

Existem, em contraposição, outros paradigmas de programação, chamados declarativos, nos quais um programa se assemelha mais a uma descrição de o que constitui uma solução de um determinado problema, em lugar de como proceder para obter essa solução.

A linguagem C é uma linguagem que provê suporte ao paradigma imperativo de programação. Os principais conceitos da programação imperativa são apresentados neste capítulo, como uma visão geral e introdutória. Cada um desses conceitos é novamente abordado em capítulos subseqüentes, nos quais introduzimos, passo a passo, por meio de vários exemplos, a aplicação desses conceitos na construção de programas para a solução de diversos problemas.

O objetivo deste capítulo é prover uma visão mais global sobre conceitos básicos de linguagens de programação, o que acreditamos irá contribuir para uma melhor compreensão da função e do significado de cada um deles individualmente e da estrutura de uma linguagem de programação como um todo, além de facilitar o aprendizado de como aplicar esses conceitos no desenvolvimento de programas.

2.1  Variável e Atribuição

Em um programa em linguagem imperativa, uma variável representa um lugar que contém um certo valor. Esse lugar é uma determinada área da memória do computador.

Esse conceito de variável difere daquele a que estamos acostumados em matemática. Em uma expressão matemática, toda ocorrência de uma determinada variável denota um mesmo valor. Em um programa em linguagem imperativa, ao contrário, ocorrências distintas de uma mesma variável podem representar valores diferentes, uma vez que tais linguagens são baseadas em comandos que têm o efeito de modificar o valor armazenado em uma variável, durante a execução do programa. Além disso, uma determinada ocorrência de uma variável no texto de um programa pode também representar valores diferentes, em momentos distintos da execução desse programa, uma vez que um comando pode ser executado repetidas vezes.

Em C, assim como na maioria das linguagens de programação imperativas, toda variável usada em um programa deve ser declarada antes de ser usada. Uma declaração de variável especifica o nome e o tipo da variável, e tem o efeito de criar uma nova variável com o nome especificado. O tipo denota o conjunto de valores que podem ser armazenados na variável.

Um nome de uma variável (ou função) deve ser uma sequência de letras ou dígitos ou o caractere sublinha (’_’), e deve começar com uma letra ou com o caractere sublinha.

Um nome de uma variável ou função em C pode ser qualquer sequência de letras ou dígitos ou o caractere sublinha ’_’), e deve começar com uma letra ou com o caractere sublinha. No entanto, existem nomes reservados, que não podem ser usados como nomes de variáveis ou funções pelo programador C. Por exemplo, return é uma palavra reservada em C — que inicia um comando usado para especificar o resultado de uma função e fazer com que a execução da função seja interrompida, retornando o resultado especificado — e portanto não pode ser usada como nome de variável ou função.

Nota sobre escolha de nomes de variáveis:

O nome de uma variável ou função pode ser escolhido livremente pelo programador, mas é importante que sejam escolhidos nomes apropriados — ou seja, mnemônicos, que lembrem o propósito ou significado da entidade (variável ou função) representada.

Por exemplo, procure usar nomes como soma, media e pi para armazenar, respectivamente, a soma e a média de determinados valores, e uma aproximação do número π, em vez de simplesmente, digamos, s, m, p (outras letras que não s, m e p são ainda menos mnemônicas, por não ter relação com soma, media e pi). No entanto, é útil procurar usar nomes pequenos, a fim de tornar o programa mais conciso. Por isso, muitas vezes é comum usar nomes como, por exemplo, i e j como contadores de comandos de repetição, e n como um número natural qualquer, arbitrário — de modo semelhante ao uso de letras gregas em matemática. Evite usar nomes como, por exemplo, aux, que são pouco significativos e poderiam ser substituídos por nomes mais concisos, caso não haja um nome que represente de forma concisa e mnemônica o propósito de uso de uma variável ou função.

Uma declaração de variável pode, opcionalmente, especificar também o valor a ser armazenado na variável, quando ela é criada — isto é, quando uma área de memória é alocada para essa variável.

Em C, a declaração:

char x; int y = 10; int z;

especifica que x é uma variável de tipo char, ou seja, que pode armazenar um caractere, e que y e z são variáveis de tipo int (variáveis inteiras, ou de tipo inteiro). A declaração da variável y especifica que o valor 10 deve ser armazenado nessa variável, quando ela é criada. Não são especificados valores iniciais para as variáveis x e z; em C, isso significa que um valor inicial indefinido (determinado de acordo com a configuração da memória no instante da execução do comando de declaração) é armazenado em cada uma dessas variáveis. A inexistência de inicialização implícita em C, e muitas outras características da linguagem, têm como principal motivação procurar proporcionar maior eficiência na execução de programas (ou seja, procuram fazer com que programas escritos em C levem menos tempo para serem executados).

Uma expressão de uma linguagem de programação é formada a partir de variáveis e constantes, usando funções ou operadores. Por exemplo, f(x+y) é uma expressão formada pela aplicação de uma função, de nome f, à expressão x+y, essa última formada pela aplicação do operador + às expressões x e y (nesse caso, variáveis). Toda linguagem de programação oferece um conjunto de funções e operadores predefinidos, que podem ser usados em expressões. Operadores sobre valores de tipos básicos predefinidos em C são apresentados no capítulo a seguir.

O valor de uma expressão (ou valor “retornado” pela expressão, como é comum dizer, em computação) é aquele obtido pela avaliação dessa expressão durante a execução do programa. Por exemplo, y+1 é uma expressão cujo valor é obtido somando 1 ao valor contido na variável y.

Na linguagem C, cada expressão tem um tipo, conhecido estaticamente — de acordo com a estrutura da expressão e os tipos dos seus componentes. Estaticamente significa durante a compilação (ou seja, antes da execução) ou, como é comum dizer em computação, “em tempo de compilação” (uma forma de dizer que tem influência da língua inglesa). Isso permite que um compilador C possa detectar “erros de tipo”, que são erros devidos ao uso de expressões em contextos em que o tipo não é apropriado. Por exemplo, supondo que + é um operador binário (deve ser chamado com dois argumentos para fornecer um resultado), seria detectado um erro na expressão x+, usada por exemplo int y = x+; — uma vez que não foram usados dois argumentos (um antes e outro depois do operador +) nessa expressão. Um dos objetivos do uso de tipos em linguagens de programação é permitir que erros sejam detectados, sendo uma mensagam de erro emitida pelo compilador, para que o programador possa corrigi-los (evitando assim que esses erros possam ocorrer durante a execução de programas).

A linguagem C provê os tipos básicos int, float, double, char e void. O tipo inteiro representa valores inteiros (sem parte fracionária).

Números de ponto flutuante (float ou double) contêm uma parte fracionária. Eles são representados em um computador por um valor inteiro correspondente à mantissa e um valor inteiro correspondente ao expoente do valor de ponto flutuante. A diferença entre float e double é de precisão: um valor de tipo double usa um espaço (número de bits) pelo menos igual, mas em geral maior do que um valor de tipo float.

Um valor de tipo char é usado para armanezar um caractere. Em C um valor de tipo char é um inteiro, sem sinal (com um tamanho menor ou igual ao de um inteiro).

Não existe valor de tipo void; esse tipo é usado basicamente para indicar que uma função não retorna nenhum resultado, ou não tem nenhum parâmetro.

Um comando de atribuição armazena um valor em uma variável. Em C, um comando de atribuição tem a forma:

v = e;

A execução desse comando tem o efeito de atribuir o valor resultante da avaliação da expressão e à variável v. Após essa atribuição, não se tem mais acesso, através de v, ao valor que estava armazenado anteriormente nessa variável — o comando de atribuição modifica o valor da variável.

É comum usar, indistintamente, os termos “valor armazenado em uma variável”, “valor contido em uma variável” ou, simplesmente, “valor de uma variável”.

Note o uso do símbolo = no comando de atribuição da linguagem C, diferente do seu uso mais comum, como símbolo de igualdade. Em C, o operador usado para teste de igualdade é ==. Usar = em vez de == é um erro cometido com freqüência por programadores iniciantes; é bom ficar atento para não cometer tal erro.

Note também a distinção existente entre o significado de uma variável v como variável alvo de um comando de atribuição, isto é, em uma ocorrência do lado esquerdo de um comando de atribuição, e o significado de um uso dessa variável em uma expressão: o uso de v como variável alvo de uma atribuição representa um “lugar” (o endereço da área de memória alocada para v, durante a execução do programa), e não o valor armazenado nessa área, como no caso em que a variável ocorre em uma expressão.

Por exemplo, supondo que o valor contido em uma variável inteira x seja 10, após a execução do comando de atribuição “ x = x + 1;”, o valor contido em x passa a ser 11: o uso de x no lado direito desse comando retorna o valor 10, ao passo que o uso de x do lado esquerdo desse comando representa uma posição de memória, onde o valor 11 é armazenado.

Um programa em linguagem como C pode incluir definições de novas funções, que podem então ser usadas em expressões desse programa. Em linguagens imperativas, a possibilidade de uso de comandos em definições de funções torna possível que a avaliação de uma expressão não apenas retorne um resultado, mas também modifique valores de variáveis. Quando uma expressão tem tal efeito, diz-se que tem um efeito colateral.

Em C, o próprio comando de atribuição é uma expressão (com efeito colateral). Por exemplo, supondo que a e b são duas variáveis inteiras, podemos escrever:

a = b = b + 1;

Na execução desse comando, o valor da expressão b + 1 é calculado, e então atribuído a b e, em seguida, atribuído a a. Se b contém, por exemplo, o valor 3, antes da execução desse comando, então a expressão b = b + 1 não só retorna o valor b + 1, igual a 4, como modifica o valor contido em b (que passa a ser 4).

O comando de atribuição não é o único comando que pode modificar o valor de uma variável. Isso ocorre também no caso de comandos de entrada de dados, também chamados de comandos de leitura, que transferem valores de dispositivos externos para variáveis. Um comando de leitura funciona basicamente como um comando de atribuição no qual o valor a ser armazenado na variável é obtido a partir de um dispositivo de entrada de dados. Comandos de entrada e saída de dados são abordados no Capítulo 3.

2.2  Composição Seqüencial

A composição seqüencial é a forma mais simples de combinação de comandos. A composição seqüencial de dois comandos c1 e c2 é escrita na forma:

c1; c2;

e consiste na execução de c1 e, em seguida, do comando c2.

Os comandos c1 e c2 podem, por sua vez, também ser formados por meio de composição seqüencial.

A composição seqüencial de comandos é naturalmente associativa (não sendo permitido o uso de parênteses). Por exemplo, a seqüência de comandos:

int a; int b; int c; a = 10; b = 20; c = a + b;

tem o efeito de:

  1. Declarar uma variável, de nome a, como tendo tipo int. Declarar uma variável significa alocar uma área de memória e associar um nome a essa área, que pode conter valores do tipo declarado.
  2. Analogamente, declarar variáveis b e c, de tipo int.
  3. Em seguida, atribuir o valor 10 à variável a.
  4. Em seguida, atribuir o valor 20 à variável b.
  5. Em seguida, atribuir o valor 30, resultante da avaliação de a + b, à variável c.

2.3  Seleção

Outra forma de combinação de comandos, a seleção, possibilita selecionar um comando para execução, conforme o valor de um determinado teste seja verdadeiro ou falso. Em C, a seleção é feita com o chamado “comando if”, que tem a seguinte forma:

if ( b ) c1; else c2;

Nesse comando, b tem que ser uma expressão de tipo int. A linguagem C não tem um tipo para representar diretamente os valores verdadeiro ou falso, e usa para isso o tipo int. A convenção usada é que, em um contexto como o da expressão b, em que um valor verdadeiro ou falso é esperado, 0 representa falso e qualquer valor diferente de zero representa verdadeiro.

Na execução do comando if, se o valor retornado pela avaliação de b for qualquer valor direrente de 0, o comando c1 é executado; se o valor retornado for 0, o comando c2 é executado.

A parte “else c2;” (chamada de “cláusula else”) é opcional. Se não for especificada, simplesmente nenhum comando é executado no caso em que a avaliação da expressão b retorna falso.

Em C, para se usar uma seqüência com mais de um comando no lugar de c1, ou no lugar de c2, devem ser usadas chaves para indicar o início e o fim da seqüência de comandos. Por exemplo:

if (a > 10) { a = a + 10; b = b + 1; } else { b = 0; if (c > 1) a = a + 5; }

Uma seqüência de comandos entre chaves é chamada de um bloco. Note que, em C, se um bloco for usado no lugar de um comando — como no comando if do exemplo anterior —, o caractere “;” não deve ser usado após o mesmo: o caractere “;” é usado como um terminador de comandos, devendo ocorrer após cada comando do programa, que não seja um bloco.

Existe também outra forma de comando de seleção, que seleciona um comando para ser executado, de acordo com o valor de uma expressão, dentre uma série de possibilidades, e não apenas duas, como no caso do comando if. Esse comando, tratado no Exercício Resolvido 6 do Capítulo 4, tem o mesmo efeito que uma seqüência de comandos if, com testes sucessivos sobre o valor da condição especificada nesse comando.

2.4  Repetição

Em um comando de repetição, um determinado comando, chamado de corpo do comando de repetição, é executado repetidas vezes, até que uma condição de terminação do comando de repetição se torne verdadeira, o que provoca o término da execução desse comando.

Cada avaliação da condição de terminação, seguida da execução do corpo desse comando, é denominada uma iteração, sendo o comando também chamado comando iterativo.

Para que a condição de terminação de um comando de repetição possa tornar-se verdadeira, depois de um certo número de iterações, é preciso que essa condição inclua alguma variável que tenha o seu valor modificado pela execução do corpo desse comando (mais especificamente exista um comando de atribuição no corpo do comando de repetição que modifique o valor de uma variável usada na condição de terminação).

O comando while é um comando de repetição, que tem, em C, a seguinte forma, onde b é a condição de terminação, e c, o corpo do comando:

while ( b ) c;

A execução desse comando consiste nos seguintes passos: antes de ser executado o corpo c, a condição b é avaliada; se o resultado for verdadeiro (isto é, diferente de 0), o comando c é executado, e esse processo se repete; senão (i.e., se o resultado da avaliação de b for falso), então a execução do comando while termina.

Devido a esse “laço” envolvendo b e depois c repetidamente, um comando de repetição é também chamado de laço (em inglês, loop).

Como exemplo de uso do comando while, considere o seguinte trecho de programa, que atribui à variável soma a soma dos valores inteiros de 1 a n:

soma = 0; i = 1; while (i <= n) { soma = soma + i; i = i + 1; }

Essa seqüência de comandos determina que, primeiramente, o valor 0 é armazenado em soma, depois o valor 1 é armazenado em i, e em seguida o comando while é executado.

Na execução do comando while, primeiramente é testado se o valor de i é menor ou igual a n. Se o resultado desse teste for falso (igual a 0), a execução do comando termina. Caso contrário, o corpo do comando while é executado, seguindo-se novo teste etc. A execução do corpo do comando while adiciona i ao valor da variável soma, e adiciona 1 ao valor armazenado na variável i, nessa ordem. Ao final da n-ésima iteração, o valor da variável i será, portanto, n+1. A avaliação da condição de terminação, no início da iteração seguinte, retorna então o valor falso (0), e a execução do comando while termina.

A linguagem C possui dois outros comandos de repetição, além do comando while: os comandos do-while e for. Esses comandos têm comportamento semelhante ao do comando while, e são abordados no Capítulo 4.

2.5  Funções e Procedimentos

Linguagens de programação de alto nível oferecem construções para definição de novas funções, que podem então ser usadas em expressões desse programa, aplicadas a argumentos apropriados. Dizemos que uma função constitui uma abstração sobre uma expressão, uma vez que representa uma expressão, possivelmente parametrizada sobre valores que ocorrem nessa expressão.

O uso de uma função em uma expressão é também chamado, em computação, de uma “chamada” a essa função.

De maneira análoga, um programa pode também incluir definições de procedimentos, que constituem abstrações sobre comandos — ou seja, um procedimento consiste em uma seqüência de comandos, possivelmente parametrizada sobre valores usados nesses comandos, podendo ser chamado em qualquer ponto do programa em que um comando pode ser usado.

A possibilidade de definição e uso de funções e procedimentos constitui um recurso fundamental para decomposição de programas, evitando duplicação de código e contribuindo para construção de programas mais concisos e legíveis, assim como mais fáceis de corrigir e modificar.

O termo função costuma também ser usado no lugar de procedimento, uma vez que em geral uma função pode usar efeitos colaterais (comandos de atribuição e outros comandos que alteram o valor de variáveis) em expressões.

Definições de funções são o tema dos nossos primeiros exemplos, no Capítulo 3.

2.5.1  Blocos, Escopo e Tempo de Vida de Variáveis

A execução de programas em linguagens de programação, como C por exemplo, é baseada de modo geral na alocação e liberação de variáveis e de memória para essas variáveis em uma estrutura de blocos. A cada função ou procedimento corresponde um bloco, que é o texto de programa correspondente ao corpo da função ou procedimento.

Um bloco pode no entanto, em linguagens como C, ser simplesmente um comando constituído por comandos que incluem pelo menos um comando de declaração de variável. Por exemplo, o seguinte programa define um bloco internamente à função main, onde é definida uma variável de mesmo nome que uma variável declarada na função main:

int main() { int x = 1; { int x=2; x = x+1; } int y = x+3; }

Um bloco determina o escopo e o tempo de vida de variáveis nele declaradas.

O escopo de uma variável v, criada em um comando de declaração que ocorre em um bloco b, é o trecho (conjunto de pontos) do programa em que a variável pode ser usada, para denotar essa variável v. Em geral, e em particular em C, o escopo de v é o trecho do bloco b que é textualmente seguinte à sua declaração, excluindo escopos internos ao bloco de variáveis declaradas com o mesmo nome. Essa regra costuma ser chamada: definir antes de usar.

Por exemplo, o escopo da variável x declarada em main é o trecho da função main que segue a declaração de x no bloco de main (i.e. int x = 1;), excluindo o escopo de x no bloco onde x é redeclarado (i.e. excluindo o escopo de x correspondente à declaração int x=2;). Note que o escopo de x declarado em main está restrito a essa função, não inclui outras funções que poderiam estar definidas antes ou depois de main.

O tempo de vida de uma variável é o intervalo da execução do programa entre sua criação e o término da existência da variável. Em uma linguagem baseada em uma estrutura de blocos, como C, o tempo de vida de uma variável, criada em um comando de declaradação, que ocorre em um bloco b, é o intervalo entre o início e o término da execução do bloco b (i.e. o intervalo entre o início da execução do primeiro e o último comandos do bloco).

As noções de tempo de vida e escopo de variáveis serão mais abordadas no Capítulo seguinte, ao tratarmos de chamadas de funções, em particular de chamadas de funçoes recursivas.

2.6  Outros Paradigmas de Programação

Além dos paradigmas de programação imperativo e orientado por objetos, existem, como mencionamos na introdução deste capítulo, outros paradigmas de programação, mais declarativos: o paradigma funcional e o paradigma lógico. Embora esses paradigmas sejam ainda relativamente pouco utilizados, o interesse por eles tem crescido de maneira significativa.

No paradigma funcional, um programa consiste, essencialmente, em uma coleção de definições de funções, cada qual na forma de uma série de equações. Por exemplo, a função que determina o fatorial de um número inteiro não-negativo n, poderia ser definida pela equação:

fatorial n = (1 se n=0, senão n * fatorial(n-1)) 

A execução de um programa em linguagem funcional consiste na avaliação de uma determinada expressão desse programa, que usa as funções nele definidas. O fato de que essas definições de funções podem também ser vistas como regras de computação estabelece o caráter operacional dessas linguagens.

Uma característica importante de linguagens funcionais é o fato de que possibilitam definir funções de ordem superior, isto é, funções que podem ter funções como parâmetros, ou retornar uma função como resultado. Essa característica facilita grandemente a decomposição de programas em componentes (funcionais) e a combinação desses componentes na construção de novos programas.

O maior interesse pela programação funcional apareceu a partir do desenvolvimento da linguagem ML e, mais recentemente, da linguagem Haskell (veja Notas Bibliográficas).

No paradigma lógico, um programa tem a forma de uma série de asserções (ou regras), que definem relações entre variáveis. A denominação dada a esse paradigma advém do fato de que a linguagem usada para especificar essas asserções é um subconjunto da Lógica de Primeira Ordem (também chamada de Lógica de Predicados). Esse subconjunto da lógica de primeira ordem usado em linguagens de programação em lógica usa asserções simples (formada por termos, ou expressões, que têm valor verdadeiro ou falso), da forma:

P  se  P1P2, …, Pn 

A interpretação declarativa dessa asserção é, informalmente, a de que P é verdadeiro se e somente se todos os termos P1, …, Pn (n≥ 0) forem verdadeiros. Além dessa interpretação declarativa, existe uma interpretação operacional: para executar (ou resolver) P, execute P1, depois P2, etc., até Pn, fornecendo o resultado verdadeiro se e somente se o resultado de cada uma das avaliações de P1, …, Pn fornecer resultado verdadeiro.

A linguagem Prolog é a linguagem de programação mais conhecida e representativa do paradigma de programação em lógica. Existe também, atualmente, um interesse expressivo em pesquisas com novas linguagens que exploram o paradigma de programação em lógica com restrições, e com linguagens que combinam a programação em lógica, a programação funcional e programação orientada por objetos (veja Notas Bibliográficas).

2.7  Exercícios

  1. Qual é o efeito das declarações de variáveis durante a execução do trecho de programa abaixo?
    int x; int y = 10;
  2. Quais são os valores armazenados nas variáveis x e y, ao final da execução do seguinte trecho de programa?
    int x; int y = 10; x = y * 3; while (x > y) { x = x - 5; y = y + 1; }
  3. Qual é o valor da variável s ao final da execução do seguinte trecho de programa, nos dois casos seguintes:
    1. as variáveis a e b têm, inicialmente, valores 5 e 10, respectivamente;
    2. as variáveis a e b têm, inicialmente, valores 8 e 2, respectivamente.
    s = 0; if (a > b) s = (a+b)/2; while (a <= b) { s = s + a; a = a + 1; b = b - 2; }

2.8  Notas Bibliográficas

Existem vários livros introdutórios sobre programação de computadores, a maioria deles em língua inglesa, abordando aspectos diversos da computação. Grande número desses livros adota a linguagem de programação PASCAL [12], que foi originalmente projetada, na década de 1970, especialmente para o ensino de programação. Dentre esses livros, citamos [8, 17].

A década de 1970 marcou o período da chamada programação estruturada [8], que demonstrou os méritos de programas estruturados, em contraposição à programação baseada em linguagens de mais baixo nível, mais semelhantes a linguagens de montagem ou linguagens de máquina. A linguagem Pascal, assim como outras linguagens também baseadas no paradigma de programação imperativo, como C [4], são ainda muito usadas no ensino introdutório de programação de computadores. Livros de introdução à programação de computadores baseados no uso de Pascal ou de linguagens similares, escritos em língua portuguesa, incluem, por exemplo, [3].

A partir do início da década de 1980, o desenvolvimento de software, em geral, e as linguagens de programação, em particular, passaram a explorar a idéia de decomposição de um sistema em partes e o conceito de módulo, originando o estilo de programação modular. As novas linguagens de programação desenvolvidas, tais como Modula-2 [18], Ada [11] e, mais tarde, Modula-3 [24], passaram a oferecer recursos para que partes de um programa pudessem ser desenvolvidas e compiladas separadamente, e combinadas de forma segura.

A programação orientada por objetos, que teve sua origem bastante cedo, com a linguagem Simula [20], só começou a despertar maior interesse a partir da segunda metade da década de 1980, após a definição da linguagem e do ambiente de programação Smalltalk [1]. A linguagem Smalltalk teve grande influência sobre as demais linguagens orientadas por objeto subseqüentes, tais como C++ [26], Eiffel [16] e Java [10].

A programação orientada por objetos explora adicionalmente, em relação à programação modular, o conceito de módulo como um tipo: uma parte de um programa, desenvolvida separadamente, constitui também um tipo, que pode ser usado como tipo de variáveis e expressões de um programa.

A influência da linguagem Java se deve, em grande parte, ao enorme crescimento do interesse por aplicações voltadas para a Internet, aliado às características do sistema de tipos da linguagem, que favorecem a construção de programas mais seguros, assim como ao grande número de classes e ferramentas existentes para suporte ao desenvolvimento de programas nessa linguagem.

O recente aumento do interesse por linguagens funcionais é devido, em grande parte, aos sistemas de tipos dessas linguagens. Os sistemas de tipos de linguagens funcionais modernas, como ML [21] e Haskell [28, 5], possibilitam a definição e uso de tipos e funções polimórficas, assim como a inferência automática de tipos.

Uma função polimórfica é uma função que opera sobre valores de tipos diferentes — todos eles instâncias de um tipo polimórfico mais geral. No caso de polimorfismo paramétrico, essa função apresenta um comportamento uniforme para valores de qualquer desses tipos, isto é, comportamento independente do tipo específico do valor ao qual a função é aplicada. De maneira semelhante, tipos polimórficos são tipos parametrizados por variáveis de tipo, de maneira que essas variáveis (e os tipos polimórficos correspondentes) podem ser “instanciadas”, fornecendo tipos específicos. Em pouco tempo de contacto com a programação funcional, o programador é capaz de perceber que os tipos das estruturas de dados e operações usadas repetidamente na tarefa de programação são, em sua grande maioria, polimórficos. Por exemplo, a função que calcula o tamanho (ou comprimento) de uma lista é polimórfica, pois seu comportamento independe do tipo dos elementos da lista.

Essas operações, usadas com freqüência em programas, são também, comumente, funções de ordem superior, isto é, funções que recebem funções como parâmetros ou retornam funções como resultado. Por exemplo, a operação de realizar uma determinada operação sobre os elementos de uma estrutura de dados pode ser implementada como uma função polimórfica de ordem superior, que recebe como argumento a função a ser aplicada a cada um dos elementos dessa estrutura.

A inferência de tipos, introduzida pioneiramente na linguagem ML, que também introduziu o sistema de tipos polimórficos, possibilita combinar duas características convenientes: a segurança e maior eficiência proporcionadas por sistemas com verificação de erros de tipo em tempo de compilação e a flexibilidade de não requerer que tipos de variáveis e expressões de um programa sejam especificados explicitamente pelo programador (essa facilidade era anteriormente encontrada apenas em linguagens que realizam verificação de tipos em tempo de execução, as quais são, por isso, menos seguras e menos eficientes).

Estes e vários outros temas interessantes, como o uso de estratégias de avaliação de expressões até então pouco exploradas e o modelamento de mudanças de estado em programação funcional, são ainda objeto de pesquisas na área de projeto de linguagens de programação. O leitor interessado nesses temas certamente encontrará material motivante nos livros sobre programação em linguagem funcional mencionados acima.

Ao leitor interessado em aprender mais sobre o paradigma de programação em lógica e a linguagem Prolog recomendamos a leitura de [14, 29].


Previous Up Next