Indice 1

Documentos relacionados
Inteligência Artificial

Tipo de argumentos. valor. argumentos

LISP - Introdução. Existem vários dialectos de LISP: COMMON LISP (o mais sofisticado e mais utilizado) Outros: MAC LISP, INTERLISP, XLISP

Fundamentos de Programação

2.2.5 EXPRESSÕES - Regras para o cálculo de valores

Resolução De Problemas Em Informática. Docente: Ana Paula Afonso Resolução de Problemas. 1. Analisar o problema

EXPRESSÕES ARITMÉTICAS PARTE 1

Linguagens de Programação. Marco A L Barbosa

Exercícios para Fundamentos da Programação Utilizando Múltiplos Paradigmas

Introdução à Programação Funcional. Conteúdo

Métodos de Programação I Ana Maria de Almeida

MA11 - Unidade 4 Representação Decimal dos Reais Semana 11/04 a 17/04

Expressões e sentença de atribuição

Capítulo 7. Expressões e Sentenças de Atribuição

Paradigmas de Linguagens de Programação. Expressões e Instruções de Atribuição

Conceitos de Linguagem de Programação - 2

Estruturação de Procedimentos

Linguagens de Programação

USANDO UM MÉTODO INDUTIVO PARA RESOLVER PROBLEMAS. Bruno Maffeo Departamento de Informática PUC-Rio

Exercícios da cadeira de Inteligência Artificial. Helena Sofia Pinto João Cachopo Daniel Gonçalves Carlos Lopes António Inês Lynce Pedro Matos

Aula 11: Desvios e Laços

Fundamentos de Programação

Programação de Computadores I Dados, Operadores e Expressões PROFESSORA CINTIA CAETANO

ALGORITMOS E APLICAÇÕES. FATEC IPIRANGA ADS Noturno 1º semestre de 2012 Prof. Luiz Carlos de Jesus Junior

Introdução a Algoritmos Parte 08

Fundamentos de Programação

4. Constantes. Constantes pré-definidas

5. Expressões aritméticas

Algoritmos e Técnicas de Programação Introdução Givanaldo Rocha de Souza

FACULDADE BATISTA MINEIRA - CST Banco de Dados Estruturas de Dados - Variáveis

Os elementos da programação

Linguagens de Programação

Computação Eletrônica. Tipos de dados, constantes, variáveis, operadores e expressões. Prof: Luciano Barbosa

Linguagens de Programação

Programação Funcional. Programação Funcional LISP. Paradigma de programação baseado em Funções Matemáticas

13 a Aula - Instruções Condicionais. Ciclos. Pré-processador. Variáveis de ambiente. Mestrado em Engenharia Física Tecnológica

Aula prática 5. Funções Recursivas

Derivadas de funções reais de variável real

Métodos Computacionais. Operadores, Expressões Aritméticas e Entrada/Saída de Dados

SEBENTA de Sistemas de Numeração

Conceitos Básicos de Programação

Alguns exercícios em pseudocódigo

1 Lógica de primeira ordem

Inteligência Artificial Taguspark

Fundamentos da Programação

Autômatos e Linguagens

Métodos de Programação I Ana Maria de Almeida

Fundamentos da Programação

Capítulo 2 Operadores. A função scanf()

Variáveis e Memória. Revisão. Conceitos. Operações sobre a memória

C Operadores e Expressões

Linguagens de Programação Funcional

Representações de Números Inteiros: Sinal e Magnitude e Representação em Excesso de k

Programação Introdução

Inteligência Artificial

Introdução à Programação Funcional

Programação I Estruturas de Decisão

Introdução à Programação com AutoLisp. António Menezes Leitão

Waldemar Celes e Roberto Ierusalimschy. 29 de Fevereiro de 2012

Ambiente Scilab Variáveis, expressões, comando de atribuição Entrada e Saída básica

Universidade Federal de Uberlândia Faculdade de Computação. Linguagem C: Operadores relacionais e lógicos estruturas condicionais If...

Uma introdução ao GAP

Fundamentos da Programação de Computadores

Universidade de Santa Cruz do Sul UNISC Departamento de informática COMPILADORES. Introdução. Geovane Griesang

ao paradigma imperativo

Introdução a Lógica de Programação

Introdução à Programação

ÍNDICE. PREFÁCIO xv. 3 PROCESSOS GERADOS POR PROCEDIMENTOS Recursão Linear Cálculo de potências Cálculo de factoriais 91

PROGRAMAÇÃO DE MICROPROCESSADORES 2007 / 2008

3. Linguagem de Programação C

Programação I Apresentação

1 Introdução à Programação O que é? Para que serve?... 1

Programação Python. Nesta aula... 1 Programação. 2 Python. Operadores e Operandos Variáveis, Expressões, Instruções Funções Ciclos Comentários

NESSES SLIDES, VOCÊ VAI APRENDER: Configuração de ambiente Variáveis Operadores Ambiguidade Condições e Laços

Aula de hoje. Expressões. Expressões. Expressões. Exemplos. Programa em Python. SCC Introdução à Programação para Engenharias

Resolução de Problemas com Computador. Resolução de Problemas com Computador. Resolução de Problemas com Computador

Procedimento. Função. Selecção Condicional - a instrução if-then-else. expressão if lógica then instrução else instrução

Conceitos Básicos de Algoritmos

Cursos: Análise, Ciência da Computação e Sistemas de Informação Laboratório I - Prof. Aníbal Notas de aula 2 SISTEMAS NUMÉRICOS

Nesta aula... Iteração indefinida. 1 Decisões em Python. 2 Funções lógicas. 3 Recursão. 4 Iteração. 5 Ciclo for ou while?

Working 03 : Conceitos Básicos I

Fundamentos de Programação

Programação de Computadores

Introdução à Programação. Operadores, Expressões Aritméticas e Entrada/Saída de Dados

Linguagem e Técnicas em Programação. Gilson de Souza Carvalho

Informática I. Aula 7. Aula 7-17/09/2007 1

Linguagens de Domínio Específico

Aula 4 - Operadores. Prof. Laura Silva de Assis. Engenharia de Computação 2 o Período

Apêndice A. Pseudo-Linguagem

Leituras de valores numéricos

Introdução à Computação

3. Linguagem de Programação C

Variáveis e Entrada de Dados Marco André Lopes Mendes marcoandre.googlepages.

Introdução à Programação. João Manuel R. S. Tavares

Inteligência Artificial Apontamentos para as aulas Luís Miguel Botelho

Informática para Ciências e Engenharias 2013/14. Teórica 7

Expressões e Instruções de Atribuição. George Darmiton da Cunha Cavalcanti

7 Operadores e Expressões

Desenvolvimento de Aplicações Desktop

Desenho e Análise de Algoritmos CC /2018. Folha 1 - Revisão: Escrita de algoritmos em pseudo-código e verificação de correção

Transcrição:

Introduc~ao a Linguagem Lisp Antonio Menezes Leit~ao Revis~ao de Jo~ao Cachopo Outubro 1995

Indice 1

