1.1. Os Elementos da Programação

Uma linguagem de programação poderosa é mais do que apenas um meio de instruir um computador a realizar tarefas. A linguagem também serve como uma estrutura dentro da qual organizamos nossas idéias sobre processos. Assim, quando descrevemos uma linguagem, devemos prestar atenção especial aos meios que a linguagem oferece para combinar ideias simples para formar ideias mais complexas. Cada linguagem poderosa tem três mecanismos para fazer isso:

  • expressões primitivas, que representam as entidades mais simples com as quais a linguagem se preocupa,

  • meios de combinação, pelos quais os elementos compostos são construídos a partir de outros mais simples, e

  • meios de abstração, pelos quais elementos compostos podem ser nomeados e manipulados como unidades.

Em programação, lidamos com dois tipos de elementos: procedimentos e dados. (Mais tarde, descobriremos que eles realmente não são tão distintos.) Informalmente, dados são "coisas" que queremos manipular e procedimentos são descrições das regras para manipular os dados. Assim, qualquer linguagem de programação poderosa deve ser capaz de descrever dados primitivos e procedimentos primitivos e deve ter métodos para combinar e abstrair procedimentos e dados.

Neste capítulo, lidaremos apenas com dados numéricos simples para que possamos nos concentrar nas regras para a construção de procedimentos.*4 Nos capítulos posteriores, veremos que essas mesmas regras nos permitem construir procedimentos para manipular dados compostos também.

Nota 4: A caracterização de números como "dados simples" é um blefe descarado. Na verdade, o tratamento dos números é um dos aspectos mais complicados e confusos de qualquer linguagem de programação. Alguns problemas típicos envolvidos são os seguintes: Alguns sistemas de computador distinguem números inteiros, como 2, de números reais, como 2,71. O número real 2,00 é diferente do inteiro 2? As operações aritméticas usadas para números inteiros são iguais às operações usadas para números reais? 6 dividido por 2 produz 3 ou 3,0? Qual o maior número que podemos representar? Quantas casas decimais de precisão podemos representar? O intervalo de inteiros é igual ao intervalo de números reais? Acima e além dessas questões, é claro, está uma coleção de questões relacionadas a erros de arredondamento e truncamento — toda a ciência da análise numérica. Visto que nosso foco neste livro é o design de programas em grande escala, e não as técnicas numéricas, vamos ignorar esses problemas. Os exemplos numéricos neste capítulo exibirão o comportamento usual de arredondamento que se observa ao usar operações aritméticas que preservam um número limitado de casas decimais de precisão em operações não inteiras.

1.1.1. Expressões

Uma maneira fácil de começar a programar é examinar algumas interações típicas com um interpretador para o dialeto Scheme do Lisp. Imagine que você está sentado em um terminal de computador. Você digita uma expressão e o interpretador responde exibindo o resultado de sua avaliação dessa expressão.

Um tipo de expressão primitiva que você pode digitar é um número. (Mais precisamente, a expressão que você digita consiste nos numerais que representam o número na base 10.) Se você apresentar a Lisp um número:

486

O interpretador responderá imprimindo: *5

Nota 5: Ao longo deste livro, quando quisermos enfatizar a distinção entre a entrada digitada pelo usuário e a resposta impressa pelo intérprete, mostraremos o código em bloco de código com realce de sintaxe e os resultados em blocos de falha (vermelho) ou sucesso (verde) como o bloco logo acima.

As expressões que representam números podem ser combinadas com uma expressão que representa um procedimento primitivo (como + ou *) para formar uma expressão composta que representa a aplicação do procedimento a esses números. Por exemplo:

(+ 137 349)
(- 1000 334)
(* 5 99)
(/ 10 5)
(+ 2.7 10)

Expressões como essas, formadas pela delimitação de uma lista de expressões entre parênteses para denotar a aplicação do procedimento, são chamadas de combinações. O elemento mais à esquerda na lista é chamado de operador e os outros elementos são chamados de operandos. O valor de uma combinação é obtido aplicando o procedimento especificado pelo operador aos argumentos que são os valores dos operandos.

A convenção de colocar o operador à esquerda dos operandos é conhecida como notação de prefixo e pode ser um pouco confusa no início porque se afasta significativamente da convenção matemática usual. A notação de prefixo tem várias vantagens, entretanto. Uma delas é que pode acomodar procedimentos que podem receber um número arbitrário de argumentos, como nos exemplos a seguir:

(+ 21 35 12 7)

ou

(* 25 4 12)

Nenhuma ambiguidade pode surgir, porque o operador é sempre o elemento mais à esquerda e toda a combinação é delimitada por parênteses.

Uma segunda vantagem da notação de prefixo é que ela se estende de maneira direta para permitir que as combinações sejam aninhadas, ou seja, ter combinações cujos elementos são eles próprios combinações:

(+ (* 3 5) (- 10 6))

Não há limite (em princípio) para a profundidade de tal aninhamento e para a complexidade geral das expressões que o interpretador Lisp pode avaliar. Somos nós, humanos, que ficamos confusos com expressões ainda relativamente simples, como:

(+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6))

Podemos nos ajudar escrevendo tal expressão na forma:

(+ (* 3
      (+ (* 2 4)
         (+ 3 5)))
   (+ (- 10 7)
      6))

Seguindo uma convenção de formatação conhecida como impressão bonita, na qual cada combinação longa é escrita de forma que os operandos sejam alinhados verticalmente. As indentações resultantes mostram claramente a estrutura da expressão.*6

Nota 6: Os sistemas Lisp normalmente fornecem recursos para auxiliar o usuário na formatação de expressões. Dois recursos especialmente úteis são um que recua automaticamente para a posição de impressão bonita adequada sempre que uma nova linha é iniciada e outro que destaca o parêntese esquerdo correspondente sempre que um parêntese direito é digitado.

Mesmo com expressões complexas, o interpretador sempre opera no mesmo ciclo básico: ele lê uma expressão do terminal, avalia a expressão e imprime o resultado. Este modo de operação é frequentemente expresso dizendo-se que o interpretador é executado em um ciclo de leitura-avaliação-impressão. Observe em particular que não é necessário instruir explicitamente o interpretador a imprimir o valor da expressão.*7

Nota 7: Lisp obedece à convenção de que toda expressão tem um valor. Essa convenção, junto com a velha reputação do Lisp como uma linguagem ineficiente, é a fonte da piada de Alan Perlis (parafraseando Oscar Wilde) de que "os programadores Lisp sabem o valor de tudo, mas o custo de nada."

1.1.2. Nomenclatura e Ambiente

Last updated

Was this helpful?