1 Linguagem de Programac~ao Uma linguagem de programac~ao n~ao e apenas um meio de indicar a um computador uma serie de operac~oes a executar. Uma linguagem de programac~ao e, sobretudo, um meio de exprimirmos ideias acerca de metodologias. Uma linguagem de programac~ao deve ser feita para seres humanos dialogarem acerca de programas e, so incidentalmente, para computadores os executarem. Como tal, deve possuir ideias simples, deve ser capaz de combinar ideias simples para formar ideias mais complexas e deve ser capaz de realizar abstracc~oes de ideias complexas para as tornar simples. Existe uma enorme diversidade de linguagens de programac~ao, umas mais adaptadas a um determinado tipo de processos, outras mais adaptadas a outros. A escolha de uma linguagem de programac~ao deve estar condicionada, naturalmente, pelo tipo de problemas que queremos resolver, mas n~ao deve ser um comprometimento total. Para um programador e muito mais importante compreender os fundamentos e tecnicas da programac~ao do que dominar esta ou aquela linguagem. Lisp e uma linguagem din^amica, cujos programas s~ao constitudos por pequenos modulos, de funcionalidade generica e que cumprem um objectivo muito simples. E a sua combinac~ao que produz um programa completo. Os modulos desenvolvidos em Lisp possuem, geralmente, uma funcionalidade que ultrapassa largamente os objectivos para que foram concebidos. A n~ao tipicac~ao de dados, a possibilidade de tratar dados e programas de um mesmo modo e a indistinc~ao entre func~oes denidas pela linguagem e func~oes denidas pelo programador s~ao algumas das raz~oes da sua exibilidade. A linguagem Lisp nasceu como uma ferramenta matematica, independente de qualquer computador e so posteriormente se procedeu a sua adaptac~ao a uma maquina. Uma vez que em Lisp n~ao existia qualquer depend^encia, a priori, do processador que iria executar a linguagem, a linguagem tambem n~ao podia tirar partido das suas potencialidades, sendo as primeiras vers~oes muito inecientes. Esta ineci^encia resultava de os programas Lisp serem interpretados, sendo por isso muito mais lentos do que o que uma compilac~ao permite ao reescrever um programa na linguagem do processador. No entanto, com o aparecimento de compiladores ecazes e de um suporte cada vez maior da parte dos processadores, Lisp possui, actualmente, uma eci^encia comparavel a das restantes linguagens. Lisp da ao programador a capacidade de extender a linguagem como entender, permitindo incorporar outros estilos de programac~ao, como programac~ao orientada para objectos, programac~ao orientada para regras, etc. Quando surgem novos paradigmas de programac~ao, o Lisp incorpora-os facilmente enquanto as antigas linguagens morrem. Isto permite adaptar a linguagem ao problema que estamos a resolver em vez de termos de adaptar o problema a linguagem que estamos a usar. 2

A facilidade de utilizac~ao, adaptac~ao e extens~ao da linguagem Lisp fez com que surgissem dezenas de vers~oes diferentes: FranzLisp, ZetaLisp, LeLisp, MacLisp, InterLisp, Scheme, T, Nil, XLisp, AutoLisp, etc, para nomear apenas as mais relevantes. Esta multitude de dialectos comecou a tornar difcil a livre comunicac~ao entre os membros da comunidade Lisp. Para obviar este problema, criou-se um standard denominado Common Lisp com o objectivo de facilitar a troca de ideias (e programas). Sendo a linguagem Common Lisp o herdeiro legtimo de todas as outras, ela deve suportar a maioria das capacidades que estas possuem. Como e logico isto impede a estabilizac~ao da linguagem, que ainda n~ao parou de evoluir, sendo ampliada de tempos a tempos para incorporar novos (e velhos) paradigmas de programac~ao. Felizmente, essa ampliac~ao evita (na medida em que isso e possvel) alterar funcionalidades das vers~oes anteriores e assim um programa Common Lisp tem a garantia de funcionar independentemente do estado actual da linguagem. O programa pode n~ao estar tecnologicamente actual mas estara sempre funcionalmente actual. Lisp e uma linguagem interactiva. Cada porc~ao de um programa pode ser compilada, corrigida e testada independentemente das restantes. Deste modo, Lisp permite a criac~ao, teste e correcc~ao de programas de uma forma incremental, o que facilita muito a tarefa do programador. Quase todos as vers~oes de Lisp possuem simultaneamente um compilador e um interpretador, permitindo misturar livremente codigo compilado e interpretado e permitindo realizar compilac~oes incrementais. Embora os exemplos que iremos apresentar tenham sido ensaiados em Common Lisp, falaremos genericamente da linguagem Lisp. 2 O Avaliador Em Lisp, qualquer express~ao tem um valor. Este conceito e de tal modo importante que todas as implementac~oes da linguagem Lisp apresentam um avaliador, i.e., um pequeno programa destinado a interagir com o utilizador de modo a avaliar express~oes por este fornecidas. Assim, quando o utilizador comeca a trabalhar com o Lisp, e-lhe apresentado um sinal e o Lisp ca a espera que o utilizador lhe forneca uma express~ao. Welcome to Common Lisp! > O caracter \>" e a \prompt" do Lisp, a frente da qual v~ao aparecer as express~oes que o utilizador escrever. O Lisp interacciona com o utilizador executando um ciclo em que l^e uma express~ao, determina o seu valor e escreve o resultado. Este ciclo de acc~oes designa-se, tradicionalmente, de ciclo read-eval-print. Quando o Lisp l^e uma express~ao, constroi internamente um objecto que a representa. Esse e o papel da fase de leitura. Na fase de avaliac~ao, 3

o objecto construdo e analisado de modo a produzir um valor. Esta analise e feita empregando as regras da linguagem que determinam, para cada caso, qual o valor do objecto construdo. Finalmente, o valor produzido e apresentado ao utilizador na fase de escrita atraves de uma representac~ao textual desse valor. Dada a exist^encia do ciclo read-eval-print, em Lisp n~ao e necessario instruir o computador a escrever explicitamente os resultados de um calculo como, por exemplo, em Pascal. Isto permite que o teste e correcc~ao de erros seja bastante facilitada. A vantagem de Lisp ser uma linguagem interactiva esta na rapidez com que se desenvolvem prototipos de programas, escrevendo, testando e corrigindo pequenas func~oes de cada vez. Exerccio 2.0.1 Descubra como e que inicia e termina a sua interacc~ao com o Lisp. 3 Elementos da Linguagem Qualquer linguagem de programac~ao lida com duas especies de objectos: dados e procedimentos. Os dados s~ao os objectos que pretendemos manipular. Os procedimentos s~ao descric~oes das regras para manipular esses dados. Se considerarmos a linguagem da matematica, podemos identicar os numeros como dados e as operac~oes algebricas como procedimentos e podemos combinar os numeros entre si usando aquelas operac~oes. Por exemplo, 22 e uma combinac~ao, tal como 222 e 2222. No entanto, a menos que pretendamos car eternamente a resolver problemas de aritmetica elementar, somos obrigados a considerar operac~oes mais elaboradas que representam padr~oes de calculos. Neste ultimo exemplo, e evidente que o padr~ao que esta a emergir e o da operac~ao de potenciac~ao, i.e, multiplicac~ao sucessiva, tendo esta operac~ao sido denida na matematica ha ja muito tempo. Tal como a linguagem da matematica, uma linguagem de programac~ao deve possuir dados e procedimentos primitivos, deve ser capaz de combinar quer os dados quer os procedimentos para produzir dados e procedimentos mais complexos e deve ser capaz de abstrair padr~oes de calculo de modo a permitir trata-los como operac~oes simples, denindo novas operac~aoes que representem esses padr~oes de calculo. 3.1 Elementos primitivos Elementos primitivos s~ao as entidades mais simples com que a linguagem lida. Um numero, por exemplo, e um dado primitivo. Como dissemos anteriormente, o Lisp executa um ciclo read-evalprint. Isto implica que tudo o que escrevemos no Lisp tem de ser avaliado, i.e., tem de ter um valor, valor esse que o Lisp escreve no ecran. 4

Assim, se dermos um numero ao avaliador, ele devolve-nos o valor desse numero. Quanto vale um numero? O melhor que podemos dizer e que ele vale aquilo que vale. Por exemplo, o numero 1 vale 1. > 1 1 > 12345 12345 > 4.5 4.5 Como se v^e no exemplo, em Lisp, os numeros podem ser inteiros ou reais. Exerccio 3.1.1 Descubra qual e o maior real que o seu Lisp aceita. Consegue fazer o mesmo para os inteiros? 3.2 Combinac~oes Combinac~oes s~ao entidades complexas feitas a partir de entidades mais simples. Por exemplo, na matematicas numeros podem ser combinados usando operac~oes como a soma ou o produto. Como exemplo de combinac~oes matematicas, temos 1 + 2 e 1 + 2 3. A soma e o produto de numeros s~ao exemplos de operac~oes extremamente elementares consideradas procedimentos primitivos. Em Lisp, criam-se combinac~ao escrevendo uma sequ^encia de express~oes entre um par de par^enteses. Uma express~ao e um elemento primitivo ou uma outra combinac~ao. A express~ao (+ 1 2) e uma combinac~ao dos elementos primitivos 1 e 2 atraves do procedimento +. Ja no caso (+ 1 (* 2 3)) a combinac~ao e ocorre entre 1 e (* 2 3), sendo esta ultima express~ao uma outra combinac~ao. N~ao e difcil de ver que as unicas combinac~oes com utilidade s~ao aquelas em que as express~oes correspondem a operadores e operandos. Por convenc~ao, o Lisp considera que o primeiro elemento de uma combinac~ao e um operador e os restantes s~ao os operandos. A notac~ao que o Lisp utiliza para construir express~oes (operador primeiro e operandos a seguir) e designada por notac~ao prexa. Esta notac~ao costuma causar alguma perplexidade a quem inicia o estudo da linguagem, que espera uma notac~ao mais proxima da que aprendeu em aritmetica e que e usada habitualmente nas outras linguagens de programac~ao. Nestas, a express~ao (+ 1 (* 2 3)) e usualmente escrita na forma 1 + 2 * 3 (designada notac~ao inxa) que, normalmente, e mais simples de ler por um ser humano. No entanto, a notac~ao prexa usada pelo Lisp tem largas vantagens sobre a notac~ao inxa: E muito facil usar operadores que t^em um numero variavel de argumentos, como por exemplo: 5

> (+ 1 2 3) 6 > (+ 1 2 3 4 5 6 7 8 9 10) 55 Numa linguagem do tipo Pascal apenas existem operadores unarios ou binarios, e e necessario explicitar os operador binarios entre cada dois operandos: 1+2+3 ou 1+2+3+4+5+6+7+8+9+10. Um operador deste tipo diz-se inxo (in signica entre). Se se pretender um operador ternario (ou outro) ja n~ao se consegue escrever do mesmo modo, sendo necessario implementa-lo como uma func~ao. N~ao existe preced^encia entre os operadores. Em Pascal, a express~ao 1+2*3 tem de ser calculada como se se tivesse escrito 1+(2*3), e n~ao (1+2)*3, i.e., foi criada uma preced^encia que elimina as ambiguidades. Essa preced^encia pode ser alterada atraves do emprego de par^enteses. Em Lisp, as express~oes seriam necessariamente distintas, (+ 1 (* 2 3)) ou (* (+ 1 2) 3), n~ao podendo haver qualquer ambiguidade. Os operadores que nos denimos usam-se exactamente da mesma maneira que os operadores da linguagem: operador primeiro e operandos a seguir. Em Pascal, os operadores s~ao inxos, i.e., est~ao entre os operandos, e as func~oes e procedimentos por nos denidos s~ao prexos, i.e., primeiro a func~ao ou procedimento e depois os operandos. Isto impede a extens~ao da linguagem de uma forma coerente. Para exemplicar este ultimo aspecto, consideremos a operac~ao de exponenciac~ao em Pascal. Para ser coerente com o resto da linguagem, deveria existir um operador, por exemplo ** que permitisse escrever 3**4 para indicar a quarta pot^encia de 3. Como esse operador n~ao existe na linguagem (standard), somos obrigados a criar uma func~ao que o substitua mas, neste caso, a sintaxe muda radicalmente. Esta func~ao, como se usa de forma prexa n~ao se pode tornar coerente com as restantes operac~oes do Pascal, como a soma e a multiplicac~ao. Em Lisp, pelo contrario, tanto podemos escrever (* 3 3 3 3) como denir uma func~ao que permita escrever (** 3 4). A desvantagem da notac~ao prexa esta na escrita de combinac~oes complexas. A express~ao Pascal 1+2*3-4/5*6, equivale a express~ao Lisp (- (+ 1 (* 2 3)) (* (/ 4 5) 6)) que embora seja mais simples de ler para uma maquina, pode ser mais difcil de ler para um ser humano devido a acumulac~ao de par^enteses. No entanto, esta express~ao pode ser escrita de forma mais clara usando indentac~ao. A regra para indentac~ao de combinac~oes Lisp e extremamente simples. Numa linha coloca-se o operador e o primeiro operando. Os restantes operandos v^em alinhados por debaixo do primeiro. 6

(- (+ 1 (* 2 3)) (* (/ 4 5) 6)) Quando a regra de indentac~ao n~ao e suciente, usam-se pequenas variac~oes, como seja colocar o operador numa linha e os operandos por debaixo, por exemplo: (umaoperacaocomumnomemuitogrande 1 2 3 4) A indentac~ao e fundamental em Lisp pois e muito facil escrever codigo complexo. A grande maioria dos editores preparados para Lisp (Emacs, por exemplo) formatam automaticamente os programas a medida que os escrevemos e mostram o emparelhamento de par^enteses. Desta forma, apos algum tempo de pratica, torna-se muito facil escrever e ler os programas, por mais complexa que possa parecer a sua estrutura. Exerccio 3.2.1 Converta as seguintes express~oes da notac~ao inxa da aritmetica para a notac~ao prexa do Lisp: 1 + 2-3 1-2 * 3 1 * 2-3 1 * 2 * 3 (1-2) * 3 (1-2) + 3 1 - (2 + 3) 2 * 2 + 3 * 3 * 3 Soluc~ao do Exerccio 3.2.1 (- (+ 1 2) 3) ou (+ 1 (- 2 3)) ou (+ 1 2 (- 3)) (- 1 (* 2 3)) (- (* 1 2) 3) (* (* 1 2) 3) ou (* 1 (* 2 3)) ou (* 1 2 3) (* (- 1 2) 3) (+ (- 1 2) 3) (- 1 (+ 2 3)) (+ (* 2 2) (* 3 3 3) Exerccio 3.2.2 Converta as seguintes express~oes da notac~ao prexa do Lisp para a notac~ao inxa da aritmetica: (* (/ 1 2) 3) (* 1 (- 2 3)) (/ (+ 1 2) 3) (/ (/ 1 2) 3) (/ 1 (/ 2 3)) (- (- 1 2) 3) (- 1 2 3) 7

Soluc~ao do Exerccio 3.2.2 1 / 2 * 3 1 * (2-3) (1 + 2) / 3 1 / 2 / 3 1 / (2 / 3) (1-2) - 3 1-2 - 3 3.3 Avaliac~ao de Combinac~oes Como ja vimos, o Lisp considera que o primeiro elemento de uma combinac~ao e um operador e os restantes s~ao os operandos. O avaliador determina o valor de uma combinac~ao como o resultado de aplicar o procedimento especicado pelo operador ao valor dos operandos. O valor de cada operando e designado de argumento do procedimento. Assim, o valor da combinac~ao (+ 1 (* 2 3)) e o resultado de somar o valor de 1 com o valor de (* 2 3). Como ja se viu, 1 vale 1 e (* 2 3) e uma combinac~ao cujo valor e o resultado de multiplicar o valor de 2 pelo valor de 3, o que da 6, e que somado a 1 da 7. > (+ 1 2) 3 > (+ 1 (* 2 3)) 7 Exerccio 3.3.1 Calcule o valor das seguintes express~oes Lisp: (* (/ 1 2) 3) (* 1 (- 2 3)) (/ (+ 1 2) 3) (/ (/ 1 2) 3) (/ 1 (/ 2 3)) (- (- 1 2) 3) (- 1 2 3) (- 1) Soluc~ao do Exerccio 3.3.1 3/2-1 1 1/6 3/2-4 -4-1 3.4 Denic~ao de Func~oes Tal como em matematica, pode-se denir numa linguagem de programac~ao a operac~ao de elevar um numero ao quadrado. Assim, se pretendermos determinar o produto de um numero por ele proprio, escrevemos a combinac~ao (* x x) sendo x o numero, i.e. 8

> (* 5 5) 25 > (* 6 6) 36 Os numeros 5 e 6 e a operac~ao * s~ao elementos primitivos. As express~oes (* 5 5) e (* 6 6) s~ao combinac~oes. A combinac~ao generica (* x x) queremos associar um nome, por exemplo, quadrado. Isso equivale a acrescentar uma nova func~ao a linguagem. A ttulo de exemplo, vamos denir a func~ao quadrado: > (defun quadrado (x) (* x x)) quadrado Para se denirem novas func~oes em Lisp, e necessario criar uma combinac~ao de quatro elementos. O primeiro elemento desta combinac~ao e a palavra defun, que informa o avaliador que estamos a denir uma func~ao. O nome defun e uma abreviatura de \dene function". O segundo elemento e o nome da func~ao que queremos denir, o terceiro elemento e uma combinac~ao com os par^ametros da func~ao e o quarto elemento e a combinac~ao que determina o valor da func~ao para aqueles par^ametros. Quando se da uma express~ao desta forma ao avaliador, ele acrescenta a func~ao ao conjunto de func~oes da linguagem, associando-a ao nome que lhe demos e devolve como valor da denic~ao o nome da func~ao denida. A denic~ao da func~ao quadrado diz que para se determinar o quadrado de um numero x, devemos multiplicar esse numero por ele proprio (* x x). Esta denic~ao associa a palavra quadrado a um procedimento. Este procedimento possui par^ametros e um corpo de express~oes. De forma generica temos: (defun nome (par^ametro 1... par^ametro n ) corpo ) Os par^ametros de um procedimento s~ao designados par^ametros formais e s~ao os nomes usados no corpo de express~oes para nos referirmos aos argumentos correspondentes. Quando escrevemos no avaliador de Lisp (quadrado 5), 5 e o argumento. Durante o calculo da func~ao este argumento esta associado ao par^ametro formal x. Os argumentos de uma func~ao s~ao tambem designados par^ametros actuais. > (quadrado 5) 25 > (quadrado 6) 36 Note-se que a regra de avaliac~ao de combinac~oes e tambem valida para as func~oes por nos denidas. Assim, a avaliac~ao da express~ao 9

(quadrado (+ 1 2)) passa pela avaliac~ao do operando (+ 1 2). Este operando tem como valor 3, valor esse que e usado pela func~ao no lugar do par^ametro x. O corpo da func~ao e ent~ao avaliado, i.e., o valor nal sera o da combinac~ao (* 3 3). O seguinte exemplo mostra um caso um pouco mais complexo. Nele est~ao apresentadas as etapas de avaliac~ao dos operandos e de avaliac~ao do corpo da func~ao. (quadrado (quadrado (+ 1 2))) (quadrado (quadrado 3)) (quadrado (* 3 3)) (quadrado 9) (* 9 9) 81 A denic~ao de func~oes permite-nos associar um procedimento a um nome. Isto implica que o Lisp tem de possuir uma memoria onde possa guardar o procedimento e a sua associac~ao ao nome dado. Esta memoria do Lisp designa-se ambiente. Note-se que este ambiente apenas existe enquanto estamos a trabalhar com a linguagem. Quando terminamos, perde-se todo o ambiente. Isto implica que, se n~ao queremos perder o trabalho que estivemos a escrever, devemos escrever as func~oes num cheiro e ir passando-as para o Lisp. A grande maioria das implementac~oes do Lisp permite fazer isto de forma automatica. 3.5 Smbolos A denic~ao de func~oes em Lisp passa pela utilizac~ao dos seus nomes. Nomes para as func~oes e nomes para os par^ametros das func~oes. Uma vez que o Lisp, antes de avaliar qualquer express~ao, tem de a ler e converter internamente para um objecto, os nomes que criamos t^em de ter um objecto associado. Esse objecto e designado por smbolo. Um smbolo e, pois, a representac~ao interna de um nome. Em Lisp, quase n~ao existem limitac~oes para a escrita de nomes. Um nome como quadrado e t~ao bom como + ou como 1+2*3 pois o que separa um nome dos outros elementos de uma combinac~ao s~ao apenas par^enteses e espacos em branco. Por exemplo, e perfeitamente correcto denir e usar a seguinte func~ao: > (defun x+y*z (x y z) x+y*z (+ (* y z) x)) > (x+y*z 1 2 3) 7 Lisp atribui um signicado muito especial aos smbolos. Reparemos que, quando denimos uma func~ao, os par^ametros formais da func~ao 10

s~ao smbolos. O nome da func~ao tambem e um smbolo. Quando escrevemos uma combinac~ao, o avaliador de Lisp usa a denic~ao de func~ao que foi associada ao smbolo que constitui o primeiro elemento da combinac~ao. Quando, no corpo de uma func~ao, usamos um smbolo para nos referimos a um par^ametro formal, esse smbolo tem como valor o respectivo par^ametro actual que lhe foi associado naquela combinac~ao. Por esta descric~ao se v^e que os smbolos constituem um dos elementos fundamentais da linguagem Lisp. 4 Express~oes Condicionais Existem muitas operac~oes cujo resultado depende da realizac~ao de um determinado teste. Por exemplo, a func~ao matematica abs que calcula o valor absoluto de um numero equivale ao proprio numero, se este e positivo, ou equivale ao seu simetrico se for negativo. Estas express~oes, cujo valor depende de um ou mais testes a realizar previamente, permitindo escolher vias diferentes para a obtenc~ao do resultado, s~ao designadas express~oes condicionais. No caso mais simples de uma express~ao condicional, existem apenas duas alternativas a seguir. Isto implica que o teste que e necessario realizar para determinar a via de calculo a seguir deve produzir um de dois valores, em que cada valor designa uma das vias. Seguindo o mesmo racioccio, uma express~ao condicional com mais de duas alternativas devera implicar um teste com igual numero de possveis resultados. Uma express~ao da forma \caso o valor do teste seja 1, 2 ou 3, o valor da express~ao e 10, 20 ou 30, respectivamente" e um exemplo desta categoria de express~oes que, embora seja considerada pouco intuitiva, existe nalgumas linguagens (Basic, por exemplo). Contudo, estas express~oes podem ser facilmente reduzidas a primeira. Basta decompor a express~ao condicional multipla numa composic~ao de express~oes condicionais simples, em que o teste original e tambem decomposto numa serie de testes simples. Assim, poderiamos transformar o exemplo anterior em: \se o valor do teste e 1, o resultado e 10, caso contrario, se o valor e 2, o resultado e 20, caso contrario e 30". 4.1 Predicados Desta forma, reduzimos todos os testes a express~oes cujo valor pode ser apenas um de dois, e a express~ao condicional assume a forma de \se... ent~ao... caso contrario... ". Nesta situac~ao, a func~ao usada como teste e denominada predicado e o valor do teste e interpretado como sendo verdadeiro ou falso. Nesta optica, o facto de se considerar o valor como verdadeiro ou falso n~ao implica que o valor seja de um tipo de dados especial, como o booleano ou logico de algumas linguagens (como o Pascal), mas apenas que a express~ao condicional considera alguns dos valores como representando o verdadeiro e os restantes como o falso. 11

Em Lisp, as express~oes condicionais consideram como falso um unico valor. Esse valor e representado por nil. Qualquer outro valor diferente de nil e considerado como verdadeiro. Assim, do ponto de vista de uma express~ao condicional, qualquer numero e um valor verdadeiro. Contudo, n~ao faz muito sentido para o utilizador humano considerar um numero como verdadeiro ou falso, pelo que se introduziu uma constante na linguagem para representar verdade. Essa constante representa-se por t. Note-se que, se t e diferente de nil, e se nil e o unico valor que representa a falsidade, ent~ao t representa verdade. Desta forma, existem muitos predicados em Lisp cujo valor e t quando a express~ao por eles designada e considerada verdadeira. > (> 4 3) t > (< 4 3) nil Existem muitos predicados em Lisp. Os predicados numericos mais usados s~ao o zerop, =, >, <, >=, <=. O zerop testa se um numero e zero ou diferente de zero. > (zerop 1) nil > (zerop 0) t O facto de zerop terminar com a letra \p" deve-se a uma convenc~ao adoptada em Common Lisp segundo a qual os predicados devem ser distinguidos das restantes func~oes atraves da concatenac~ao da letra \p" (de predicate) ao seu nome. Apesar da adopc~ao dos smbolos t e nil, convem alertar que nem todos os predicados devolvem t ou nil exclusivamente. Alguns ha que, quando querem indicar verdade, devolvem valores diferentes de t (e de nil, obviamente). 4.2 Operadores Logicos Para se poder combinar express~oes logicas entre si existem os operadores and, or e not. O and e o or recebem qualquer numero de argumentos. O not so recebe um. O valor das combinac~oes que empregam estes operadores logicos e determinado do seguinte modo: O and avalia os seus argumentos da esquerda para a direita ate que um deles seja falso, devolvendo este valor. Se nenhum for falso o and devolve o valor do ultimo argumento. O or avalia os seus argumentos da esquerda para a direita ate que um deles seja verdade, devolvendo este valor. Se nenhum for verdade o or devolve o valor do ultimo argumento. 12

O not avalia para verdade se o seu argumento for falso e para falso em caso contrario. Note-se que embora o signicado de falso seja claro pois corresponde necessariamente ao valor nil, o signicado de verdade ja n~ao e t~ao claro pois, desde que seja diferente de nil, e considerado verdade. Exerccio 4.2.1 Qual o valor das seguintes express~oes? (and (or (> 2 3) (not (= 2 3))) (< 2 3)) (not (or (= 1 2) (= 2 3))) (or (< 1 2) (= 1 2) (> 1 2)) (and 1 2 3) (or 1 2 3) (and nil 2 3) (or nil nil 3) Soluc~ao do Exerccio 4.2.1 T T T 3 1 nil 3 4.3 Selecc~ao simples O if e a express~ao condicional mais simples do Lisp. O if determina a via a seguir em func~ao do valor de uma express~ao logica. A sintaxe do if e: (if condic~ao consequente alternativa ) Para o if, a condic~ao e o primeiro argumento, o consequente no caso de a condic~ao ser verdade e o segundo argumento e a alternativa no caso de ser falso e o terceiro argumento. > (if (> 4 3) 5 5 6) Uma express~ao if e avaliada determinando o valor da condic~ao. Se ela for verdade, e avaliado o consequente. Se ela for falsa e avaliada a alternativa. Exerccio 4.3.1 Dena uma func~ao soma-grandes que recebe tr^es numeros como argumento e determina a soma dos dois maiores. 13

Soluc~ao do Exerccio 4.3.1 (defun soma-grandes (x y z) (if (>= x y) (if (>= y z) (+ x y) (+ x z)) (if (< x z) (+ y z) (+ x y)))) Exerccio 4.3.2 Escreva uma func~ao que calcule o factorial de um numero. Soluc~ao do Exerccio 4.3.2 (defun fact (n) (if (zerop n) 1 (* n (fact (- n 1))))) A func~ao fact e um exemplo de uma func~ao recursiva, i.e., que se refere a ela propria. 4.4 Selecc~ao Multipla Para alem do if, existem outras express~oes condicionais em Lisp. O cond e uma vers~ao mais potente que o if. E uma especie de switch-case do C. A sua sintaxe e: (cond (condic~ao 1 express~ao 1 ) (condic~ao 2 express~ao 2 ). (condic~ao n express~ao n )) Designa-se o par (condic~ao i express~ao i ) por clausula. O cond testa cada uma das condic~oes em sequ^encia, e quando uma delas avalia para verdade, e devolvido o valor da express~ao correspondente, terminando a avaliac~ao. Um exemplo sera: > (cond ((> 4 3) 5) 5 (t 6)) O cond permite uma analise de casos mais simples do que o if. (defun teste (x y z w) (cond ((> x y) z) ((< (+ x w) (* y z)) x) ((= w z) (+ x y)) (t 777))) A func~ao equivalente usando if seria mais complicada. 14

(defun teste (x y z w) (if (> x y) z (if (< (+ x w) (* y z)) x (if (= w z) (+ x y) 777)))) 4.5 Formas Especiais Dada a similitude entre o if e o cond e lcito perguntar se precisaremos dos dois. Sera que n~ao e possvel denir um em termos do outro? A seguinte denic~ao parece ser uma resposta: > (defun meu-if (condicao consequente alternativa) meu-if (cond (condicao consequente) (t alternativa))) > (meu-if (> 4 3) 5 6) 5 > (meu-if (> 3 4) 5 6) 6 Quando testamos o meu-if, tudo parece bem. No entanto, se escrevermos um exemplo ligeiramente diferente, algo corre mal. > (defun divide (x y) divide (meu-if (zerop y) 0 > (divide 2 0) Error:... (/ x y))) O que e que esta a acontecer? Tentemos seguir as regras do avaliador para explicar a situac~ao. A avaliac~ao de combinac~oes implica a aplicac~ao da func~ao que e o primeiro elemento da combinac~ao aos valores dos restantes elementos. No exemplo (divide 2 0), a aplicac~ao da func~ao divide ao valor de 2 e 0 e o valor da combinac~ao (meu-if (zerop 0) 0 (/ 2 0)), que e a aplicac~ao da func~ao meu-if ao valor de (zerop 0) que e t, 0 que vale 0 e (/ 2 0) que e um erro pois n~ao se pode dividir 2 por 0. Desta forma, a func~ao meu-if avalia o seu ultimo argumento cedo demais, n~ao esperando pelo valor de (zerop 0) para saber se pode avaliar (/ 2 0). Mas ha situac~oes piores. Consideremos a seguinte denic~ao e aplicac~ao da func~ao factorial. 15

> (defun meu-fact (n) (meu-if (zerop n) meu-fact 1 > (meu-fact 4) Error:... (* n (meu-fact (- n 1))))) Nesta situac~ao, a avaliac~ao de combinac~oes implica a aplicac~ao da func~ao que e o primeiro elemento da combinac~ao aos valores dos restantes elementos. No exemplo (meu-fact 4), a aplicac~ao da func~ao meu-fact ao valor de 4 e o valor da combinac~ao (meu-if (zerop 4) 1 (* 4 (meu-fact (- 4 1)))), que e a aplicac~ao da func~ao meu-if ao valor de (zerop 4) que e nil, 1 que vale 1 e (meu-fact 3) que e a aplicac~ao da func~ao meu-fact ao valor 3, que e o valor da combinac~ao... Desta forma, a func~ao meu-if n~ao chega sequer a completar a avaliac~ao dos seus argumentos, n~ao podendo determinar qual dos valores, consequente ou alternativa, retornar, repetindo indenidamente a aplicac~ao da func~ao meu-fact a argumentos sucessivamente decrescentes. Suponhamos agora a seguinte interacc~ao com o Lisp: > (if (> 4 3) 100 100 (inexistente)) Segundo o modelo de avaliac~ao que tnhamos apresentado, uma combinac~ao e avaliada aplicando o procedimento que o primeiro elemento da combinac~ao especica ao valor dos restantes elementos. Nesta optica, antes de se escolher a opc~ao a seguir, o avaliador deveria avaliar todas elas, i.e., 100 cujo valor e 100 e a aplicac~ao da func~ao inexistente que devia produzir um erro pois a func~ao ainda n~ao foi denida. Porque sera que quando testamos isto n~ao e produzido nenhum erro? E evidente que, de algum modo, o if n~ao seguiu as regras do modelo de avaliac~ao de combinac~oes, caso contrario, teria mesmo produzido um erro. Isto sugere que if n~ao e uma func~ao normal mas sim algo que possui a sua propria regra de avaliac~ao. Uma forma especial e uma express~ao da linguagem que possui a sua propria sintaxe e a sua propria regra de avaliac~ao. E de salientar que uma forma especial n~ao e uma func~ao. Ela faz parte da estrutura do avaliador e n~ao do seu ambiente. O defun, o if e o cond s~ao algumas das formas especiais. Mas o and e o or tambem s~ao, pois so avaliam os operandos que forem necessarios para determinar o resultado. O and para de avaliar quando um deles produzir falso, o or quando um deles produzir verdade. Como uma forma especial possui a sua propria regra de avaliac~ao, nem tudo o que se faz com uma func~ao se pode fazer com uma forma especial, e nem tudo o que se faz com uma forma especial se pode fazer com uma func~ao. 16

Ao contrario das outras linguagens que possuem muitas formas especiais, Lisp tem muito poucas. Desta forma, a linguagem possui uma regra de avaliac~ao muito simples e muito bem denida, e em que as pequenas excepc~oes s~ao implementadas pelas formas especiais. No entanto, e ao contrario do que acontece na maioria das outras linguagens, Lisp permite ao utilizador denir novas formas cujo comportamento e semelhante ao das formas especiais. Isso e feito atraves de macros, que s~ao formas que s~ao transformadas noutras formas (especiais ou n~ao) durante a interpretac~ao ou a compilac~ao. 5 Func~oes 5.1 Func~oes Recursivas Uma func~ao recursiva e uma func~ao que se refere a si propria. A ideia consiste em utilizar a propria func~ao que estamos a denir na sua denic~ao. Em todas as func~oes recursivas existe: Um passo basico (ou mais) cujo resultado e imediatamente conhecido. Um passo recursivo em que se tenta resolver um sub-problema do problema inicial. Se analisarmos a func~ao factorial, o caso basico e o teste de igualdade a zero (zerop n), o resultado imediato e 1, e o passo recursivo e (* n (fact (- n 1))). Geralmente, uma func~ao recursiva so funciona se tiver uma express~ao condicional, mas n~ao e obrigatorio que assim seja. A execuc~ao de uma func~ao recursiva consiste em ir resolvendo subproblemas sucessivamente mais simples ate se atingir o caso mais simples de todos, cujo resultado e imediato. Desta forma, o padr~ao mais comum para escrever uma func~ao recursiva e: Comecar por testar os casos mais simples. Fazer chamada (ou chamadas) recursivas com subproblemas cada vez mais proximos dos casos mais simples. Dado este padr~ao, os erros mais comuns associados as func~oes recursivas s~ao, naturalmente: N~ao detectar os casos simples A recurs~ao n~ao diminuir a complexidade do problema. 17

No caso de erro em func~ao recursivas, o mais usual e a recurs~ao nunca parar. O numero de chamadas recursivas cresce indenidamente ate esgotar a memoria (stack), e o programa gera um erro. Em certas linguagens (Scheme) e implementac~oes do Common Lisp, isto n~ao e assim, e pode nunca ser gerado um erro. A recurs~ao innita e o equivalente das func~oes recursivas aos ciclos innitos dos metodos iterativos do tipo while-do e repeat-until. Repare-se que uma func~ao recursiva que funciona perfeitamente para os casos para que foi prevista pode estar completamente errada para outros casos. A func~ao fact e um exemplo. Quando o argumento e negativo, o problema torna-se cada vez mais complexo, cada vez mais longe do caso simples. (fact -1)! (fact -2)! (fact -3)! Exerccio 5.1.1 Considere uma vers~ao extremamente primitiva da linguagem Lisp, em que as unicas func~oes numericas existentes s~ao zerop e duas func~oes que incrementam e decrementam o seu argumento em uma unidade, respectivamente, 1+ e 1-. Isto implica que as operac~oes >, <, = e similares n~ao podem ser utilizadas. Nesta linguagem, que passaremos a designar por nanolisp, abreviadamente Lisp, dena o predicado menor, que recebe dois numero inteiros positivos e determina se o primeiro argumento e numericamente inferior ao segundo. Soluc~ao do Exerccio 5.1.1 (defun menor (x y) (cond ((zerop y) nil) ((zerop x) t) (t (menor (1- x) (1- y))))) Exerccio 5.1.2 Dena a operac~ao igual? que testa igualdade numerica de inteiros positivos na linguagem Lisp. Soluc~ao do Exerccio 5.1.2 (defun igual? (x y) (cond ((zerop x) (zerop y)) ((zerop y) nil) (t (igual? (1- x) (1- y))))) Exerccio 5.1.3 Ate ao momento, a linguagem Lisp apenas trabalha com numeros inteiros positivos. Admitindo que as operac~oes 1+, 1- e zerop tambem funcionam com numeros negativos, dena a func~ao negativo que recebe um numero inteiro positivo e retorna o seu simetrico. Assim, pretendemos obter: (negativo 3)! -3. Soluc~ao do Exerccio 5.1.3 (defun negativo (x) (if (zerop x) x (1- (negativo (1- x))))) Exerccio 5.1.4 Agora que a linguagem Lisp pode tambem trabalhar com numeros inteiros negativos, dena o predicado positivo?, que recebe um numero e indica se ele e positivo ou n~ao. 18

Soluc~ao do Exerccio 5.1.4 (defun positivo? (x) (positivo-aux x x)) (defun positivo-aux (x+ x-) (cond ((zerop x-) t) ((zerop x+) nil) (t (positivo-aux (1+ x+) (1- x-))))) Exerccio 5.1.5 Dena o teste de igualdade de dois numeros na linguagem Lisp contemplando a possibilidade de trabalhar tambem com numeros inteiros negativos. Soluc~ao do Exerccio 5.1.5 (defun igual? (x y) (igual-aux x x y y)) (defun igual-aux (x+ x- y+ y-) (cond ((zerop x+) (zerop y+)) ((zerop x-) (zerop y-)) ((or (zerop y+) (zerop y-)) nil) (t (igual-aux (1+ x+) (1- x-) (1+ y+) (1- y-))))) Exerccio 5.1.6 Dena a func~ao simetrico de um numero qualquer na linguagem Lisp. Soluc~ao do Exerccio 5.1.6 (defun simetrico (x) (simetrico-aux x x 0 0)) (defun simetrico-aux (x+ x- -x+ -x-) (cond ((zerop x+) -x+) ((zerop x-) -x-) (t (simetrico-aux (1+ x+) (1- x-) (1+ -x+) (1- -x-))))) Exerccio 5.1.7 E possvel denir a soma de dois numeros inteiros positivos em Lisp, i.e., apenas recorrendo as func~oes 1+ e 1- que somam e subtraem uma unidade, respectivamente. Dena a operac~ao soma. Soluc~ao do Exerccio 5.1.7 (defun soma1 (a b) (if (zerop a) b (1+ (soma1 (1- a) b)))) Exerccio 5.1.8 Generalize a func~ao de soma de modo a poder receber numeros inteiros positivos e negativos. Soluc~ao do Exerccio 5.1.8 (defun soma-geral (a b) (soma-iter a a b b)) (defun soma-iter (a+ a- b+ b-) (cond ((zerop a+) b-) ((zerop a-) b+) (t (soma-iter (1+ a+) (1- a-) (1+ b+) (1- b-))))) 19

Exerccio 5.1.9 Do mesmo modo que a soma pode ser denida exclusivamente em termos de sucessor 1+ e predecessor 1-, a multiplicac~ao pode ser denida exclusivamente em termos da soma. Dena a func~ao mult que recebe dois numero e os multiplica usando a func~ao soma. Soluc~ao do Exerccio 5.1.9 (defun mult (a b) (if (zerop a 0) 0 (soma (mult (1- a) b) b))) 5.2 Depurac~ao de Func~oes Em Lisp, e possvel analizar as chamadas as func~oes atraves da forma especial trace. Ela recebe o nome das func~oes que se pretendem analizar e altera essas func~oes de forma a que elas escrevam no terminal as chamadas com os respectivos argumentos em cada chamada, e os valores retornados. Esta informac~ao e extremamente util para a depurac~ao das func~oes. Para se parar a depurac~ao de uma func~ao, usa-se a forma especial untrace, que recebe o nome da func~ao ou func~oes de que se pretende tirar o trace. Se se usar a forma especial trace sem argumentos ela limita-se a indicar quais as func~oes que est~ao em trace. Se se usar a forma especial untrace sem argumentos, s~ao retiradas de trace todas as func~oes que estavam em trace. Exerccio 5.2.1 Experimentar o trace do fact. Soluc~ao do Exerccio 5.2.1 > (trace fact) (fact) > (fact 5) 0: (fact 5) 1: (fact 4) 2: (fact 3) 3: (fact 2) 4: (fact 1) 5: (fact 0) 5: returned 1 4: returned 1 3: returned 2 2: returned 6 1: returned 24 0: returned 120 120 5.3 Func~oes de Ordem Superior Vimos que as func~oes permitem-nos dar um nome a um conjunto de operac~oes e trata-lo como um todo. Muitas vezes, porem, ha um padr~ao que se repete, variando apenas uma ou outra operac~ao. Por exemplo, consideremos uma func~ao que soma os quadrados de todos os inteiros entre a e b, P b i=a i 2. 20

(defun soma-quadrados (a b) (if (> a b) 0 (+ (quadrado a) (soma-quadrados (1+ a) b)))) > (soma-quadrados 1 4) 30 Consideremos agora uma outra func~ao que soma as raizes quadradas de todos os inteiros entre a e b, P b i=a pi. (defun soma-raizes (a b) (if (> a b) 0 (+ (sqrt a) (soma-raizes (1+ a) b)))) > (soma-raizes 1 4) 6.146264369941973 Em ambas as func~oes existe uma soma de express~oes matematicas entre dois limites, i.e., existe um somatorio P b i=a f(i). O somatorio e uma abstracc~ao matematica para uma soma de numeros. Dentro do somatorio e possvel colocar qualquer operac~ao matematica relativa ao ndice do somatorio. Esse ndice varia desde o limite inferior ate ao limite superior. Para que se possa denir o processo do somatorio na nossa linguagem de programac~ao ela deve ser capaz de fazer abstrac~ao sobre as proprias operac~oes a realizar, e deve poder usa-las como se de par^ametros do processo se tratasse. O padr~ao a executar seria qualquer coisa do estilo: (defun soma-??? (a b) (if (> a b) 0 (+ (aplica-??? a) (soma-??? (1+ a) b)))) O smbolo??? representa a operac~ao a realizar dentro do somatorio, e que pretendemos transformar num par^ametro. Em Lisp, para se aplicar uma func~ao que e o valor de um argumento, usa-se a func~ao funcall, que recebe essa func~ao e os seus par^ametros actuais. Para se indicar que pretendemos a func~ao associada a um determinado smbolo, usa-se a forma especial function. > (function 1+) #<compiled-function 1+> > (funcall (function 1+) 9) 10 > (defun teste (f x y) (funcall f x y)) teste > (teste (function +) 1 2) 3 21

Deste modo ja podemos escrever a implementac~ao do nosso somatorio: (defun somatorio (func a b) (if (> a b) 0 (+ (funcall func a) (somatorio func (1+ a) b)))) Podemos testar a func~ao para o exemplo anterior. > (somatorio (function quadrado) 1 4) 30 Como se v^e, a func~ao somatorio representa a abstracc~ao associada ao somatorio matematico. Para isso, ela recebe uma func~ao como argumento e aplica-a aos sucessivos inteiros includos no somatorio. As func~oes que recebem e manipulam outras func~oes s~ao designadas func~oes de ordem superior. Exerccio 5.3.1 Repare-se que, tal como a func~ao somatorio, podemos escrever a abstracc~ao correspondente ao produtorio (tambem designado piatorio) Q b i=a f(i). Esta abstracc~ao corresponde ao produto dos valores de uma determinada express~ao para todos os inteiros de um intervalo. Escreva uma func~ao Lisp que a implemente. Soluc~ao do Exerccio 5.3.1 (defun produtorio (func a b) (if (> a b) 1 (* (funcall func a) (produtorio func (1+ a) b)))) Exerccio 5.3.2 Escreva a func~ao factorial usando o produtorio. Soluc~ao do Exerccio 5.3.2 Uma vez que n~ao queremos aplicar nenhuma func~ao ao ndice do produtorio, temos de denir a func~ao identidade. (defun identidade (x) x) (defun fact (n) (produtorio (function identidade) 1 n)) Exerccio 5.3.3 Quer o somatorio, quer o produtorio podem ser vistos como casos especiais de uma outra abstracc~ao ainda mais generica, designada acumulatorio. Nesta abstracc~ao, quer a operac~ao de combinac~ao dos elementos, quer a func~ao a aplicar a cada um, quer o valor inicial, quer o limite inferior, quer a passagem para o elemento seguinte (designado o sucessor), quer o limite superior s~ao par^ametros. Dena esta func~ao. Dena o somatorio e o produtorio em termos de acumulatorio. 22

Soluc~ao do Exerccio 5.3.3 (defun acumulatorio (combina func inicial a suc b) (if (> a b) inicial (funcall combina (funcall func a) (acumulatorio combina func inicial (funcall suc a) suc b)))) (defun somatorio (func a b) (acumulatorio (function +) func 0 a (function 1+) b)) (defun produtorio (func a b) (acumulatorio (function *) func 1 a (function 1+) b)) 5.4 Especializac~ao Por vezes, embora exista uma abstracc~ao de ordem superior, e mais simples pensar numa de ordem inferior, quando esta ultima e um caso particular da primeira. Embora o acumulatorio seja uma abstracc~ao de muito alto nvel, o grande numero de par^ametros que ela possui torna difcil a quem l^e perceber o que se esta a fazer. Assim, quando se repete muito um dado padr~ao de utilizac~ao, pode valer a pena criar casos particulares cuja leitura seja imediata. Estes casos particulares correspondem a xar alguns dos par^ametros da abstracc~ao superior. Vimos que as abstracc~oes matematicas do somatorio e do produtorio se podiam escrever em termos da abstracc~ao de ordem superior acumulatorio, i.e., s~ao especializac~oes desta ultima em que a operac~ao de combinac~ao, o valor inicial e a operac~ao de sucessor est~ao xas. Um outro exemplo desse caso particular e a func~ao produto. O produto e muito semelhante ao produtorio, mas com a diferenca que a passagem de um elemento ao seguinte e dada por uma func~ao, e n~ao incrementando o ndice do produtorio. Quanto ao resto e absolutamente igual. Assim, podemos denir o produto em termos do acumulatorio xando a operac~ao de combinac~ao no * e o valor inicial no 1. (defun produto (func a suc b) (acumulatorio (function *) func 1 a suc b)) Exerccio 5.4.1 O produto e uma abstrac~ao de ordem superior ou inferior a do produtorio? Qual delas e que e um caso particular da outra? Soluc~ao do Exerccio 5.4.1 Uma vez que o produto apenas difere do produtorio por n~ao imp^or a operac~ao de sucessor, e uma abstracc~ao de ordem superior, podendo ser especializada para o produtorio: (defun produtorio (func a b) (produto func a (function 1+) b)) Exerccio 5.4.2 Tal como a func~ao produto, existe uma abstracc~ao correspondente designada soma. Dena-a. 23

Soluc~ao do Exerccio 5.4.2 (defun soma (func a suc b) (acumulatorio (function +) func 0 a suc b)) Exerccio 5.4.3 Sabe-se que a soma 1 13 + 1 57 + 1 911 + converge (muito lentamente) para 8. Dena a func~ao que calcula a aproximac~ao a ate ao n-esimo termo da soma. Determine ate ao termo 2000. Soluc~ao do Exerccio 5.4.3 (defun pi/8-seguinte (x) (+ x 4)) (defun pi/8-func (x) (/ 1.0 (* x (+ x 2)))) (defun pi (n) (* 8 (soma (function pi/8-func) 1 (function pi/8-seguinte) n))) > (pi 2000) 3.140592653839793 5.5 Lambdas Como se viu pelo exemplo anterior, para que pudessemos implementar =8 em func~ao de acumulatorio tivemos de escrever as func~oes pi/8-seguinte e pi/8-func, cuja utilidade e muito limitada. Elas apenas servem para este exemplo. Por esse motivo, n~ao tem muito sentido estar a deni-las no ambiente do avaliador. O que pretendiamos era t~ao somente que fosse possvel especicar as express~oes que aquelas func~oes calculam, independentemente do nome que se lhes pudesse atribuir. Para isso, a linguagem Lisp fornece as lambdas. Uma lambda e uma func~ao com todas as caractersticas das restantes mas que n~ao esta associada a nenhum smbolo. Pode ser vista como uma func~ao sem nome. A sintaxe da lambda e igual a da defun, mas em que se omite o nome. > ((lambda (z) (+ z 3)) 2) 5 Como se v^e, uma lambda pode ser usada onde se usaria uma func~ao. Isto permite simplicar o exerccio da aproximac~ao a sem ter de denir quaisquer outras func~oes: (defun pi (n) (* 8 (soma (function (lambda (x) (/ 1.0 (* x (+ x 2))))) 1 (function (lambda (x) (+ x 4))) n))) 24

Exerccio 5.5.1 Imagine uma func~ao f ao longo de um intervalo [a; b]. Essa func~ao devera apresentar um maximo nesse intervalo, i.e., um valor entre a e b para o qual a func~ao toma o seu valor maximo. Usando o acumulatorio, escreva a func~ao maximo-func que recebe uma func~ao e um intervalo e encontra o maximo. Para determinar o maior entre dois numero pode usar a func~ao Lisp max. Teste maximo-func para o exemplo y = x x 2 no intervalo [0; 2] com uma toler^ancia de 0:01. Soluc~ao do Exerccio 5.5.1 (defun maximo-func (func a b) (acumulatorio (function max) func (funcall func a) a (function (lambda (x) (+ x 0.01))) b)) Matematicamente, o maximo de x x 2 ocorre quando a derivada da func~ao se anula, i.e., quando 1 2x = 0, x = 1=2. O valor maximo sera ent~ao de y = 1=2 (1=2) 2 = 1=4. Testando, temos: > (maximo-func (function (lambda (x) (- x (quadrado x)))) 0 2) 0.25 As lambdas s~ao a ess^encia do Lisp. A qualquer func~ao corresponde uma lambda. Na realidade, a forma especial defun n~ao faz mais do que criar uma lambda com os par^ametros e o corpo da func~ao e associala ao nome da func~ao que se esta a denir. Quando a forma especial function recebe um nome (um smbolo) ela devolve a lambda associada a esse nome. A designac~ao de lambda () deriva duma area da matematica que se dedica ao estudo dos conceitos de func~ao e de aplicac~ao de func~ao, e que se designa por calculo-. O calculo- e uma ferramenta muito utilizada para estudar a sem^antica das linguagens de programac~ao. As lambdas possuem ainda muitas outras utilidades para alem da mera criac~ao de func~oes sem nome. 5.6 Variaveis Locais Imagine-se que pretendemos escrever uma func~ao que calcula a seguinte express~ao: f(x; y) = (1+x 2 y) 2 x+(1+x 2 y)y. Em Lisp, temos a seguinte traduc~ao: (defun f (x y) (+ (* (quadrado (+ 1 (* (quadrado x) y))) x) (* (+ 1 (* (quadrado x) y)) y))) Como se v^e, a express~ao (+ 1 (* (quadrado x) y)) aparece repetida duas vezes. Isto, para alem de dicultar a leitura da func~ao torna-a menos eciente pois aquela express~ao vai ter de ser calculada duas vezes. Quase todas as linguagens de programac~ao fornecem os meios para se criarem variaveis locais, temporarias, para guardarem resultados parciais que v~ao ser utilizados noutros stios. Em Lisp, isso pode ser obtido denindo func~oes intermedias: 25

(defun f (x y) (f* x y (+ 1 (* (quadrado x) y)))) (defun f* (x y temp) (+ (* (quadrado temp) x) (* temp y))) Mas como ja vimos, n~ao ha necessidade de se denir uma func~ao f* no ambiente pois podemos usar as lambdas directamente. (defun f (x y) ((lambda (temp) (+ (* (quadrado temp) x) (* temp y))) (+ 1 (* (quadrado x) y)))) Repare-se que dentro do corpo da lambda ha refer^encias quer aos par^ametros da lambda (temp) quer aos par^ametros da func~ao f em que a lambda esta inserida. Uma vez que n~ao e muito conveniente separar os valores das variaveis, Lisp providencia uma forma especial designada let que e convertida para uma lambda. A sua sintaxe e: (let ((var 1 exp 1 ) (var 2 exp 2 ). (var n exp n )) corpo ) Quando se avalia um let, cada smbolo var i e associado ao valor da express~ao correspondente exp i (em paralelo) e em seguida o corpo e avaliado como se as refer^encias a var i estivessem substituidas pelos valores correspondentes de exp i. Esta forma especial e absolutamente equivalente a escrever: ((lambda (var 1 var 2... var n ) corpo ) exp 1 exp 2... exp n ) Embora equivalentes, a utilizac~ao da forma let e mais facil de ler. Este genero de formas especiais que se limitam a ser uma traduc~ao mais agradavel para outras formas especiais s~ao designadas por acucar sintatico. O let e acucar sintatico para uma lambda. Exerccio 5.6.1 Usando o let, reescreva a func~ao f anterior. Soluc~ao do Exerccio 5.6.1 (defun f (x y) (let ((temp (+ 1 (* (quadrado x) y)))) (+ (* (quadrado temp) x) (* temp y)))) 26

Exerccio 5.6.2 Qual o valor das seguintes express~oes: Soluc~ao do Exerccio 5.6.2 > (let ((x 10)) (+ (let ((x 20)) (+ x 5)) (+ x 2))) 37 > (let ((x 10)) (+ (let ((x 11) (y (+ x 4))) (+ y x)) (+ x 2))) 37 5.7 Func~oes Locais Tal como se podem criar variaveis locais com a forma especial let, tambem e possvel criar func~oes locais com a forma especial flet. A sua sintaxe e extremamente parecida com a do let, so que o valor de cada variavel e a denic~ao de uma func~ao. A ttulo de exemplo, estude-se a seguinte denic~ao: (defun teste (x) (flet ((f-local1 (y) (+ x y)) (f-local2 (z) (* x z)) (f-local3 (x) (+ x 2))) (+ (f-local1 x) (f-local2 x) (f-local3 x)))) > (teste 2) 12 > (f-local1 2) Error: Undefined function F-LOCAL1 As func~oes f-local1, f-local2 e f-local3 s~ao locais a func~ao teste, sendo estabelecidas a cada aplicac~ao desta func~ao. Tal como as variaveis do let, as func~oes locais de um flet n~ao se podem referir umas as outras, pois s~ao avaliadas em paralelo. Isto implica, tambem, que n~ao se podem referir a si propias, impedindo a criac~ao de func~oes locais recursivas. Atendendo a que a maioria das vezes as func~oes que denimos s~ao recursivas, independentemente de serem locais ou n~ao, interessa possuir um meio de o podermos fazer. A forma especial labels providencia esta possibilidade. A sua sintaxe e igual a do flet, mas a sua sem^antica e ligeiramente diferente. Para o flet, o ^ambito do nome das func~oes denidas e apenas o corpo do flet. Para o labels, esse ^ambito e extendido a propria forma especial. Isto permite que se possam denir func~oes locais recursivas ou mutuamente recursivas. 6 ^Ambito e Durac~ao Quando uma determinada express~ao que se da ao avaliador faz refer^encia a uma variavel, ele precisa de saber qual e o valor dessa variavel. Ate 27

agora, vimos que as lambdas eram o unico meio de estabelecer variaveis. Os par^ametros de uma lambda denem um contexto em que as variaveis tomam um determinado valor. O contexto abarca todo o corpo da lambda. 6.1 ^Ambito de uma Refer^encia Designa-se por ^ambito de uma refer^encia, a zona textual em que ela pode ser correctamente referida. Assim, o ^ambito de um par^ametro de uma lambda e a zona textual correspondente ao corpo da func~ao. Isto implica que qualquer par^ametro da lambda pode ser referido dentro desse corpo, mas n~ao fora dele. > ((lambda (z) (+ z z)) 3) 6 > (+ z z) Error: Unbound variable Z Uma vez que o ^ambito de um par^ametro e o corpo da lambda correspondente, e possvel escrever: > ((lambda (z) ((lambda (w) (+ w z)) 3) 4) 7 Reescrevendo o exemplo usando o let, temos > (let ((z 4)) 7 (let ((w 3)) (+ w z))) Neste exemplo, cada lambda (ou cada let) estabelece um valor para uma variavel. Quando se encontra uma refer^encia a uma variavel, o seu valor e dado pela ligac~ao correspondente ao contexto mais pequeno. Se n~ao existe qualquer ligac~ao em nenhum dos contextos, a variavel diz-se n~ao ligada. A avaliac~ao de variaveis n~ao ligadas produz um erro. Quando uma mesma variavel aparece ligada repetidamente em contextos sucessivos, a ligac~ao mais \interior" obscurece todas as \exteriores". Isto n~ao quer dizer que as ligac~oes exteriores sejam destrudas. Elas s~ao apenas localmente substitudas durante a avaliac~ao do corpo mais interior. Assim, temos o seguinte exemplo: > (let ((x 10)) 30 (+ (let ((x 20)) x) x)) Diz-se que uma refer^encia e de ^ambito lexico quando ela so pode ser correctamente referida dentro da regi~ao textual da express~ao que a criou. 28