Vous êtes sur la page 1sur 74

Introduca~o 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 computa-
dores 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 adap-
tadas a outros. A escolha de uma linguagem de programac~ao deve
estar condicionada, naturalmente, pelo tipo de problemas que quere-
mos 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 tipi cac~ao de dados, a possibilidade de tratar dados
e programas de um mesmo modo e a indistinc~ao entre func~oes de nidas
pela linguagem e func~oes de nidas pelo programador s~ao algumas das
raz~oes da sua exibilidade.
A linguagem Lisp nasceu como uma ferramenta matematica, inde-
pendente 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 ine cientes. Esta ine ci^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 progra-
ma na linguagem do processador. No entanto, com o aparecimento de
compiladores e cazes e de um suporte cada vez maior da parte dos pro-
cessadores, Lisp possui, actualmente, uma e ci^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 re-
gras, 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, Ze-
taLisp, LeLisp, MacLisp, InterLisp, Scheme, T, Nil, XLisp, AutoLisp,
etc, para nomear apenas as mais relevantes. Esta multitude de dialec-
tos comecou a tornar difcil a livre comunicac~ao entre os membros da
comunidade Lisp. Para obviar este problema, criou-se um standard de-
nominado 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 am-
pliac~ao evita (na medida em que isso e possvel) alterar funcionalidades
das vers~oes anteriores e assim um programa Common Lisp tem a ga-
rantia 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 po-
de 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 com-
pilador e um interpretador, permitindo misturar livremente codigo com-
pilado 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 apare-


cer as express~oes que o utilizador escrever. O Lisp interacciona com
o utilizador executando um ciclo em que l^e uma express~ao, determi-
na 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 cor-
recc~ao de erros seja bastante facilitada. A vantagem de Lisp ser uma lin-
guagem interactiva esta na rapidez com que se desenvolvem prototipos
de programas, escrevendo, testando e corrigindo pequenas func~oes de
cada vez.
Exerc
cio 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 identi car
os numeros como dados e as operac~oes algebricas como procedimentos e
podemos combinar os numeros entre si usando aquelas operac~oes. Por
exemplo, 2  2 e uma combinac~ao, tal como 2  2  2 e 2  2  2  2. No en-
tanto, 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 poten-
ciac~ao, i.e, multiplicac~ao sucessiva, tendo esta operac~ao sido de nida na
matematica ha ja muito tempo.
Tal como a linguagem da matematica, uma linguagem de progra-
mac~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, de nindo
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-eval-
print. Isto implica que tudo o que escrevemos no Lisp tem de ser ava-
liado, 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 Combinaco~es
Combinac~oes s~ao entidades complexas feitas a partir de entidades mais
simples. Por exemplo, na matematicas numeros podem ser combina-
dos 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 pro-
duto de numeros s~ao exemplos de operac~oes extremamente elementares
consideradas procedimentos primitivos.
Em Lisp, criam-se combinac~ao escrevendo uma sequ^encia de ex-
press~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 com-
binac~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 com-
binac~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 pre xa. 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 in xa) que, normalmente, e
mais simples de ler por um ser humano. No entanto, a notac~ao pre xa
usada pelo Lisp tem largas vantagens sobre a notac~ao in xa:
 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 unari-


os 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 ope-
rador deste tipo diz-se in xo (in signi ca 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 ex-
press~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 elimi-
na as ambiguidades. Essa preced^encia pode ser alterada atraves
do emprego de par^enteses. Em Lisp, as express~oes seriam ne-
cessariamente distintas, (+ 1 (* 2 3)) ou (* (+ 1 2) 3), n~ao
podendo haver qualquer ambiguidade.
 Os operadores que nos de nimos usam-se exactamente da mesma
maneira que os operadores da linguagem: operador primeiro e
operandos a seguir. Em Pascal, os operadores s~ao in xos, i.e.,
est~ao entre os operandos, e as func~oes e procedimentos por nos
de nidos s~ao pre xos, i.e., primeiro a func~ao ou procedimento e
depois os operandos. Isto impede a extens~ao da linguagem de uma
forma coerente.
Para exempli car 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 per-
mitisse escrever 3**4 para indicar a quarta pot^encia de 3. Como
esse operador n~ao existe na linguagem (standard), somos obriga-
dos a criar uma func~ao que o substitua mas, neste caso, a sintaxe
muda radicalmente. Esta func~ao, como se usa de forma pre xa
n~ao se pode tornar coerente com as restantes operac~oes do Pascal,
como a soma e a multiplicac~ao. Em Lisp, pelo contrario, tan-
to podemos escrever (* 3 3 3 3) como de nir uma func~ao que
permita escrever (** 3 4).
A desvantagem da notac~ao pre xa 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 sim-
ples. Numa linha coloca-se o operador e o primeiro operando. Os res-
tantes operandos v^em alinhados por debaixo do primeiro.
6
(- (+ 1
(* 2 3))
(* (/ 4 5)
6))

Quando a regra de indentac~ao n~ao e su ciente, 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 me-
dida que os escrevemos e mostram o emparelhamento de par^enteses.
Desta forma, apos algum tempo de pratica, torna-se muito facil escre-
ver e ler os programas, por mais complexa que possa parecer a sua
estrutura.
Exerc cio 3.2.1 Converta as seguintes express~
oes da notac~ao in xa da
aritmetica para a notac~ao pre xa 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
Solu
ca~o do Exerc
cio 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)

Exerc
cio 3.2.2 Converta as seguintes express~oes da notac~ao pre xa
do Lisp para a notac~ao in xa da aritmetica:
(* (/ 1 2) 3)
(* 1 (- 2 3))
(/ (+ 1 2) 3)
(/ (/ 1 2) 3)
(/ 1 (/ 2 3))
(- (- 1 2) 3)
(- 1 2 3)

7
Solu
ca~o do Exerc
cio 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 Combinaco~es


Como ja vimos, o Lisp considera que o primeiro elemento de uma com-
binac~ao e um operador e os restantes s~ao os operandos.
O avaliador determina o valor de uma combinac~ao como o resul-
tado de aplicar o procedimento especi cado pelo operador ao valor dos
operandos. O valor de cada operando e designado de argumento do pro-
cedimento. 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

Exerc
cio 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)

Solu
ca~o do Exerc
cio 3.3.1

3/2
-1
1
1/6
3/2
-4
-4
-1

3.4 De nic~ao de Funco~es


Tal como em matematica, pode-se de nir numa linguagem de progra-
mac~ao a operac~ao de elevar um numero ao quadrado. Assim, se preten-
dermos 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 de nir a func~ao quadrado:
> (defun quadrado (x)
(* x x))
quadrado

Para se de nirem novas func~oes em Lisp, e necessario criar uma com-


binac~ao de quatro elementos. O primeiro elemento desta combinac~ao
e a palavra defun, que informa o avaliador que estamos a de nir uma
func~ao. O nome defun e uma abreviatura de \de ne function". O se-
gundo elemento e o nome da func~ao que queremos de nir, 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 de nic~ao o nome da func~ao
de nida.
A de nic~ao da func~ao quadrado diz que para se determinar o qua-
drado de um numero x, devemos multiplicar esse numero por ele proprio
(* x x). Esta de ni c~ao associa a palavra quadrado a um procedimen-
to. Este procedimento possui par^ametros e um corpo de express~oes. De
forma generica temos:
ametro n )
ametro 1 ... par^
(defun nome (par^
corpo )

Os par^ametros de um procedimento s~ao designados par^ametros for-


mais 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 de nidas. 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 de nic~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 traba-
lhar com a linguagem. Quando terminamos, perde-se todo o ambiente.
Isto implica que, se n~ao queremos perder o trabalho que estivemos a es-
crever, 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 de nic~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
de nir e usar a seguinte func~ao:
> (defun x+y*z (x y z)
(+ (* y z) x))
x+y*z
> (x+y*z 1 2 3)
7

Lisp atribui um signi cado muito especial aos smbolos. Reparemos


que, quando de nimos uma func~ao, os par^ametros formais da func~ao

10
s~ao smbolos. O nome da func~ao tambem e um smbolo. Quando escre-
vemos uma combinac~ao, o avaliador de Lisp usa a de nic~ao de func~ao
que foi associada ao smbolo que constitui o primeiro elemento da com-
binac~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 cal-
cula 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 previa-
mente, 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 conside-
rada 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 de-
composto 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 n umero 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 signi cado de falso seja claro pois corresponde
necessariamente ao valor nil, o signi cado de verdade ja n~ao e t~ao claro
pois, desde que seja diferente de nil, e considerado verdade.
Exerc
cio 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)

Solu
ca~o do Exerc
cio 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 condi
c~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
6)
5

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 De na uma fun
c~ao soma-grandes que recebe tr^es nu-
meros como argumento e determina a soma dos dois maiores.

13
Solu
ca~o do Exerc
cio 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))))

Exerc
cio 4.3.2 Escreva uma func~ao que calcule o factorial de um nu-
mero.
Solu
ca~o do Exerc
cio 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
e uma vers~ao mais potente que o if. E uma especie de switch-case
cond 
do C. A sua sintaxe e:
(cond (condi
c~ao 1 express~
ao 1 )
(condi
c~ao 2 express~
ao 2 )
.
.
.
(condi
c~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, termi-
nando a avaliac~ao. Um exemplo sera:
> (cond ((> 4 3) 5)
(t 6))
5

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 de nir um em termos do outro ?
A seguinte de nic~ao parece ser uma resposta:
> (defun meu-if (condicao consequente alternativa)
(cond (condicao consequente)
(t alternativa)))
meu-if
> (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 escre-


vermos um exemplo ligeiramente diferente, algo corre mal.
> (defun divide (x y)
(meu-if (zerop y)
0
(/ x y)))
divide
> (divide 2 0)
Error:.....

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 de nic~ao e apli-
cac~ao da func~ao factorial.

15
> (defun meu-fact (n)
(meu-if (zerop n)
1
(* n (meu-fact (- n 1)))))
meu-fact
> (meu-fact 4)
Error:.....
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 res-
tantes 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 ava-
liac~ao dos seus argumentos, n~ao podendo determinar qual dos valores,
consequente ou alternativa, retornar, repetindo inde nidamente a apli-
cac~ao da func~ao meu-fact a argumentos sucessivamente decrescentes.
Suponhamos agora a seguinte interacc~ao com o Lisp:
> (if (> 4 3)
100
(inexistente))
100
Segundo o modelo de avaliac~ao que tnhamos apresentado, uma com-
binac~ao e avaliada aplicando o procedimento que o primeiro elemento da
combinac~ao especi ca 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 de nida. 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 es-
peciais, Lisp tem muito poucas. Desta forma, a linguagem possui uma
regra de avaliac~ao muito simples e muito bem de nida, 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 de nir novas formas cujo compor-
tamento 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 Funco~es 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 de nir na sua de -
nic~ao.
Em todas as func~oes recursivas existe:
 Um passo basico (ou mais) cujo resultado e imediatamente conhe-
cido.
 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 nun-
ca parar. O numero de chamadas recursivas cresce inde nidamente ate
esgotar a memoria (stack), e o programa gera um erro. Em certas lin-
guagens (Scheme) e implementac~oes do Common Lisp, isto n~ao e assim,
e pode nunca ser gerado um erro. A recurs~ao in nita e o equivalente
das func~oes recursivas aos ciclos in nitos dos metodos iterativos do tipo
while-do e repeat-until.
Repare-se que uma func~ao recursiva que funciona perfeitamente pa-
ra os casos para que foi prevista pode estar completamente errada para
outros casos. A func~ao fact e um exemplo. Quando o argumento e nega-
tivo, o problema torna-se cada vez mais complexo, cada vez mais longe
do caso simples. (fact -1) ! (fact -2) ! (fact -3) !   
Exerc
cio 5.1.1 Considere uma vers~ao extremamente primitiva da lin-
guagem 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 nano Lisp, abreviadamente Lisp, de na o
predicado menor, que recebe dois numero inteiros positivos e determina
se o primeiro argumento e numericamente inferior ao segundo.
Solu
ca~o do Exerc
cio 5.1.1

(defun menor (x y)
(cond ((zerop y) nil)
((zerop x) t)
(t (menor (1- x) (1- y)))))

Exerc
cio 5.1.2 De na a operac~ao igual? que testa igualdade nume-
rica de inteiros positivos na linguagem Lisp.
Solu
ca~o do Exerc
cio 5.1.2

(defun igual? (x y)
(cond ((zerop x) (zerop y))
((zerop y) nil)
(t (igual? (1- x) (1- y)))))

Exerc
cio 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, de na a func~ao
negativo que recebe um n umero inteiro positivo e retorna o seu sime-
trico. Assim, pretendemos obter: (negativo 3) ! -3.
Solu
ca~o do Exerc
cio 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, de na o predicado positivo?, que re-
cebe um numero e indica se ele e positivo ou n~ao.
18
Solu
ca~o do Exerc
cio 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-)))))

Exerc
cio 5.1.5 De na o teste de igualdade de dois numeros na lin-
guagem Lisp contemplando a possibilidade de trabalhar tambem com
numeros inteiros negativos.
Solu
ca~o do Exerc
cio 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-)))))

Exerc
cio 5.1.6 De na a func~ao simetrico de um numero qualquer
na linguagem Lisp.
Solu
ca~o do Exerc
cio 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-)))))

Exerc
cio 5.1.7 E possvel de nir a soma de dois numeros inteiros po-
sitivos em Lisp, i.e., apenas recorrendo as func~oes 1+ e 1- que somam
e subtraem uma unidade, respectivamente. De na a operac~ao soma.
Solu
ca~o do Exerc
cio 5.1.7

(defun soma1 (a b)
(if (zerop a)
b
(1+ (soma1 (1- a) b))))

Exerccio 5.1.8 Generalize a fun c~ao de soma de modo a poder receber


numeros inteiros positivos e negativos.
Solu
ca~o do Exerc
cio 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
Exerc
cio 5.1.9 Do mesmo modo que a soma pode ser de nida exclu-
sivamente em termos de sucessor 1+ e predecessor 1-, a multiplicac~ao
pode ser de nida exclusivamente em termos da soma. De na a func~ao
mult que recebe dois n
umero e os multiplica usando a func~ao soma.
Solu
ca~o do Exerc
cio 5.1.9

(defun mult (a b)
(if (zerop a 0)
0
(soma (mult (1- a) b) b)))

5.2 Depurac~ao de Funco~es


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 anali-
zar 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 fun c~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.
Exerc
cio 5.2.1 Experimentar o trace do fact.
Solu
ca~o do Exerc
cio 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 Funco~es 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,
consideremosPuma func~ao que soma os quadrados de todos os inteiros
entre a e b, bi=a i2.

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 P funpc~ao que soma as raizes quadradas


de todos os inteiros entre a e b, bi=a i.
(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 deP express~oes matematicas


entre dois limites, i.e., existe um somatorio bi=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 de nir 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 so-
matorio:
(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 associa-


da 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.
Exerc cio 5.3.1 Repare-se que, tal como a fun c~ao somatorio, podemos
escrever aQabstracc~ao correspondente ao produtorio (tambem designado
piatorio) bi=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.
Solu
ca~o do Exerc
cio 5.3.1

(defun produtorio (func a b)


(if (> a b)
1
(* (funcall func a) (produtorio func (1+ a) b))))

Exerc
cio 5.3.2 Escreva a func~ao factorial usando o produtorio.
Soluca
~o do Exerc cio 5.3.2 Uma vez que n~ ao queremos aplicar nenhuma func~ao ao ndice
do produtorio, temos de de nir a func~ao identidade.
(defun identidade (x) x)

(defun fact (n)


(produtorio (function identidade) 1 n))

Exerc cio 5.3.3 Quer o somat orio, quer o produtorio podem ser vis-
tos como casos especiais de uma outra abstracc~ao ainda mais generica,
designada acumulatorio. Nesta abstracc~ao, quer a operac~ao de combi-
nac~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. De na
esta func~ao. De na o somatorio e o produtorio em termos de acumu-
latorio.

22
Solu
ca~o do Exerc
cio 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 lei-
tura seja imediata. Estes casos particulares correspondem a xar alguns
dos par^ametros da abstracc~ao superior. Vimos que as abstracc~oes ma-
tematicas 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 ope-
rac~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 de nir 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))

Exerc
cio 5.4.1 O produto e uma abstrac~ao de ordem superior ou in-
ferior a do produtorio? Qual delas e que e um caso particular da outra?
Solu
ca~o do Exerc
cio 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))

Exerc cio 5.4.2 Tal como a fun


c~ao produto, existe uma abstracc~ao
correspondente designada soma. De na-a.
23
Solu
ca~o do Exerc
cio 5.4.2

(defun soma (func a suc b)


(acumulatorio (function +) func 0 a suc b))

Exerc
cio 5.4.3 Sabe-se que a soma 113 + 517 + 9111 +    converge
(muito lentamente) para 8 . De na a func~ao que calcula a aproximac~ao
a  ate ao n-esimo termo da soma. Determine  ate ao termo 2000.
Solu
ca~o do Exerc
cio 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 implemen-
tar =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 ape-
nas servem para este exemplo. Por esse motivo, n~ao tem muito sentido
estar a de ni-las no ambiente do avaliador. O que pretendiamos era t~ao
somente que fosse possvel especi car 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 simpli car o exerccio da aproximac~ao a  sem ter de de nir
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
Exerc
cio 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 x2 no intervalo [0; 2] com uma toler^ancia de 0:01.
Solu
ca~o do Exerc
cio 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 x2 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 associa-
la ao nome da func~ao que se esta a de nir. 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+ x2y)2x +(1+ x2y)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 repe-
tida duas vezes. Isto, para alem de di cultar a leitura da func~ao torna-a
menos e ciente pois aquela express~ao vai ter de ser calculada duas vezes.
Quase todas as linguagens de programac~ao fornecem os meios pa-
ra se criarem variaveis locais, temporarias, para guardarem resultados
parciais que v~ao ser utilizados noutros stios. Em Lisp, isso pode ser
obtido de nindo 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 de nir 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 va-
riaveis, Lisp providencia uma forma especial designada let que e con-
vertida 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.
Exerc
cio 5.6.1 Usando o let, reescreva a func~ao f anterior.
Solu
ca~o do Exerc
cio 5.6.1

(defun f (x y)
(let ((temp (+ 1 (* (quadrado x) y))))
(+ (* (quadrado temp) x)
(* temp y))))

26
Exerc
cio 5.6.2 Qual o valor das seguintes express~oes:
Solu
ca~o do Exerc
cio 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 Funco~es 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 de nic~ao de uma func~ao.
A ttulo de exemplo, estude-se a seguinte de nic~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 aplica c~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 de nimos 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
de nidas e apenas o corpo do flet. Para o labels, esse ^ambito e
extendido a propria forma especial. Isto permite que se possam de nir
func~oes locais recursivas ou mutuamente recursivas.

6 A^ mbito 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 de nem um contexto em que as variaveis
tomam um determinado valor. O contexto abarca todo o corpo da
lambda.
6.1 A^ mbito 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 cor-


respondente, e possvel escrever:
> ((lambda (z) ((lambda (w) (+ w z)) 3) 4)
7

Reescrevendo o exemplo usando o let, temos


> (let ((z 4))
(let ((w 3))
(+ w z)))
7

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 con-
textos sucessivos, a ligac~ao mais \interior" obscurece todas as \exterio-
res". 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))
(+ (let ((x 20))
x)
x))
30

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
Diz-se que uma refer^encia e de ^ambito vago (ou inde nido) quan-
do ela pode ser correctamente referida a partir de qualquer regi~ao do
programa.
Exerc
cio 6.1.1 Que tipo de ^ambito possui uma variavel de um let?
Que tipo de ^ambito possui o nome de uma func~ao?
Solu
ca~o do Exerc cio 6.1.1 Qualquer vari avel de um let possui ^ambito lexico. Como
as func~oes podem ser livremente referidas de qualquer ponto do programa, o seu nome e de
^ambito vago.

6.2 Durac~ao de uma Refer^encia


Designa-se por durac~ao de uma refer^encia o intervalo de tempo durante
o qual ela pode ser correctamente referida.
Diz-se que uma refer^encia e de durac~ao din^amica quando so pode
ser correctamente referida no intervalo de tempo que decorre durante a
avaliac~ao da express~ao que a criou.
Diz-se que uma refer^encia e de durac~ao vaga (ou inde nida) quando
pode ser correctamente referida em qualquer instante apos a avaliac~ao
da express~ao que a criou.
Em Pascal, os par^ametros de uma func~ao ou procedimento t^em
^ambito lexico e durac~ao din^amica. A ligac~ao dos par^ametros formais
aos par^ametros actuais existe apenas durante a execuc~ao da func~ao ou
procedimento.
Em Scheme ou Common Lisp, os par^ametros das lambdas t^em ^am-
bito lexico e durac~ao vaga. Isto implica que e possvel aceder a uma
variavel mesmo depois de a func~ao que a criou ter terminado, desde que
essa variavel seja acedida dentro da regi~ao textual dessa func~ao.
A ttulo de exemplo, se tentarmos escrever a func~ao que determina o
maximo de uma func~ao numerica mas de forma a que ela possa receber
uma toler^ancia como par^ametro, podemos ser conduzidos a qualquer
coisa do genero:
(defun maximo-func (func a b tol)
(acumulatorio (function max)
func
(funcall func a)
a
(function (lambda (x) (+ x tol)))
b))

Repare-se que neste exemplo a func~ao que estabelece o incremento


refere-se a variavel livre tol. Uma das capacidades fundamentais das
lambdas e a sua refer^encia a variaveis livres. Uma variavel diz-se li-
vre numa lambda quando n~ao e um dos par^ametros da lambda onde e
referida.
Quando se aplica uma lambda aos seus argumentos, os par^ametros
tomam como valor os argumentos correspondentes, enquanto que as

29
variaveis livres tomam como valor o valor da primeira variavel igual no
contexto em que a lambda e de nida. E por esse motivo que quando a
lambda que realiza o incremento e aplicada a um numero, ela sabe qual
o valor correcto de tol. Ele e dado pelo contexto lexico (i.e. textual)
em que a lambda foi de nida.
Exerc
cio 6.2.1 Analise os seguintes casos:
((lambda (x) (+ x y)) 10)
(let ((y 5)) ((lambda (x) (+ x y)) 10))

Soluca
~o do Exerc cio 6.2.1 ((lambda (x) (+ x y)) 10) gera um erro pois no momento
da de nic~ao da lambda n~ao existe qualquer valor para y, enquanto que no caso (let ((y 5))
((lambda (x) (+ x y)) 10)) o avaliador devolve 15 pois y vale 5 no contexto envolvente da
lambda.

Exerc
cio 6.2.2 Explique a seguinte func~ao:
(defun incrementa (delta)
(function (lambda (x) (+ x delta))))

Solu
ca~o do Exerc
cio 6.2.2 A func~ao incrementa devolve uma func~ao que possui uma
variavel livre delta. Uma vez que os par^ametros das func~oes possuem ^ambito lexico e a
de nic~ao da lambda a devolver e feita dentro do corpo da func~ao incrementa, aquela lambda
pode referir-se livremente a variavel delta. Por outro lado, uma vez que os par^ametros
das func~oes possuem durac~ao vaga, mesmo depois de a func~ao incrementa ter terminado, a
lambda devolvida pode referir-se livremente a variavel delta.

Exerc
cio 6.2.3 Use a func~ao incrementa para reescrever a func~ao
que determina o maximo de uma func~ao numerica|maximo-func.
Solu
ca~o do Exerc
cio 6.2.3

(defun maximo-func (func a b tol)


(acumulatorio (function max)
func
(funcall func a)
a
(incrementa tol)
b))

7 Dados
Em todos os exemplos anteriores temos apresentado func~oes essencial-
mente numericas. Os numeros s~ao um exemplo dos dados que os proce-
dimentos podem usar e produzir. Vamos agora apresentar outros tipos
de dados que se podem utilizar.
7.1 A tomos
Ja vimos que os numeros s~ao um dos elementos primitivos do Lisp. Os
smbolos (nomes de func~oes e variaveis) s~ao outro dos exemplos. Estes
elementos dizem-se atomicos, pois n~ao podem ser decompostos.
Para se testar se um elemento e atomico pode-se usar a func~ao atom:

30
> (atom 1)
T

Exerc
cio 7.1.1 Quando tentamos testar se o smbolo xpto e atomico,
e escrevemos a express~ao (atom xpto) recebemos um erro. Explique o
que se passa.
Solu
ca~o do Exerc
cio 7.1.1 Segundo as regras do modelo de avaliac~ao, xpto e uma
refer^encia a uma variavel. (atom xpto) pretende determinar se o valor de xpto e um atomo.
Como a variavel xpto n~ao tem valor, o Lisp gera um erro.

Para que o Lisp possa considerar um smbolo por si so, i.e., sem
o considerar uma variavel, temos de usar a forma especial quote, que
devolve o seu argumento sem o avaliar.
> (quote xpto)
XPTO
> (atom (quote xpto))
T

A raz~ao de ser do quote esta associada a distinc~ao que existe entre


as frases \Diz-me o teu nome" e \Diz-me `o teu nome' ". No primeiro
caso a frase tem de ser completamente interpretada para que o ouvinte
possa dizer qual e o seu proprio nome. No segundo caso, as plicas est~ao
la para indicar ao ouvinte que ele n~ao deve tomar o que esta entre
plicas a letra, e deve limitar-se a dizer \o teu nome". As plicas servem,
pois, para distinguir o que deve ser tomado como e, e o que deve ser
interpretado.
Existem varias func~oes para se testar a igualdade de elementos pri-
mitivos. Como ja se viu, a igualdade de numeros e dada pela func~ao =.
Esta func~ao compara numeros de todos os tipos.
> (= 1 1)
t
> (= 1 1.0)
t

Em Lisp, existe unicidade de smbolos, i.e., dados dois smbolos com


o mesmo nome, eles representam necessariamente o mesmo objecto, ou
seja, o mesmo espaco da memoria do computador. Isto permite que a
comparac~ao entre dois smbolos possa ser feita testando se eles represen-
tam o mesmo espaco, i.e., se apontam para a mesma zona da memoria.
A func~ao eq realiza essa operac~ao.
> (eq (quote a) (quote a))
t
> (eq (quote a) (quote b))
nil

Note-se que a func~ao eq pode n~ao funcionar correctamente quando


aplicada a numeros.

31
> (eq 1 1)
t
> (eq 111111111111111111111111111111111111
111111111111111111111111111111111111)
nil

A raz~ao do comportamento incoerente da func~ao eq em numeros


deve-se ao facto de os numeros pequenos poderem ser representados
como dados imediatos, enquanto os numeros grandes ocupam espaco na
memoria, em zonas diferentes.
Para se testar smbolos e numeros do mesmo tipo existe uma outra
func~ao designada eql.
> (eql (quote a) (quote a))
t
> (eql 111111111111111111111111111111
111111111111111111111111111111)
t
> (eql 1 1.0)
nil

7.2 Combinaco~es de Dados


Para se combinar dados, e preciso que a linguagem possua uma \cola"
que permita agrupar esses dados. Em Lisp, essa \cola" e implementada
pela func~ao cons.
A func~ao cons cria um novo objecto que consiste na aglomerac~ao
de dois outros objectos, argumentos do cons. O cons e para o Lisp o
mesmo que as tabelas (arrays ) e estruturas (records, structs ) s~ao para
as outras linguagens como Pascal ou C.
> (cons 1 2)
(1 . 2)
> (cons (cons 1 2) 3)
((1 . 2) . 3)

Dada uma combinac~ao de objectos (um \cons") podemos obter o


primeiro elemento da combinac~ao usando a func~ao car e o segundo
usando a func~ao cdr.
> (car (cons 1 2))
1
> (cdr (cons 1 2))
2

Note-se que aplicar o car ou o cdr a um cons n~ao destroi esse cons.
O cons de dois objectos e designado um par com ponto (dotted pair ).
Como e natural, um cons n~ao e um objecto atomico:
> (atom (cons 1000 2000))
nil

32
Note-se que para combinar dois quaisquer objectos e necessario ar-
ranjar espaco na memoria para indicar qual o primeiro objecto e qual o
segundo. E a func~ao cons que arranja esse espaco. De cada vez que ela
e chamada, mesmo que seja para juntar os mesmos objectos, ela arranja
um novo espaco de memoria. Isto implica que a func~ao eq e sempre
falsa para o cons.
> (eq (cons 1 2) (cons 1 2))
nil

Exerc
cio 7.2.1 De na a func~ao igual? que recebe dois objectos e
testa se s~ao os mesmos ou se s~ao combinac~oes de objectos iguais, i.e.
> (igual? 1 1)
t
> (igual? (cons (cons 1 2) (cons 2 3))
(cons (cons 1 2) (cons 2 3)))
t
Solu
ca~o do Exerc
cio 7.2.1

(defun igual? (obj1 obj2)


(cond ((atom obj1) (and (atom obj2) (eql obj1 obj2)))
((atom obj2) nil)
((igual? (car obj1) (car obj2))
(igual? (cdr obj1) (cdr obj2)))))

Esta func~ao ja existe em Lisp e designa-se equal.

7.3 Abstracc~ao de Dados


A abstrac~ao de dados e uma forma de aumentar a modularidade. Se
decidirmos implementar numeros racionais, teremos de pensar em com-
binar dois numeros|o numerador e o denominador, e de os tratar co-
mo um todo. Se n~ao fosse possvel considerar aquela combinac~ao de
numeros como uma abstrac~ao (um racional), toda a sua utilizac~ao seria
extremamente difcil. Por exemplo, para se somar dois numeros racio-
nais, seria necessario usar uma operac~ao para o calculo do numerador, e
outra operac~ao para o calculo do denominador, em vez de se pensar nu-
ma operac~ao generica, soma-racional, que receberia dois argumentos|
dois racionais|e calcularia um terceiro numero|um racional.
Para nos abstrairmos da complexidade de um numero racional, deve-
mos de nir func~oes que os manipulam internamente. Podemos comecar
por de nir uma func~ao que constroi um numero racional a partir do
numerador e do denominador.
(defun racional (numerador denominador)
(cons numerador denominador))

Para sabermos qual e o numerador ou o denominador de um dado


numero racional podemos de nir:

33
(defun numerador (racional)
(car racional))

(defun denominador (racional)


(cdr racional))

Assim, ja ja podemos escrever a func~ao que calcula a soma de dois
racionais, usando a formula nd11 + nd22 = n1dd21+dn2 2d1 .
(defun +racional (r1 r2)
(racional (+ (* (numerador r1) (denominador r2))
(* (numerador r2) (denominador r1)))
(* (denominador r1) (denominador r2))))

Para simpli car a escrita de racionais, podemos de nir uma func~ao


que escreve um racional de acordo com uma convenc~ao qualquer.
(defun escreve-racional (racional)
(format t "~a/~a" (numerador racional)
(denominador racional)))

Agora, ja podemos calcular a seguinte express~ao:


> (escreve-racional (+racional (racional 1 2)
(racional 1 3)))
5/6

Exerc cio 7.3.1 De na as restantes fun


c~oes do tipo abstracto de in-
formac~ao racional: -racional, *racional, e /racional.
Solu
ca~o do Exerc
cio 7.3.1

(defun -racional (r1 r2)


(racional (- (* (numerador r1) (denominador r2))
(* (numerador r2) (denominador r1)))
(* (denominador r1) (denominador r2))))

(defun *racional (r1 r2)


(racional (* (numerador r1) (numerador r2))
(* (denominador r1) (denominador r2))))

(defun /racional (r1 r2)


(racional (* (numerador r1) (denominador r2))
(* (denominador r1) (numerador r2))))

Como se v^e, tratamos um numero racional como um so objecto, e


separamos a parte do programa que usa racionais da parte que os im-
plementa como pares de inteiros. Esta tecnica designa-se por abstrac~ao
de dados.
A abstrac~ao e a melhor maneira de lidar com a complexidade. A
abstrac~ao de dados permite-nos isolar a utilizac~ao dos dados do modo
como eles est~ao implementados, atraves da utilizac~ao de barreiras de
abstrac~ao. Essas barreiras consistem em limitar a utilizac~ao dos dados a
um pequeno conjunto de func~oes (racional, numerador e denominador)
que escondem a maneira como eles est~ao implementados. Ao utilizador
34
de um dado tipo de dados, apenas se diz quais as func~oes que ele pode
usar para os manipular, e n~ao qual o funcionamento das func~oes que
implementam aquele tipo de dados.
Seguindo esta metodologia, se precisarmos de testar a igualdade
de racionais, devemos escrever uma func~ao que o faca usando apenas
as func~oes de manipulac~ao de racionais, i.e., racional, numerador e
denominador:

(defun =racional (r1 r2)


(and (= (numerador r1) (numerador r2))
(= (denominador r1) (denominador r2))))

Exerc
cio 7.3.2 A func~ao que compara dois racionais n~ao funciona
correctamente para todos os casos. Assim,
> (=racional (racional 4 6) (racional 4 6))
t
> (=racional (racional 4 6) (racional 2 3))
nil

Qual e o problema? Como e que se pode resolver?


Solu
ca~o do Exerc
cio 7.3.2 O problema esta no facto de os racionais n~ao estarem ambos
reduzidos a sua forma mais simples. Para reduzir um racional, basta dividir o numerador e
o denominador pelo maior divisor comum entre os dois.

Exerc cio 7.3.3 Escreva uma fun c~ao que calcule o maior divisor co-
mum entre dois numeros. Para isso, use o algoritmo de Euclides que
diz que se r e o resto da divis~ao de a por b, ent~ao o maior divisor
comum entre a e b e tambem o maior divisor comum entre b e r:
mdc(a,b)=mdc(b,r). Como e natural, quando o resto e zero, o maior
divisor comum e o proprio b.
Solu
ca~o do Exerc
cio 7.3.3

(defun mdc (a b)
(if (= b 0)
a
(mdc b (mod a b))))

Exerc cio 7.3.4 Empregue o metodo de Euclides para reescrever a fun-


c~ao racional de modo a so construir numeros na forma reduzida.
Solu
ca~o do Exerc
cio 7.3.4

(defun racional (n d)
(let ((mdc (mdc n d)))
(cons (/ n mdc) (/ d mdc))))

Repare-se como se alterou a implementac~ao dos numeros racionais sem afectar as ope-
rac~oes que usavam numeros racionais, como +racional ou *racional.

35
7.4 Tipos Abstractos de Informac~ao
A teoria dos tipos abstractos de informac~ao diz que o conceito funda-
mental para a abstracc~ao de dados e a de nic~ao de uma interface entre
a implementac~ao dos dados e a sua utilizac~ao. Essa interface e consti-
tuida por func~oes que se podem classi car em categorias: construtores,
selectores, reconhecedores e testes. Estas func~oes s~ao de nidas em ter-
mos dos objectos mais primitivos que implementam o tipo de dados que
se quer de nir.
Os construtores s~ao as func~oes que criam um objecto composto a
partir dos seus elementos mais simples. Por exemplo, a func~ao racional
e um construtor para o tipo racional.
Os selectores s~ao as func~oes que recebem um objecto composto e
devolvem as suas partes. As func~oes numerador e denominador s~ao
exemplos de selectores.
Os reconhecedores s~ao as func~oes que reconhecem certos objectos
especiais do tipo de dados que se esta a de nir. A func~ao zerop e um
reconhecedor para o tipo numero do Lisp.
Finalmente, os testes s~ao func~oes que comparam objectos do tipo
que se esta a de nir. A func~ao =racional e um exemplo de uma func~ao
desta categoria. Como se pode veri car pela func~ao =racional, por
vezes, os testes s~ao implementados usando os proprios selectores.
Para que abstracc~ao de dados seja correctamente realizada, e fun-
damental de nir o conjunto de construtores, selectores, reconhecedores
e testes. Todos os programas que pretenderem utilizar aquele tipo de
dados s~ao obrigados a usar apenas aquelas func~oes. Isso permite que se
possa alterar a implementac~ao do tipo de dados sem afectar os progra-
mas que o utilizam.
A implementac~ao de estruturas de dados complexas so e correcta-
mente realizada quando se segue esta metodologia com extremo rigor.
Exerc
cio 7.4.1 De na o teste >racional.
Solu
ca~o do Exerc
cio 7.4.1 Reduzindo as fracc~oes ao mesmo denominador, basta-nos
comparar os numeradores.
(defun >racional (r1 r2)
(> (* (numerador r1) (denominador r2))
(* (numerador r2) (denominador r1))))

Quando um tipo abstracto de informac~ao tem de interagir com um


utilizador, quer para lhe pedir uma descric~ao de um elemento do ti-
po, quer para lhe apresentar uma descric~ao de um elemento do ti-
po, usa os denominados transformadores de entrada/sada. A func~ao
escreve-racional  e um exemplo de um transformador de sada para o
tipo racional. Ela limita-se a a apresentar uma representac~ao compre-
ensvel de um numero racional. O transformador de entrada realiza a
operac~ao inversa, i.e., constroi um elemento do tipo abstracto a partir
de uma representac~ao fornecida.

36
8 Listas
As listas s~ao um dos componentes fundamentais da linguagem Lisp. O
nome da linguagem e, alias, uma abreviac~ao de \list processing". Como
iremos ver, as listas constituem uma estrutura de dados extremamente
exvel.
8.1 Operaco~es sobre Listas
Em Lisp, quando o segundo elemento de um cons e outro cons, o Lisp
escreve o resultado sob a forma de uma lista:
> (cons 1 (cons 2 (cons 3 (cons 4 5))))
(1 2 3 4 . 5)

Se o ultimo elemento e a constante nil, o Lisp considera que ela


representa a lista vazia, pelo que escreve:
> (cons 1 (cons 2 (cons 3 (cons 4 nil))))
(1 2 3 4)

Esta notac~ao designa-se de lista e e esta que o Lisp usa para simpli -
car a leitura e a escrita. Uma lista e ent~ao uma sequ^encia de elementos.
Nesta optica, a func~ao car devolve o primeiro elemento de uma lista,
enquanto a func~ao cdr devolve o resto da lista. A func~ao cons pode ser
vista como recebendo um elemento e uma lista e devolve como resultado
uma nova lista correspondente a junc~ao daquele elemento no princpio
daquela lista. Segundo esta abordagem, a func~ao cons e um construtor
do tipo abstracto de informac~ao lista, enquanto as func~oes car e cdr
s~ao selectores.
Uma lista vazia e uma sequ^encia sem qualquer elemento e pode ser
escrita como nil ou ainda mais simplesmente (). A lista vazia e o
elemento mais primitivo do tipo lista. nil e o constructor do elemento
primitivo. Pode-se testar se uma lista e vazia com a func~ao null. A
func~ao null e, portanto, um reconhecedor do tipo lista.
> (null nil)
t
> (null (cons 1 (cons 2 nil)))
nil

Exerccio 8.1.1 Escreva uma fun


c~ao que calcula uma lista de todos os
numeros desde a ate b.
Solu
ca~o do Exerc
cio 8.1.1

(defun enumera (a b)
(if (> a b)
nil
(cons a (enumera (1+ a) b))))

> (enumera 1 10)


(1 2 3 4 5 6 7 8 9 10)

37
Embora as listas n~ao sejam mais do que uma estruturac~ao particular
de celulas cons, podendo por isso ser acedidas com as func~oes car e cdr,
e considerado melhor estilo de programac~ao usar as func~oes equivalentes
first e rest. first devolve o primeiro elemento da lista enquanto rest
devolve o resto da lista, i.e., sem o primeiro elemento. Do mesmo modo,
o predicado null deve ser substitudo pela seu equivalente endp.
> (first (enumera 1 10))
1
> (rest (enumera 1 10))
(2 3 4 5 6 7 8 9 10)
Exerc
cio 8.1.2 Escreva uma func~ao que ltra uma lista, devolven-
do uma lista com os elementos que veri cam um determinado criterio.
Utilize-a para encontrar os numeros pares entre 1 e 20.
Solu
ca~o do Exerc
cio 8.1.2

(defun filtra (pred lista)


(cond ((null lista) nil)
((funcall pred (first lista))
(cons (first lista) (filtra pred lista)))
(t (filtra pred (rest lista)))))

> (filtra (function par?) (enumera 1 20))


(2 4 6 8 10 12 14 16 18 20)
Esta func~ao ja existe em Lisp e denomina-se remove-if-not.
Quando se pretendem construir listas pode-se usar tambem a func~ao
list. Esta func~ao recebe qualquer numero de argumentos e constroi
uma lista com todos eles.
> (list 1 2 3 4)
(1 2 3 4)
> (first (list 1 2 3 4))
1
> (rest (list 1 2 3 4))
(2 3 4)
> (list 1 2 (list 10 20) 3 4)
(1 2 (10 20) 3 4)
Como se v^e e possvel construir listas dentro de listas. Lisp permite
tambem a construc~ao de listas directamente no avaliador. Idealmente,
bastaria escrever (1 2 3 ...), so que isso seria avaliado segundo as regras
de avaliac~ao das combinac~oes. O numero 1 seria considerado um ope-
rador e os restantes elementos da lista os operandos. Para evitar que
uma lista possa ser avaliada podemos usar a forma especial quote, que
devolve o seu argumento sem o avaliar.
> (quote (1 . (2 . (3 . nil))))
(1 2 3)
> (quote (1 2 3 4 5 6 7 8 9 10))
(1 2 3 4 5 6 7 8 9 10)
> (filtro (function par?) (quote (1 2 3 4 5 6 7 8 9 10)))
(2 4 6 8 10)

38
Uma vez que as formas especiais quote e function s~ao bastante
utilizadas, Lisp fornece um meio de se simpli car a sua utilizac~ao. Se
dermos ao avaliador uma express~ao precedida por uma plica (quote em
Ingl^es), e como se tivessemos empregue a forma especial quote. A
substituic~ao e feita durante a leitura da express~ao. Do mesmo modo, se
precedermos uma func~ao ou uma lambda por #' (cardinal-plica) e como
se tivessemos empregue a forma especial function. 'exp e equivalente
a (quote exp ), enquanto que #'exp e equivalente a (function exp).
> '(1 2 3 4 5)
(1 2 3 4 5)
> (filtra #'par '(1 2 3 4 5 6))
(2 4 6)

Exerc
cio 8.1.3 O que e que o avaliador de Lisp devolve para a se-
guinte express~ao: (first ''(1 2 3))?
Solu
ca~o do Exerc
cio 8.1.3

(first ''(1 2 3)) !


(first (quote (quote (1 2 3)))) !
(first (quote (1 2 3))) !
quote

Como e natural, as operac~oes car e cdr podem ser encadeadas:


> (car '(1 2 3))
1
> (cdr '(1 2 3))
(2 3)
> (car (cdr '(1 2 3))
2
> (car (cdr (cdr '(1 2 3))))
3

Dado que aquele genero de express~oes e muito utilizado em Lisp,


foram compostas as varias combinac~oes, e criaram-se func~oes do tipo
(caddr exp ), que correspondem a (car (cdr (cdr exp ))). O nome
da func~ao indica quais as operac~oes a realizar. Um \a" representa um
car e um \d" representa um cdr.

> (cadr '(1 2 3))


2
> (cdddr '(1 2 3))
nil

8.2 Funco~es U teis


Exerc
cio 8.2.1 Escreva uma func~ao n-esimo que devolva o n-esimo
elemento de uma lista. Note que o primeiro elemento da lista corres-
ponde a n igual a zero.

39
Solu
ca~o do Exerc
cio 8.2.1

(defun n-esimo (n lista)


(if (= n 0)
(first lista)
(n-esimo (1- n) (rest lista))))

Esta func~ao ja existe em Lisp e denomina-se nth.

Exerc
cio 8.2.2 Escreva uma func~ao muda-n-esimo que recebe um nu-
mero n, uma lista e um elemento, e substitui o n-esimo elemento da lista
por aquele elemento. Note que o primeiro elemento da lista corresponde
a n igual a zero.
Solu
ca~o do Exerc
cio 8.2.2

(defun muda-n-esimo (n lista elem)


(if (= n 0)
(cons elem (rest lista))
(cons (first lista) (muda-n-esimo (1- n) (rest lista) elem))))

Exerc
cio 8.2.3 Escreva uma func~ao que calcula o comprimento de
uma lista, i.e., determina quantos elementos ela tem.
Solu
ca~o do Exerc
cio 8.2.3

(defun comprimento (lista)


(if (null lista)
0
(1+ (comprimento (rest lista)))))

Esta func~ao ja existe em Lisp e denomina-se length.

Exerc
cio 8.2.4 Escreva uma func~ao que recebe um elemento e uma
lista que contem esse elemento e devolve a posic~ao desse elemento na
lista.
Solu
ca~o do Exerc
cio 8.2.4

(defun posicao (elem lista)


(if (eql elem (first lista))
0
(1+ (posicao elem (rest lista)))))

Exerc
cio 8.2.5 Escreva uma func~ao que calcula o numero de atomos
que uma lista (possivelmente com sublistas) tem.
Solu
ca~o do Exerc
cio 8.2.5

(defun n-atomos (lista)


(cond ((null lista) 0)
((atom lista) 1)
(t (+ (n-atomos (first lista))
(n-atomos (rest lista))))))

Exerc cio 8.2.6 Escreva uma fun


c~ao junta que recebe duas listas co-
mo argumento e devolve uma lista que e o resultado de as juntar uma
a frente da outra.

40
Solu
ca~o do Exerc
cio 8.2.6

(defun junta (lista1 lista2)


(if (null lista1)
lista2
(cons (first lista1) (junta (rest lista1) lista2))))

Esta func~ao ja existe em Lisp e denomina-se append.

Exerc
cio 8.2.7 De na uma func~ao inverte que recebe uma lista e
devolve outra lista que possui os mesmos elementos da primeira so que
por ordem inversa.
Solu
ca~o do Exerc
cio 8.2.7

(defun inverte (lista)


(labels ((inverte-aux (lista lista-aux)
(if (null lista)
lista-aux
(inverte-aux (rest lista)
(cons (first lista) lista-aux)))))
(inverte-aux lista nil)))

Esta func~ao ja existe em Lisp e denomina-se reverse.

Exerc
cio 8.2.8 Escreva uma func~ao designada inverte-tudo que re-
cebe uma lista (possivelmente com sublistas) e devolve outra lista que
possui os mesmo elementos da primeira so que por ordem inversa, e em
que todas as sublistas est~ao tambem por ordem inversa, i.e.:
> (inverte-tudo '(1 2 (3 4 (5 6)) 7))
(7 ((6 5) 4 3) 2 1)
Solu
ca~o do Exerc
cio 8.2.8

(defun inverte-tudo (lista)


(labels ((inverte-tudo-aux (lista lista-aux)
(cond ((null lista) lista-aux)
((atom lista) lista)
(t
(inverte-tudo-aux
(rest lista)
(cons (inverte-tudo (first lista))
lista-aux))))))
(inverte-tudo-aux lista nil)))

Exerc
cio 8.2.9 Escreva uma func~ao mapear que recebe uma func~ao
e uma lista como argumentos e devolve outra lista com o resultado de
aplicar a func~ao a cada um dos elementos da lista.
Solu
ca~o do Exerc
cio 8.2.9

(defun mapear (func lista)


(if (null lista)
nil
(cons (funcall func (first lista))
(mapear func (rest lista)))))

Esta func~ao ja existe em Lisp e denomina-se mapcar.

Exerc cio 8.2.10 Escreva uma func~ao denominada alisa que recebe
uma lista (possivelmente com sublistas) como argumento e devolve outra
lista com todos os atomos da primeira e pela mesma ordem, i.e.
41
> (alisa '(1 2 (3 4 (5 6)) 7))
(1 2 3 4 5 6 7)
Solu
ca~o do Exerc
cio 8.2.10

(defun alisa (lista)


(cond ((null lista) nil)
((atom lista) (list lista))
(t (append (alisa (first lista))
(alisa (rest lista))))))

Exerc
cio 8.2.11 Escreva uma func~ao membro? que recebe um objecto
e uma lista e veri ca se aquele objecto existe na lista.
Solu
ca~o do Exerc
cio 8.2.11

(defun membro? (obj lista)


(cond ((null lista) nil)
((eql obj (first lista)) t)
(t (membro? obj (rest lista)))))

Esta func~ao ja existe em Lisp e denomina-se member. Quando ela encontra um elemento
igual na lista devolve o resto dessa lista.
> (member 3 '(1 2 3 4 5 6))
(3 4 5 6)

Exerc cio 8.2.12 Escreva uma func~ao elimina que recebe um elemen-
to e uma lista como argumentos e devolve outra lista onde esse elemento
n~ao aparece.
Solu
ca~o do Exerc
cio 8.2.12

(defun elimina (elem lista)


(cond ((null lista) nil)
((eql elem (first lista))
(elimina elem (rest lista)))
(t (cons (first lista)
(elimina elem (rest lista))))))

Esta func~ao ja existe em Lisp e denomina-se remove.

Exerc cio 8.2.13 Escreva uma func~ao substitui que recebe dois ele-
mentos e uma lista como argumentos e devolve outra lista com todas as
ocorr^encias do segundo elemento substitudas pelo primeiro.
Solu
ca~o do Exerc
cio 8.2.13

(defun substitui (novo velho lista)


(cond ((null lista) nil)
((eql velho (first lista))
(cons novo (substitui novo velho (rest lista))))
(t (cons (first lista)
(substitui novo velho (rest lista))))))

Esta func~ao ja existe em Lisp e denomina-se subst.

Exerc
cio 8.2.14 Escreva uma func~ao remove-duplicados que recebe
uma lista como argumento e devolve outra lista com todos os elementos
da primeira mas sem duplicados, i.e.:
> (remove-duplicados '(1 2 3 3 2 4 5 4 1))
(3 2 5 4 1)

42
Solu
ca~o do Exerc
cio 8.2.14

(defun remove-duplicados (lista)


(cond ((null lista) nil)
((member (first lista) (rest lista))
(remove-duplicados (rest lista)))
(t (cons (first lista)
(remove-duplicados (rest lista))))))

Esta func~ao n~ao mantem a anterior ordenac~ao da lista. Se pretendermos preservar a


ordem original, temos de testar se um elemento ja existe na lista que estamos a construir e
n~ao na que estamos a analisar.
(defun remove-duplicados2 (lista)
(labels ((remove-aux (lista lista-aux)
(cond ((null lista) nil)
((member (first lista) lista-aux)
(remove-aux (rest lista) lista-aux))
(t (cons (first lista)
(remove-aux
(rest lista)
(cons (first lista)
lista-aux)))))))
(remove-aux lista nil)))

> (remove-duplicados2 '(1 2 3 3 2 4 5 4 1))


(1 2 3 4 5)

Esta func~ao ja existe em Lisp e denomina-se remove-duplicates.

Exerc
cio 8.2.15 Escreva as func~oes inversas do cons, car e cdr, de-
signadas snoc, rac e rdc. O snoc recebe um elemento e uma lista e
junta o elemento ao m da lista. O rac devolve o ultimo elemento da
lista. O rdc devolve todos os elementos da lista menos o primeiro.
Solu
ca~o do Exerc
cio 8.2.15

(defun snoc (elem lista)


(if (null lista)
(list elem)
(cons (first lista) (snoc elem (rest lista)))))

A func~ao snoc ja existe em Lisp atraves da combinac~ao das func~oes append e list.
(defun rac (lista)
(if (null (rest lista))
(first lista)
(rac (rest lista))))

A func~ao rac ja existe em Lisp atraves da combinac~ao das func~oes first e last.
(defun rdc (lista)
(if (null (rest lista))
nil
(cons (first lista) (rdc (rest lista)))))

A func~ao rdc ja existe em Lisp e denomina-se butlast.

Exerc cio 8.2.16 As fun c~oes todos?, algum?, nenhum? e nem-todos?


s~ao predicados que recebem uma func~ao e uma lista e veri cam, respec-
tivamente, se a func~ao e verdade para todos os elementos da lista, se e
verdade para pelo menos um, se n~ao e verdade para todos e se n~ao e
verdade para pelo menos um. De na estas func~oes.
43
Solu
ca~o do Exerc
cio 8.2.16

(defun todos? (func lista)


(cond ((null lista) t)
((funcall func (first lista))
(todos? func (rest lista)))
(t nil)))

Esta func~ao ja existe em Lisp e denomina-se every.


(defun algum? (func lista)
(cond ((null lista) nil)
((funcall func (first lista)) t)
(t (algum? func (rest lista)))))

Esta func~ao ja existe em Lisp e denomina-se some.


(defun nenhum? (func lista)
(cond ((null lista) t)
((not (funcall func (first lista)))
(nenhum? func (rest lista)))
(t nil)))

Esta func~ao ja existe em Lisp e denomina-se notany.


(defun nem-todos? (func lista)
(cond ((null lista) nil)
((not (funcall func (first lista))) t)
(t (nem-todos? func (rest lista)))))

Esta func~ao ja existe em Lisp e denomina-se notevery.

8.3 Listas de Argumentos


Sendo as listas uma das estruturas basicas do Lisp, a linguagem permite
aplicar func~oes directamente a listas de argumentos atraves da func~ao
apply. Esta  e em tudo id^entica a func~ao funcall, mas em vez de ter
os argumentos da func~ao a aplicar como argumentos da func~ao funcall,
tem-nos numa lista, i.e.:
(funcall func arg 1 arg 2 ... arg n ) $
(apply func (list arg 1 arg 2 ... arg n ))

Na linguagem Common Lisp, a func~ao apply e uma fus~ao entre a


func~ao funcall e a func~ao apply primitiva, pois e da forma:
(apply func arg 1 arg 2 ... rest-args )

Nesta aplicac~ao o ultimo argumento rest-args e uma lista com


os restantes argumentos. Desta forma, pode-se escrever qualquer das
seguintes equival^encias:
(funcall func arg 1 arg 2 ... arg n ) $
(apply func (list arg 1 arg 2 ... arg n )) $
(apply func arg 1 (list arg 2 ... arg n )) $
(apply func arg 1 arg 2 ... (list arg n )) $
(apply func arg 1 arg 2 ... arg n nil))

44
8.4 Tipos Aglomerados
Um tipo aglomerado e um tipo abstracto de informac~ao que e composto
exclusivamente pela aglomerac~ao de outros tipos abstractos. O conjun-
to dos racionais e um exemplo pois, como vimos, um racional n~ao e mais
do que uma aglomerac~ao de dois inteiros. As operac~oes fundamentais
de um tipo aglomerado s~ao os seus construtores e selectores, embora
possam existir outras. Como vimos, para um racional, as operac~oes
mais utilizadas eram o construtor racional e os selectores numerador e
denominador, mas tambem foram de nidos alguns testes e os transfor-
madores de entrada/sada.
Os tipos aglomerados s~ao extremamente utilizados. Por este motivo
e costume as linguagens de alto nvel fornecerem ferramentas proprias
para os tratar. Pascal, por exemplo, permite de ni-los com a forma
especial record, enquanto que a linguagem C usa, para o mesmo efeito,
o struct.
Para exempli carmos a utilizac~ao de tipos aglomerados podemos
considerar a de nic~ao de um automovel. Um automovel e caracterizado
por uma marca, um modelo, um dado numero de portas, uma cilin-
drada, uma pot^encia, etc. Para simpli car, podemos considerar so as
tr^es primeiras. O construtor de um objecto do tipo automovel n~ao tem
mais que agrupar as informac~oes relativas a cada uma daquelas carac-
tersticas. Para isso, podemos usar a func~ao list. Assim, criamos o
construtor do tipo da seguinte forma:
(defun novo-automovel (marca modelo portas)
(list marca modelo portas))

Os selectores do tipo automovel limitam-se a determinar de que e


que um dado objecto daquele tipo e composto:
(defun automovel-marca (automovel)
(nth 0 automovel))

(defun automovel-modelo (automovel)


(nth 1 automovel))

(defun automovel-portas (automovel)


(nth 2 automovel))

Estando na posse destas func~oes, podemos criar um automovel es-


pec co, por exemplo:
> (novo-automovel 'honda 'civic 2)
(honda civic 2)

Dado aquele objecto do tipo automovel, podemos estar interessa-


dos em alterar-lhe o numero de portas, passando-as de 2 para 4, por
exemplo. Contudo, para manipularmos um tipo abstracto devemos
restringirmo-nos as operac~oes desse tipo. Precisamos, portanto, de criar
45
novas operac~oes que nos permitem modi car um objecto. Para o tipo
automovel poderiamos de nir:
(defun muda-automovel-marca (automovel nova-marca)
(muda-n-esimo 0 automovel nova-marca))

(defun muda-automovel-modelo (automovel novo-modelo)


(muda-n-esimo 1 automovel novo-modelo))

(defun muda-automovel-portas (automovel novo-portas)


(muda-n-esimo 2 automovel novo-portas))

A func~ao muda-n-esimo recebia um numero n, uma lista e um novo


elemento, e substituia o n-esimo elemento da lista pelo novo elemen-
to. Esta func~ao n~ao alterava a lista original, produzindo uma nova
lista. Desta forma, qualquer destas func~oes do tipo automovel deixa
o automovel a modi car absolutamente inalterado, produzindo um no-
vo automovel. Por este motivo, estas operac~oes devem ser vistas como
construtores do tipo automovel, pois elas criam um novo automovel a
partir de um outro ja existente. Elas n~ao permitem alterar um au-
tomovel ja criado.

9 Programac~ao Imperativa
Todas as func~oes que apresentamos anteriormente realizam operac~oes
muito variadas e algumas s~ao ate relativamente complexas, mas nenhu-
ma afecta os seus argumentos. Elas limitam-se a produzir novos objectos
a partir de outros ja existentes, sem alterar estes ultimos seja de que
forma for. Ate as proprias variaveis que introduzimos nas func~oes e
que se destinavam a guardar valores temporarios n~ao eram mais do que
par^ametros de uma lambda, e a sua inicializac~ao correspondia a invocar
a lambda com os valores iniciais como argumentos, sendo por isso inicia-
lizadas uma unica vez e nunca modi cadas. Por este motivo, nem sequer
foi apresentado nenhum operador de atribuic~ao, t~ao caracterstico em
linguagens como C e Pascal.
Este estilo de programac~ao, sem atribuic~ao, sem alterac~ao dos argu-
mentos de func~oes, e em que estas se limitam a produzir novos valores,
e designado programac~ao funcional. Neste paradigma de programac~ao,
qualquer func~ao da linguagem e considerada uma func~ao matematica
pura que, para os mesmos argumentos produz sempre os mesmos valo-
res. Nunca nada e destrudo. Uma func~ao que junta duas listas produz
uma nova lista sem alterar as listas originais. Uma func~ao que muda o
numero de portas de um automovel produz um novo automovel.
A programac~ao funcional tem muitas vantagens sobre outros estilos
de programac~ao, em especial no que diz respeito a produzir programas
muito rapidamente e minimizando os erros. Contudo, tem tambem as
suas limitac~oes, e a sua incapacidade em modi car seja o que for e

46
a maior. A partir do momento em que introduzimos a modi cac~ao
de objectos, estamos a introduzir o conceito de destruic~ao. A forma
anterior do objecto que foi modi cado deixou de existir, passando a
existir apenas a nova forma. A modi cac~ao implica a introduc~ao do
conceito de tempo. Os objectos passam a ter uma historia, e isto conduz
a um novo estilo de programac~ao.
9.1 Atribuic~ao
Para se alterar o valor de uma variavel Lisp possui um operador de
atribuic~ao. A forma especial setq recebe uma variavel e um valor e
atribui o valor a variavel.
> (let ((x 2))
(setq x (+ x 3))
(setq x (* x x))
(setq x (- x 5))
x)
20
Cada vez que se realiza uma atribuic~ao, perde-se o valor anterior que
a variavel possua. Muito embora a forma especial setq, como todas as
func~oes e forma especiais, retorne um valor, esse valor n~ao possui qual-
quer interesse. O operador de atribuic~ao serve apenas para modi car
o estado de um programa, neste exemplo, alterando o valor de x. Por
este motivo, a atribuic~ao assemelha-se mais a um comando do que a
uma func~ao. A atribuic~ao e uma ordem que tem de ser cumprida e de
que n~ao interessa o resultado. A ordem e a operac~ao fundamental da
programac~ao imperativa, em que um programa e composto por suces-
sivos comandos. Neste estilo de programac~ao, os comandos funcionam
por efeitos secundarios. O valor de cada comando n~ao tem interesse e
muitas vezes nem sequer tem signi cado falar dele.
9.2 Sequenciac~ao
A forma especial progn esta especialmente vocacionada para este genero
de utilizac~ao. Ela recebe um conjunto de express~oes que avalia sequen-
cialmente retornando o valor da ultima. Desta forma e possvel incluir
varias acc~oes no consequente ou alternativa de um if, por exemplo.
(if (> 3 2)
(progn
(print 'estou-no-consequente)
(+ 2 3))
(progn
(print 'estou na alternativa)
(* 4 5)))

Algumas das formas especiais do Lisp incluem um progn implcito.


Vimos atras um exemplo de um let que realizava quatro operac~oes em
47
sequ^encia. Isto implica que o let e, por arrastamento, as lambdas e
tudo o que for de nido com defun, possuem tambem a capacidade de
realizar operac~oes em sequ^encia. O cond esta tambem nesta categoria.
A sua sintaxe e, na realidade, a seguinte:
(cond (condi
c~ao 1 express~ ao 1l )
ao 11 ...express~
(condi
c~ao 2 express~ ao 2m )
ao 21 ...express~
.
.
.
(condi
c~ao n express~
ao n1 ...express~
ao nk ))

O cond testa cada uma das condic~oes em sequ^encia, e quando uma


delas avalia para verdade, s~ao avaliadas todas as express~oes da clausula
correpondente sendo devolvido o valor da ultima dessas express~oes.
Usando o cond, o exemplo anterior cara mais elegante:
(cond ((> 3 2)
(print 'estou-no-consequente)
(+ 2 3))
(t
(print 'estou na alternativa)
(* 4 5)))
A forma especial prog1 e semelhante ao progn. Ela recebe um
conjunto de express~oes que avalia sequencialmente retornando o valor da
primeira. A express~ao equivalente ao exemplo anterior mas utilizando
o prog1 sera:
(if (> 3 2)
(prog1
(+ 2 3)
(print 'estou-no-consequente))
(prog1
(* 4 5)
(print 'estou na alternativa)))

Note-se que a ordem de avaliac~ao das express~oes de um prog1 e igual


a de um progn. Apenas o valor retornado e diferente: e o primeiro no
caso do prog1 e o ultimo no caso do progn.
A sequenciac~ao e tambem suportada por qualquer lambda. Em con-
sequ^encia, as formas especiais que implicam a criac~ao de lambdas, como
o let e o proprio defun permitem tambem especi car mais do que uma
express~ao, sendo estas avaliadas em sequ^encia e devolvido o valor da
ultima.
9.3 Alterac~ao de Dados
A atribuic~ao n~ao esta restricta a variaveis. E tambem possvel alterar
o conteudo da maioria dos tipos de dados Lisp. Uma celula cons, por
exemplo, pode ser alterada com as func~oes rplaca e rplacd, signi can-
do, respectivamente \replace-car" e \replace-cdr".
48
> (let ((x (cons 1 2)))
(rplaca x 3)
x)
(3 . 2)

> (let ((x (cons 1 2)))


(rplacd x 3)
x)
(1 . 3)

Note-se que estas func~oes s~ao uma forma de atribuic~ao e, como tal,
destroem o conteudo anterior da estrutura a que se aplicam. Por este
motivo, este genero de func~oes diz-se destrutivo. Na sequ^encia logica
da convenc~ao usual em Lisp para a de nic~ao de reconhecedores, que
terminam sempre com um ponto de interrogac~ao (ou com a letra \p"
de predicado), deve-se colocar um ponto de exclamac~ao no m do nome
das func~oes destrutivas para salientar o seu caracter imperativo.
Exerc
cio 9.3.1 Escreva uma func~ao junta! (note-se o ponto de ex-
clamac~ao) que recebe duas listas como argumento e devolve uma lista
que e o resultado de as juntar destrutivamente uma a frente da outra,
i.e., faz a cauda da primeira lista apontar para a segunda.
Solu
ca~o do Exerc
cio 9.3.1 Para resolver o problema, basta atingir a ultima celula cons
da primeira lista (usando uma func~ao local fim) e ligar destrutivamente o seu cdr (que e
nil) 
a segunda lista.
(defun junta! (lista1 lista2)
(labels ((fim (lista)
(if (null (rest lista))
lista
(fim (rest lista)))))
(if (null lista1)
lista2
(progn
(rplacd (fim lista1) lista2)
lista1))))

Esta func~ao ja existe em Lisp e denomina-se nconc.

Exerc
cio 9.3.2 Analise o seguinte exemplo funcional e imperativo:
> (let ((x '(1 2 3)))
(junta x x))
(1 2 3 1 2 3)

> (let ((x '(1 2 3)))


(junta! x x))
(1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 ...

Solu
ca~o do Exerc
cio 9.3.2 O que se esta a passar e que a alterac~ao destrutiva da cauda
da lista x de modo a apontar para o princpio da lista x implicou a criac~ao de uma lista
circular, em que os elementos se sucedem uns aos outros inde nidamente.

49
Exerc
cio 9.3.3 Escreva uma func~ao muda-n-esimo! (note-se o ponto
de exclamac~ao) que recebe um numero n, uma lista e um elemento, e
substitui o n-esimo elemento da lista por aquele elemento. Note que o
primeiro elemento da lista corresponde a n igual a zero.
Solu
ca~o do Exerc
cio 9.3.3

(defun muda-n-esimo! (n lista elem)


(if (= n 0)
(rplaca lista elem)
(muda-n-esimo! (1- n) (rest lista) elem))
lista)

Repare-se como a ultima coisa a ser avaliada e devolvida pela func~ao e o par^ametro
lista original. Se tal n~ao fosse feito, perderiamos acesso aos elementos anteriores aquele que
se estava a modi car. Na de nic~ao segundo a programac~ao funcional, esses valores eram
recuperados por sucessivas inserc~oes na nova lista que tinham sido deixadas em suspenso.

Exerc
cio 9.3.4 Reescreva as operac~oes do tipo abstracto de informa-
c~ao automovel que alteravam as caractersticas de um elemento do tipo
de forma a torna-las destrutivas.
Solu
ca~o do Exerc
cio 9.3.4

(defun muda-automovel-marca! (automovel nova-marca)


(muda-n-esimo! 0 automovel nova-marca))

(defun muda-automovel-modelo! (automovel novo-modelo)


(muda-n-esimo! 1 automovel novo-modelo))

(defun muda-automovel-portas! (automovel novo-portas)


(muda-n-esimo! 2 automovel novo-portas))

Quando as operac~oes de um tipo abstracto alteram um elemento do


tipo, essas operac~oes s~ao classi cadas como modi cadores do tipo abs-
tracto. Os modi cadores, como caso especial da atribuic~ao, s~ao muito
empregues em programac~ao imperativa. Note-se que os modi cadores
possuem todos os problemas da atribuic~ao simples, nomeadamente a al-
terac~ao ser destrutiva. Isto levanta problemas quando se testa igualdade
em presenca de modi cac~ao.
> (let ((x (novo-automovel 'honda 'civic 2)))
(let ((y (muda-automovel-portas x 4)))
(eql x y)))
NIL

> (let ((x (novo-automovel 'honda 'civic 2)))


(let ((y (muda-automovel-portas! x 4)))
(eql x y)))
T

Repare-se que no primeiro exemplo (funcional), o automovel modi -


cado e, logicamente, diferente do automovel original. x e y representam
automoveis diferentes. No segundo exemplo (imperativo), a modi cac~ao
do automovel que x representa e realizada sobre esse proprio automovel,

50
de modo que x e y acabam por representar um mesmo automovel (mo-
di cado). Muito embora esta situac~ao possa ter vantagens, ela permite
tambem a introduc~ao de erros muito subtis e extremamente difceis de
tirar. Para dar apenas um pequeno exemplo, repare-se que o automovel
que x representa passou a ter quatro portas embora uma leitura super-
cial do codigo sugira que ele foi criado com apenas duas.
9.4 Repetic~ao
Para alem dos operadores de atribuic~ao (setq, rplaca, rplacd, etc.) e
de sequenciac~ao (progn, prog1, etc.) a linguagem Common Lisp possui
muitas outras formas especiais destinadas a permitir o estilo de pro-
gramac~ao imperativa. De destacar s~ao as estruturas de controle de
repetic~ao, tais como o loop, o do, o dotimes e ainda outras adequadas
para iterar ao longo de listas.
O loop e a mais generica de todas as formas de repetic~ao. Ela recebe
um conjunto de express~oes que avalia sequencialmente, repetindo essa
avaliac~ao em ciclo ate que seja avaliada a forma especial return. Esta
ultima recebe uma express~ao opcional e termina o ciclo em que esta
inserida, fazendo-o devolver o valor daquela express~ao.
A seguinte express~ao exempli ca um ciclo que escreve todos os nu-
meros desde 0 ate 100, retornando o smbolo fim no nal do ciclo.
(let ((n 0))
(loop
(print n)
(setq n (1+ n))
(when (> n 100)
(return 'fim))))

A forma especial do e um pouco mais so sticada que o loop. Ela


permite estabelecer variaveis, inicializa-las e incrementa-las automatica-
mente, testar condic~oes de paragem com indicac~ao do valor a retornar
e repetir a execuc~ao de codigo. Se reescrevermos o exemplo anterior
usando a forma especial do, obtemos:
(do ((n 0 (1+ n)))
((> n 100) 'fim)
(print n))

Tal como o loop, a forma especial do pode ser interrompida em


qualquer altura com um return, retornando o valor opcional fornecido
com o return.
Apesar do estilo mais utilizado na maioria das linguagens de pro-
gramac~ao ser o imperativo, ele e muito pouco natural em Lisp.
A falta de naturalidade resulta, por um lado, de os programas em
Lisp se decomporem geralmente em pequenas func~oes que calculam va-
lores, invalidando uma abordagem baseada em ciclos de alterac~ao de
variaveis, tpica da programac~ao imperativa.
51
Por outro lado, a grande maioria de tipos de dados existentes em Lisp
s~ao inerentemente recursivos, o que di culta o seu tratamento segundo
o estilo imperativo.
Apesar de muito pouco practico para usar em Lisp, a programac~ao
imperativa tem algumas vantagens, das quais a possibilidade de atri-
buic~ao e a maior (e tambem a mais perigosa).

10 Modelo de Ambientes
Ate agora vimos que as variaveis eram apenas designac~oes para valores.
Quando se avaliava uma express~ao, as variaveis desapareciam, sendo
substitudas pelos seus valores. A partir do momento em que podemos
alterar o valor de uma variavel, o seu comportamento torna-se menos
claro.
Para se explicar correctamente este comportamento e necessario pas-
sar para um modelo de avaliac~ao mais elaborado designado modelo de
avaliac~ao em ambientes.
Neste modelo, uma variavel ja n~ao e uma designac~ao de um valor
mas sim uma designac~ao de um objecto que contem um valor. Esse
objecto pode ser visto como uma caixa onde se guardam coisas. Em
cada instante, a variavel designa sempre a mesma caixa, mas esta pode
guardar coisas diferentes. Segundo o modelo de ambientes, o valor de
uma variavel e o conteudo da caixa que ela designa. A forma especial
setq  e a operac~ao que permite meter valores dentro da caixa.
As variaveis s~ao guardadas em estruturas denominadas enquadra-
mentos. Por exemplo, cada vez que usamos a forma let e criado um
novo enquadramento para conter as variaveis estabelecidas pelo let.
Todas as express~oes pertencentes ao corpo do let ser~ao avaliadas em
relac~ao a este enquadramento. Imaginemos agora a seguinte situac~ao:
(let ((x 1))
(let ((y 2)
(z 3))
(+ x y z)))

Neste exemplo, o corpo do primeiro let e um novo let. Existem


portanto dois enquadramentos. Estes enquadramentos est~ao organiza-
dos de modo a que o corpo do segundo let consiga fazer refer^encia as
tr^es variaveis x, y e z.
Para isso, os enquadramentos s~ao estruturados sequencialmente, des-
de aquele que for textualmente mais interior ate ao mais exterior. Essa
sequ^encia de enquadramentos e designada por ambiente.
Cada enquadramento e uma tabela de ligac~oes, que associa as va-
riaveis aos seus valores correspondentes. Uma variavel nunca pode estar
repetida num enquadramento, embora possa aparecer em varios enqua-
dramentos de um ambiente. Cada enquadramento aponta para o am-
biente envolvente, excepto o ambiente global, que e composto por um

52
unico enquadramento sem ambiente envolvente. E no ambiente global
que est~ao guardadas todas as func~oes que usamos normalmente.
10.1 A^ mbito Lexico
A regra de avaliac~ao de variaveis em ambientes diz que o valor de uma
variavel em relac~ao a um ambiente e dado pela ligac~ao dessa variavel
no primeiro enquadramento em que ela surja ao longo da sequ^encia de
enquadramentos que constituem esse ambiente. Se nenhum enquadra-
mento possui uma ligac~ao para essa variavel ela diz-se n~ao ligada. E um
erro avaliar variaveis n~ao ligadas.
Uma vez que os enquadramentos de um ambiente est~ao associados
lexicamente as formas que os criaram, e possvel determinar o ^ambito de
uma variavel qualquer simplesmente observando o texto do programa.
Usando o modelo de avaliac~ao em ambientes e muito facil perceber
o comportamento da forma especial let (que n~ao e mais do que uma
simpli cac~ao de uma lambda) e da forma especial setq. Cada let
aumenta o ambiente em que e avaliado com um novo enquadramento,
estabelecendo ligac~oes para as suas variaveis. Quando se pretende saber
o valor de uma variavel percorre-se o ambiente, comecando pelo primeiro
enquadramento ate se encontrar a ligac~ao correspondente. Se ela n~ao
aparecer, vai-se passando de enquadramento em enquadramento ate se
atingir o ambiente global, e se a tambem n~ao existir nenhuma ligac~ao
para aquela variavel e gerado um erro de variavel n~ao ligada. O setq
altera o valor da variavel que aparece estabelecida no enquadramento
mais proximo do ponto onde o setq e avaliado. Se se atingir o ambiente
global, e se a tambem n~ao existir nenhuma ligac~ao para aquela variavel,
e criada essa ligac~ao no ambiente global.
> (let ((x 10))
(+ x y))
Error: Unbound variable: Y
> (setq y 20)
20
> (let ((x 10))
(+ x y))
30

Como se v^e pelo exemplo. A partir do momento em que se esta-


beleceu uma ligac~ao no ambiente global para a variavel y, ja e possvel
avaliar aquele let apesar de ele fazer refer^encia a uma variavel livre. No
entanto, a utilizac~ao da forma especial setq para criar variaveis globais
e considerada pouca correcta e o compilador emitira um aviso se en-
contrar uma destas formas de utilizac~ao. A utilizac~ao do setq deve ser
restricta a modi cac~oes do valor de variaveis previamente estabelecidas.
As regras de avaliac~ao do modelo de ambientes s~ao, em tudo, equi-
valentes as do modelo classico, excepto no que diz respeito a aplicac~ao
de func~oes.

53
No modelo de ambientes todas as func~oes possuem um ambiente
associado, que corresponde aquele que existia quando a func~ao foi de-
nida. Quando se aplica uma func~ao aos seus argumentos, cria-se um
novo ambiente, cujo primeiro enquadramento contem as ligac~oes dos
par^ametros formais da func~ao aos seus argumentos e cujo ambiente en-
volvente e aquele em que a func~ao foi de nida. E em relac~ao a este novo
ambiente que se avalia o corpo da func~ao.
Note-se que a forma especial defun de ne func~oes (i.e., cria uma
ligac~ao entre o nome da func~ao e a lambda correspondente ao seu cor-
po) sempre no ambiente global, enquanto que setq altera a ligac~ao de
uma variavel no primeiro enquadramento do ambiente em que a forma
especial e avaliada. So se a variavel n~ao for encontrada na sequ^encia de
enquadramentos e que o setq cria uma no ambiente global.
Exerc
cio 10.1.1 Interprete o comportamento da seguinte func~ao:
(let ((valor 0))
(defun incrementa ()
(setq valor (1+ valor))
valor))

> (incrementa)
1
> (incrementa)
2
> (incrementa)
3
Solu
ca~o do Exerc
cio 10.1.1 Quando se aplica a func~ao incrementa, o ambiente em que
ela foi de nida e ampliado com um enquadramento vazio pois a func~ao n~ao tem par^ametros.
Neste ambiente, a variavel valor possuia o valor zero. A avaliac~ao do corpo da func~ao altera
o valor daquela variavel para 1, e devolve o seu novo valor. Cada nova avaliac~ao altera a
ligac~ao que tinha sido estabelecida na de nic~ao da func~ao.
E de salientar o caracter evolutivo da func~ao incrementa. Na meto-
dologia da programac~ao funcional, o resultado de uma func~ao so depen-
de dos seus argumentos, e uma func~ao sem argumentos equivale a uma
constante. Sempre que a func~ao e aplicada ela tem de devolver sempre
o mesmo resultado. A partir do momento que se admite a atribuic~ao,
abre-se a porta a inumeras possibilidades de violac~ao do modelo fun-
cional. As func~oes passam a ser caracterizadas por um estado local e
o resultado da aplicac~ao de uma func~ao passa a depender n~ao so dos
argumentos mas tambem do estado local.
Exerccio 10.1.2 Uma excelente aplica c~ao de func~oes com estado local
e na criac~ao de geradores de sequ^encias de numeros, i.e., func~oes sem
argumentos que, a cada invocac~ao, devolvem o elemento que sucede lo-
gicamente a todos os que foram gerados anteriormente. Seguindo este
paradigma, de na o gerador da sequ^encia de Fibonacci, que e represen-
tada pela sucess~ao crescente 1, 2, 3, 5, 8, ..., em que cada numero e a
soma dos dois ultimos que o precedem.
54
Solu
ca~o do Exerc
cio 10.1.2 Para implementar a sucess~ao de Fibonacci, e conveniente
usar a vers~ao iterativa da func~ao de Fibonacci, para se poder determinar mais rapidamente o
proximo elemento da sequ^encia em func~ao dos dois ultimos que o precederam. Assim sendo,
a func~ao necessita de duas variaveis no seu estado local, para guardar o ultimo e o penultimo
valores produzidos.
(let ((ultimo 1) (penultimo 0))
(defun gera-fib ()
(let ((novo (+ ultimo penultimo)))
(setq penultimo ultimo
ultimo novo)
novo)))

> (gera-fib)
1
> (gera-fib)
2
> (gera-fib)
3

Exerc
cio 10.1.3 Infelizmente, a func~ao gera-fib n~ao sabe recome-
car. Para isso, e necessario de ni-la (compila-la) outra vez. Este pro-
cesso, como e logico, e muito pouco practico, em especial porque obriga
o utilizador a ter acesso ao codigo do gerador. Complemente a de nic~ao
da func~ao gera-fib com uma outra func~ao denominada repoe-fib que
rep~oe o gerador em condic~oes de recomecar de novo desde o princpio.
Soluca
~o do Exerc cio 10.1.3 Para implementar reposi c~ao da sucess~ao de Fibonacci, e
necessario que haja acesso as variaveis de estado do processo. Isto implica que a func~ao
repoe- b tem de estar de nida no mesmo ^ambito lexico da func~ao gera-fib.
(let ((ultimo 1) (penultimo 0))
(defun repoe-fib ()
(setq ultimo 1
penultimo 0))
(defun gera-fib ()
(let ((novo (+ ultimo penultimo)))
(setq penultimo ultimo
ultimo novo)
novo)))

> (gera-fib)
1
> (gera-fib)
2
> (repoe-fib)
0
> (gera-fib)
1

10.2 A^ mbito Din^amico


A criac~ao de variaveis globais em Common Lisp deve ser feita usando
as formas especiais defconstant para criar constantes, defvar para
criar variaveis inicializaveis uma unica vez e defparameter para criar
variaveis inicializaveis varias vezes. Esta diferenca e relevante sobretudo
durante a fase de desenvolvimento e depurac~ao de programas.

55
> (defconstant acelaracao-gravidade 9.8)
ACELARACAO-GRAVIDADE
> (defvar *y*)
*Y*
> (defparameter *z* 10)
*Z*

Note-se a convenc~ao adoptada para as variaveis globais de usar no-


mes compreendidos entre um par de asteriscos. Quando se de nem
constantes essa convenc~ao n~ao se aplica.
O facto de podermos ter variaveis globais introduz uma alterac~ao
nas regras de avaliac~ao. Tnhamos visto que as variaveis que eram
par^ametros de func~oes (e, como tal, as variaveis introduzidas por um
let) tinham ^ ambito lexico, ou seja, apenas podiam ser referidas dentro
da regi~ao textual que as introduziu. No entanto, as variaveis globais
como aceleracao-gravidade, *y* ou *z* podem ser referidas de qual-
quer ponto do programa, fazendo com que o seu ^ambito passe a ser vago.
No entanto, apesar de ser possvel refer^enciar a variavel *y*, sera pro-
duzido um erro quando tentarmos determinar o seu valor, uma vez que
ele ainda esta inde nido. Sera preciso ligarmos um valor aquela variavel
antes de a podermos avaliar e, para isso, podemos usar um let.
> (defvar *y*)
*Y*
> (defun teste () (+ *y* *y*))
TESTE
> (teste)
Error: Unbound variable: *Y*
> (let ((*y* 10)) (teste))
20
>*y*
Error: Unbound variable: *Y*

Repare-se que apesar de, momentaneamente, termos atribuido um


valor a variavel *y* por intermedio de um let, ela perdeu esse valor
assim que terminou o let. A durac~ao da variavel *y* e, assim, din^amica.
Apenas as variaveis lexicas possuem durac~ao inde nida. Variaveis como
*y* e *z* dizem-se especiais e possuem a^mbito vago e durac~ao din^amica.
Esta combinac~ao da origem a um comportamento que se designa de
^ambito din^amico.
Um dos aspectos mais crticos na utilizac~ao de variaveis de ^ambito
din^amico e o facto de, geralmente, n~ao ser su ciente ler o codigo de um
programa para perceber o que e que ele vai fazer|e tambem preciso
executa-lo. O seguinte exemplo explica este ponto.
Imaginemos as seguintes de nic~oes:

56
> (let ((x 2))
(defun soma-2 (y)
(+ x y)))
SOMA-2
> (let ((x 1000))
(soma-2 1))
3
> (defparameter *x* 1)
*X*
> (let ((*x* 2))
(defun soma-2 (y)
(+ *x* y)))
SOMA-2
> (let ((*x* 1000))
(soma-2 1))
1001
O primeiro exemplo envolve apenas variaveis lexicas. Da que baste
observar o texto da func~ao soma-2 para se perceber que a variavel x
usada em (+ x y) toma sempre o valor 2.
No segundo exemplo, a unica diferenca esta no facto de a variavel
*x* ser especial. Nesta situa c~ao a func~ao soma-2 n~ao usa o valor de
*x* que existia no momento da de ni ca~o da func~ao, mas sim o valor de
*x* que existe no momento da execu ca~o da func~ao. Desta forma, ja n~ao
e su ciente observar o texto da func~ao soma-2 para perceber o que ela
faz. Por este motivo, o uso excessivo de variaveis din^amicas pode tornar
um programa difcil de ler e, consequentemente, difcil de desenvolver e
difcil de corrigir.

11 Par^ametros Especiais
11.1 Par^ametros Opcionais
Como referimos anteriormente, a forma especial return, que se pode
utilizar dentro de um loop, possui um par^ametro opcional, que e o valor
a retornar do ciclo. Isto quer dizer que o argumento que deveremos pas-
sar para esse par^ametro pode ser omitido. Esta caracterstica e tambem
muito util para a de nic~ao de func~oes, permitindo que ela possa assumir
certos par^ametros por omiss~ao.
Para de nirmos func~oes que t^em par^ametros opcionais temos de usar
um quali cador especial designado &optional na lista de par^ametros
formais. Esse quali cador indica que todos os par^ametros que se lhe
seguem s~ao opcionais e que, se os argumentos correspondentes forem
omitidos, eles valem nil. Se pretendermos um valor diferente para um
par^ametro, podemos inserir o par^ametro numa lista com o seu valor. A
seguinte func~ao mostra como se pode de nir a func~ao incr que incre-
menta o seu argumento de uma unidade, ou de uma quantidade que lhe
seja fornecida.
57
(defun incr (x &optional (i 1))
(+ x i))

> (incr 10)


11
> (incr 10 5)
15

Exerc cio 11.1.1 De na a fun


c~ao eleva, que eleva um numero a uma
determinada pot^encia. Se a pot^encia n~ao for indicada devera ser consi-
derada 2. Nota: a express~ao (expt x y) determina a pot^encia y de x,
i.e., xy .
Solu
ca~o do Exerc
cio 11.1.1

(defun eleva (x &optional (n 2))


(expt x n))

Os par^ametros opcionais permitem ainda simpli car a escrita de


processos iterativos, ao aumentarem o numero de variaveis de estado
sem obrigar o utilizador a inicializa-las. No entanto, e usual considerar-
se esta utilizac~ao como mau estilo de programac~ao.
Exerccio 11.1.2 Reescreva a fun c~ao factorial de forma a gerar um
processo iterativo mas sem usar func~oes auxiliares.
Solu
ca~o do Exerc
cio 11.1.2

(defun fact (n &optional (result 1))


(if (= n 0)
result
(fact (1- n) (* n result))))

11.2 Par^ametros de Resto


Para alem do quali cador &optional existem ainda o &rest e o &key.
O &rest so pode quali car o ultimo par^ametro de uma func~ao, e indica
que esse par^ametro vai car ligado a uma lista com todos os restantes
argumentos. A ttulo de exemplo, temos:
> ((lambda (x y &rest z) (list x y z)) 1 2 3 4 5 6)
(1 2 (3 4 5 6))

O quali cador &rest permite assim construir func~oes com qualquer


numero de argumentos.
Exerc
cio 11.2.1 De na a func~ao lista que recebe qualquer numero de
argumentos e constroi uma lista com todos eles.
Solu
ca~o do Exerc
cio 11.2.1

(defun lista (&rest elems)


elems)

>(lista 1 2 3 4 5 6 7 8 9 0)
(1 2 3 4 5 6 7 8 9 0)

58
11.3 Par^ametros de Chave
O quali cador &key informa o avaliador que os par^ametros quali cados
s~ao ligados atraves de uma indicac~ao explcita de quem chama a func~ao.
Essa indicac~ao e feita designando o nome de cada par^ametro precedido
por dois pontos e indicando qual o valor a que ele deve estar ligado.
Os par^ametros que n~ao forem ligados comportam-se como se fossem
opcionais. Este genero de par^ametros dizem-se de chave (keyword).
Desta forma, o &key permite trocar a ordem dos argumentos. O
seguinte exemplo mostra o funcionamento do &key.
> ((lambda (x y &key z (w 4) k) (lista x y z w k))
1 2 :k 5 :z 3)
(1 2 3 4 5)

A grande maioria das func~oes pre-de nidas na linguagem para ma-


nipular listas possui par^ametros opcionais e de chave. Repare-se que
as chaves s~ao tratadas de forma especial pelo avaliador. Efectivamen-
te, se assim n~ao fosse, quando especi cavamos os argumentos de uma
func~ao com par^ametros de chave, o avaliador iria tentar determinar o
valor das chaves, gerando ent~ao um erro por estas n~ao terem valor. Na
realidade, quando um smbolo qualquer e precedido por dois pontos,
esse smbolo e considerado como especial, pertencendo ao conjunto dos
smbolos chaves, e avaliando para si proprio.
> ola
Error: Unbound variable: OLA
> 'ola
OLA
> :ola
:OLA

Os tipos aglomerados constituem uma das grandes utilizac~oes dos


par^ametros de chave. Nestes tipos de dados, os constructores limitam-
se a realizar um agrupamento de valores para os diversos constituintes
do tipo. Vimos o exemplo de um automovel, que era constituido por
uma marca, um modelo, um numero de portas, etc. Embora n~ao exista
qualquer raz~ao para que a marca de um automovel seja mais importan-
te que o seu numero de portas, infelizmente a ordenac~ao implcita dos
argumentos das func~oes Lisp imp~oem que assim seja. Podemos resolver
este problema usando par^ametros de chave, de forma a eliminar a orde-
nac~ao dos argumentos e permitir ao utilizador especi ca-los pela ordem
que entender, por exemplo:
(defun novo-automovel (&key marca modelo portas)
(list marca modelo portas))

> (novo-automovel :portas 2 :marca 'honda :modelo 'civic)


(honda civic 2)

59
Para alem da possibilidade de alterac~ao da ordem dos argumentos, o
quali cador &key ajuda a legibilidade do programa, ao tornar explcito
o papel que cada argumento tem na func~ao.

12 Macros
Como referimos na apresentac~ao da linguagem Lisp, existem certas for-
mas da linguagem que n~ao obedecem as regras de avaliac~ao usuais. Essas
formas designam-se formas especiais e o if e um exemplo. Cada for-
ma especial possui a sua propria regra de avaliac~ao. Vimos que, por
isso, era impossvel de nir o if como se fosse uma func~ao, pois todos
os operandos (o teste, o consequente e a alternativa) seriam avaliados.
Embora a linguagem Lisp possua muitas formas especiais, e possvel
\criar" outras formas especiais atraves da utilizac~ao de macros. Uma
macro e uma forma que a linguagem expande para outra forma, supe-
rando assim as di culdades inerentes a avaliac~ao dos argumentos que as
func~oes realizam. Na realidade, Lisp possui muito poucas formas espe-
ciais reais. A grande maioria das formas especiais s~ao implementadas
atraves de macros, usando a forma especial defmacro cuja sintaxe e
igual a da defun.
12.1 Avaliac~ao de Macros
A utilizac~ao de uma macro implica duas avaliac~oes. Na primeira, a
macro produz uma express~ao Lisp a partir dos seus argumentos, que
se designa a expans~ao da macro. Esta express~ao e ent~ao avaliada uma
segunda vez para produzir o valor nal. A ttulo de exemplo, se de -
nirmos o if como uma macro que expande para um cond e avaliarmos
a express~ao (if (> 3 4) (+ 1 2) (- 5 2)), a primeira avaliac~ao de-
vera produzir a express~ao (cond ((> 3 4) (+ 1 2)) (t (- 5 2))),
que sera avaliada segunda vez para determinar o seu valor.
Note-se que, neste exemplo, se a forma cond fosse, tambem ela, uma
macro, o processo era aplicado recursivamente ate que n~ao surgisse mais
nenhuma macro. Nessa altura, o Lisp usava a regra usual de avaliac~ao
para determinar o valor nal da express~ao
12.2 Escrita de Macros
A escrita de uma macro e inerentemente mais complexa que a escrita
de uma func~ao, sendo decomposta em quatro fases:
1. Decidir se a macro e realmente necessaria. Esta fase e de gran-
de import^ancia, pois cada vez que se de ne uma macro esta-se a
aumentar a linguagem com uma nova forma especial. Quem pre-
tende ler um programa que usa a macro e obrigado a conhecer a
sua sintaxe e sem^antica, e se o numero de macros e muito grande,
pode ser difcil perceber o codigo.

60
2. Escrever a sintaxe da macro. Nesta fase pretende-se de nir qual
vai ser a forma de utilizac~ao da macro. A sintaxe deve ser o mais
simples possvel e o mais coerente possvel com as restantes formas
da linguagem para n~ao complicar a sua leitura.
3. Escrever a expans~ao da macro. Nesta fase determina-se a ex-
press~ao Lisp que a macro deve produzir quando expandida. A
expans~ao e qualquer express~ao Lisp, que pode inclusive fazer re-
fer^encia a outras macros.
4. Escrever a macro usando a forma especial defmacro. E esta a fase
mais delicada do processo, em que se programa um processo de
transformar a forma especial que queremos de nir numa express~ao
que use outras formas especiais ja de nidas.
A ttulo de exemplo vamos de nir a forma especial meu-if cujo
objectivo e simpli car o uso do cond quando so existe um teste, um
consequente e uma alternativa.
A sintaxe da forma meu-if e:
(meu-if teste consequente alternativa )

A expans~ao da macro sera qualquer coisa da forma:


(cond (teste consequente )
(t alternativa ))

A de nic~ao da macro e:


(defmacro meu-if (teste consequente alternativa)
(list 'cond
(list teste consequente)
(list t alternativa)))

12.3 Depurac~ao de Macros


Uma vez que a aplicac~ao de uma macro mostra apenas o resultado
nal, depois de a macro ter sido expandida e a sua expans~ao avalia-
da, e necessario um meio auxiliar para visualizarmos se a expans~ao
esta a ser feita de modo correcto. Para isso o Lisp fornece as func~oes
macroexpand-1 e macroexpand que realizam a expans~ ao da macro uma
unica vez ou todas as vezes possveis, respectivamente.
> (macroexpand-1 '(meu-if (> 3 4) (+ 2 3) (- 5 3)))
(COND ((> 3 4) (+ 2 3)) (T (- 5 3)))

Exerc cio 12.3.1 Implemente a macro quando, que recebe um teste e


um conjunto de express~oes. Esta forma especial avalia o teste e, quando
este e verdade, avalia sequencialmente as express~oes, devolvendo o valor
da ultima. Se o teste e falso, a forma retorna nil sem avaliar mais nada.

61
Solu
ca~o do Exerc
cio 12.3.1 A sintaxe da macro e:
(quando teste
expr 1 expr 2 ... expr n )

A expans~ao desejada e:


(cond (teste
expr 1 expr 2 ... expr n )
(t nil))

A macro sera:
(defmacro quando (teste &rest exprs)
(list 'cond
(cons teste exprs)
'(t nil)))

Esta macro ja existe em Lisp e denomina-se when.

12.4 Caracteres de Macro


Geralmente, a parte mais difcil na escrita de uma macro e a determi-
nac~ao das express~oes Lisp que quando avaliadas produzem uma nova
express~ao Lisp que realiza o nosso objectivo. Para simpli car esta tare-
fa e usual utilizarem-se caracteres especiais que, a semelhanca da plica
(quote) e do cardinal-plica (function) s~ao transformados na leitura
para outras express~oes. Cada um dos caracteres especiais possui uma
func~ao Lisp associada que e avaliada quando a linguagem, durante a
leitura das express~oes, encontra um desses caracteres. Essa func~ao Lisp
pode ent~ao realizar mais leituras e retornar o que achar mais convenien-
te.
Estes caracteres especiais s~ao designados caracteres de macro pois
eles s~ao transformados (s~ao expandidos) na leitura em outras express~oes,
um pouco a imagem do que acontecia com as macros. A diferenc~ao
esta no instante em que a expans~ao ocorre. Uma macro e expandida
em tempo de compilac~ao (ou, em algumas implementac~oes de Lisp, em
tempo de execuc~ao). Um caracter de macro e expandido na leitura de
express~oes.
Qualquer caracter pode ser considerado especial, bastando, para is-
so, usar a func~ao set-macro-caracter que recebe um caracter e uma
func~ao a aplicar sempre que o caracter for lido. A func~ao a aplicar deve
possuir dois par^ametros que receber~ao, o primeiro, o local donde o Lisp
estava a ler (terminal, cheiro, etc) para que a func~ao possa continuar
a leitura do mesmo stio, e o segundo, o proprio caracter de macro.
Para se indicar um caracter em Lisp e necessario preced^e-lo dos
caracteres \#\". Por exemplo, o caracter $ e indicado por \#\$". Como
ja e sabido, se n~ao se inclussem os caracteres \#\", o Lisp consideraria
o objecto lido como um smbolo e n~ao como um caracter.
Para se compreender a utilizac~ao dos caracteres de macro, podemos
admitir que n~ao existia o caracter de plica e que pretendamos de ni-
lo. A express~ao 'ola representa, como sabemos, (quote ola), logo a

62
func~ao que seria invocada quando se encontrasse a plica necessitaria de
ler o objecto que estava apos a plica (usando a func~ao read) e construiria
uma lista com o smbolo quote a cabeca e o objecto lido no m, ou seja:
(defun plica (canal-leitura caracter)
(list (quote quote) (read canal-leitura)))

> (set-macro-character # (function plica))


T
> 'ola
OLA

A linguagem Lisp possui, previamente de nidos, varios caracteres


de macro. A plica e um deles, o ponto e vrgula (que representa um co-
mentario) e outro, etc. Alguns dos caracteres de macro s~ao precedidos
por um caracter especial, considerado o caracter de despacho, permitin-
do aumentar o numero de possveis caracteres de macro sem reduzir o
numero de caracteres normais utilizaveis. O caracter de despacho mais
usado e o cardinal, mas pode ser qualquer outro. Como exemplos destes
caracteres de macro com despacho temos o cardinal-plica para indicar
func~oes, o cardinal-barra para indicar caracteres, etc.
De todos os caracteres de macro, aqueles que s~ao particularmente
uteis para a escrita de macros s~ao o plica para tras (`|backquote), a
vrgula (,|comma) e o vrgula-arroba (,@|comma-at).
O backquote indica ao avaliador que n~ao deve avaliar uma express~ao
excepto quando for indicado em contrario. Quando usado isoladamente,
o backquote funciona exactamente como o quote. No entanto, a sua
combinac~ao com o comma e o comma-at permite simpli car imenso a
escrita de express~oes complexas. O comma so pode ser utilizado numa
express~ao (uma lista, tipicamente) precedida do backquote, e informa
o avaliador que a express~ao que se segue e para avaliar e o seu resultado
inserido na lista. O comma-at e id^entico mas o resultado da avaliac~ao
tem de ser uma lista cujos elementos s~ao inseridos.
A ttulo de exemplo, temos:
> `((+ 1 2) ,(+ 1 2) (list 1 2) ,(list 1 2) ,@(list 1 2))
((+ 1 2) 3 (LIST 1 2) (1 2) 1 2)

12.5 Macros U teis


Exerc
cio 12.5.1 Reescreva a macro quando usando o backquote, o
comma e o comma-at.
Solu
ca~o do Exerc
cio 12.5.1

(defmacro quando (teste &rest exprs)


`(cond (,teste ,@exprs)
(t nil)))

63
Exerc
cio 12.5.2 Escreva a macro a-menos-que, que recebe um teste e
um conjunto de express~oes. Esta forma especial avalia o teste e, quando
este e falso, avalia sequencialmente as express~oes, devolvendo o valor
da ultima. Se o teste e verdade, a forma retorna nil sem avaliar mais
nada.
Solu
ca~o do Exerc
cio 12.5.2

(defmacro a-menos-que (teste &rest exprs)


`(cond (,teste nil)
(t ,@exprs)))

Esta macro ja existe em Lisp e denomina-se unless.

Exerc
cio 12.5.3 Escreva uma implementac~ao da macro meu-cond u-
sando a forma especial if.
Solu
ca~o do Exerc
cio 12.5.3

(defmacro meu-cond (&rest clausulas)


(if (null clausulas)
nil
`(if ,(caar clausulas)
(progn ,@(cdar clausulas))
(meu-cond ,@(cdr clausulas)))))

Exerc
cio 12.5.4 A implementac~ao da macro meu-cond anterior im-
plica que a expans~ao e apenas parcial, i.e., enquanto houver clausulas, o
meu-cond expande para outro meu-cond. Implemente a macro meu-cond
de modo a realizar a expans~ao de uma so vez.
Solu ca
~o do Exerc cio 12.5.4 Em princ pio, bastaria forcar a avaliac~ao do meu-cond nal
mas, como a de nic~ao tem um par^ametro do tipo &rest, o que iria ser passado n~ao eram
as clausulas restantes mas sim uma lista com as clausulas restantes, o que seria incorrecto
(o par^ametro clausulas teria como valor uma lista com a lista das clausulas restantes). O
problema resume-se ent~ao a eliminar o par^ametro do tipo &rest, o que poderemos fazer
recorrendo a uma func~ao local.
(defmacro meu-cond (&rest clausulas)
(labels ((expande (clausulas)
(if (null clausulas)
nil
`(if ,(caar clausulas)
(progn ,@(cdar clausulas))
,(expande (cdr clausulas))))))
(expande clausulas)))

Exerc
cio 12.5.5 A macro seja implementa a mesma funcionalidade
que a forma especial let. Como se sabe, o let n~ao e mais do que uma
macro que expande para uma lambda. A ideia sera de nir a macro seja
exactamente da mesma forma, i.e., devera existir uma correspond^encia
entre a express~ao:
(seja ((x 10) (y 20))
(+ x y))

e a sua expans~ao:
((lambda (x y) (+ x y)) 10 20)

64
De na a macro seja de modo a implementar essa correspond^encia.
Solu
ca~o do Exerc
cio 12.5.5

(defmacro seja (vars-vals &rest exprs)


`((lambda ,(mapcar #'car vars-vals)
,@exprs)
,@(mapcar #'cadr vars-vals)))

> (seja ((x 10) (y 20)) (+ x y))


30

Como se disse, esta macro ja existe em Lisp e designa-se let.

Exerc
cio 12.5.6 Escreva a macro enquanto, que recebe um teste e
um conjunto de express~oes. A forma enquanto deve avaliar o teste
e, caso seja verdade, avaliar sequencialmente as express~oes e voltar ao
princpio. Caso o teste seja falso, deve terminar com o valor nil.
Solu
ca~o do Exerc
cio 12.5.6

(defmacro enquanto (teste &rest exprs)


`(loop
(a-menos-que ,teste (return nil))
,@exprs))

Exerc
cio 12.5.7 Escreva a macro caso, que recebe uma express~ao e
um conjunto de pares atomo-express~oes. A forma especial caso avalia
a primeira express~ao, e compara o resultado (usando a func~ao eql) com
cada um dos atomos em sequ^encia. Se um dos atomos emparelhar, s~ao
avaliadas as express~oes a ele associadas e retornado o valor da ultima.
Um exemplo de utilizac~ao seria:
(defun inverso-fact (x)
(caso x
(1 (print 'basico) 1)
(2 (print 'menos-basico) 2)
(6 (print 'ainda-menos-basico) 3)))

> (inverso-fact 1)
BASICO
1
Solu
ca~o do Exerc
cio 12.5.7

(defmacro caso (exp &rest clausulas)


`(let ((temp ,exp))
(cond ,@(mapcar #'(lambda (clausula)
`((eql temp ,(car clausula)) ,@(cdr clausula)))
clausulas))))

Esta forma especial ja existe em Lisp e denomina-se case.

65
12.6 Iteradores
Como se pode depreender dos exemplos apresentados, as macros desti-
nam-se essencialmente a criac~ao de acucar sintatico, i.e., de express~oes
que sejam mais simples de utilizar que outras ja existentes. Esta carac-
terstica torna as macros ferramentas extremamente uteis para criac~ao
de tipos abstractos de informac~ao capazes de dar ao utilizador iteradores
sobre os objectos desse tipo.
A ttulo de exemplo, vamos considerar a de nic~ao de um iterador
para os elementos de uma lista. Este iterador devera ser uma forma
especial que recebe um smbolo (uma variavel), uma lista e um conjunto
de express~oes, e itera aquelas express~oes com o smbolo ligado a cada
elemento da lista. Apresenta-se agora um exemplo da sintaxe da forma
especial:
(itera-lista (elem (list 1 2 3 4 5))
(print elem))

A sua de nic~ao e relativamente simples:


(defmacro itera-lista (var-e-lista &rest exprs)
`(let ((lista ,(cadr var-e-lista))
(,(car var-e-lista) nil))
(loop
(unless lista (return nil))
(setq ,(car var-e-lista) (car lista)
lista (cdr lista))
,@exprs)))

> (itera-lista (x '(1 2 3)) (print x))


1
2
3
NIL

Infelizmente, nem tudo esta bem. Reparemos no seguinte exemplo:


> (let ((lista '(1 2 3)))
(itera-lista (x '(4 5 6))
(print (cons x lista))))
(4 5 6)
(5 6)
(6)
NIL

O problema esta no facto de a macro estabelecer uma variavel deno-


minada lista, que interfere com a variavel exterior do segundo exem-
plo, pois tem o mesmo nome, cando assim obscurecida. A refer^encia
a lista feita no corpo da macro refere-se assim a variavel interna da

66
macro, e n~ao a que seria desejavel. Nesta situac~ao diz-se que a macro
capturou variaveis.
A soluc~ao para este problema esta na utilizac~ao de variaveis que
n~ao possam interferir de modo algum com outras ja existentes. Um
remedio possvel sera criar as variaveis necessarias as macros com nomes
estranhos, com pouca probabilidade de serem usados pelo utilizador
da macro, como por exemplo, %%%$$$lista$$$%%%. No entanto esta
soluc~ao n~ao e perfeita. O melhor a fazer e usar novos smbolos que
n~ao se possam confundir com os ja existentes. A func~ao gensym produz
um smbolo novo e unico de cada vez que e chamada, sendo ideal para
resolver estas di culdades.
Exerc cio 12.6.1 De na a macro itera-lista usando a referida fun-
c~ao gensym para proteger as variaveis do utilizador de uma captura
indevida.
Solu
ca~o do Exerc
cio 12.6.1

(defmacro itera-lista (var-e-lista &rest exprs)


(let ((var-lista (gensym)))
`(let ((,var-lista ,(cadr var-e-lista))
(,(car var-e-lista) nil))
(loop
(unless ,var-lista (return nil))
(setq ,(car var-e-lista) (car ,var-lista)
,var-lista (cdr ,var-lista))
,@exprs))))

> (let ((lista '(1 2 3)))


(itera-lista (x '(4 5 6))
(print (cons x lista))))
(4 1 2 3)
(5 1 2 3)
(6 1 2 3)
NIL

Esta forma especial ja existe em Lisp e denomina-se dolist.

Exerc
cio 12.6.2 De na de novo a forma especial itera-lista, mas
recorrendo desta vez a abordagem da programac~ao funcional, i.e., sem
utilizar formas especiais para ciclos nem atribuic~ao de valores a va-
riaveis.
Solu
ca~o do Exerc
cio 12.6.2

(defmacro itera-lista (var-e-lista &rest exprs)


(let ((itera (gensym)) (lista (gensym)))
`(labels ((,itera (,lista)
(if (null ,lista)
nil
(let ((,(car var-e-lista) (car ,lista)))
,@exprs
(,itera (cdr ,lista))))))
(,itera ,(cadr var-e-lista)))))

Note-se que a de nic~ao da macro implicou a utilizac~ao de smbolos unicos (via gensym)
quer para o nome de um par^ametro quer para o nome de uma func~ao local.

67
Exerc
cio 12.6.3 A forma especial caso de nida anteriormente sofria
do mesmo problema do itera-lista, pois a variavel usada para guar-
dar o valor temporario pode obscurecer variaveis id^enticas declaradas
exteriormente. Rede na a macro de forma a evitar esse perigo.
Solu
ca~o do Exerc
cio 12.6.3

(defmacro caso (exp &rest clausulas)


(let ((temp (gensym)))
`(let ((,temp ,exp))
(cond ,@(mapcar #'(lambda (clausula)
`((eql ,temp ,(car clausula)) ,@(cdr clausula)))
clausulas)))))

12.7 Fichas
Como vimos, uma cha (no sentido da linguagem Pascal) n~ao e mais
do que um tipo aglomerado. Uma cha possui um nome e e composta
por um conjunto de campos, cada um com um nome distinto. Cada ele-
mento de um dado tipo de cha corresponde a um conjunto de valores
apropriados para cada campo desse tipo. A utilizac~ao de chas e de tal
modo simples que todas as linguagens de programac~ao possuem capaci-
dades proprias para lidar com elas. Um dado tipo de cha n~ao necessita
mais do que um construtor para os elementos desse tipo, um selector
e um modi cador para cada um dos campos da cha e, possivelmente,
um reconhecedor de chas de um dado tipo.
Dadas as capacidades da linguagem Lisp em aglomerar facilmente
objectos, a implementac~ao de chas e uma tarefa de tal modo simples
que e bastante facil automatiza-la.
Vimos na descric~ao do tipo aglomerado automovel que ele era de -
nido por um constructor:
(defun novo-automovel (&key marca modelo portas) ...)

e pelos selectores:
(defun automovel-marca (automovel) ...)

(defun automovel-modelo (automovel) ...)

(defun automovel-portas (automovel) ...)

e ainda pelos modi cadores:


(defun muda-automovel-marca! (automovel nova-marca) ...)

(defun muda-automovel-modelo! (automovel novo-modelo) ...)

(defun muda-automovel-portas! (automovel novo-portas) ...)

Vamos tambem incluir um reconhecedor de automoveis muito util


para distinguirmos os diversos tipos de chas:
68
(defun automovel? (obj) ...)

Repare-se que o conjunto de func~oes que apresentamos constituem


um modelo para a de nic~ao de chas. A cha automovel possui um
construtor criado atraves da concatenac~ao da palavra \novo" ao nome
da cha. Cada selector e dado pela concatenac~ao do nome da cha ao
nome do campo a que ele diz respeito. Cada modi cador e dado pela
concatenac~ao da palavra \muda" ao nome do selector correspondente e
terminado com a letra \!'. O reconhecedor e dado pelo nome da cha
terminado com a letra \?'. Qualquer outra cha seria de nida de forma
id^entica, pelo que podemos fazer uma abstracc~ao da de nic~ao de chas,
criando uma macro que encapsule a de nic~ao de todas aquelas func~oes.
Um dos problemas que temos de resolver e a implementac~ao das
chas. Vimos no exemplo do automovel que o podiamos implementar
como uma lista com os valores dos varios campos. Infelizmente, aquela
implementac~ao n~ao nos permite distinguir o tipo de registo automovel de
outros tipos de registo, pelo que temos de modi car a implementac~ao
de modo a isso ser possvel. Para minimizar as alterac~oes, podemos
considerar que cada registo e implementado por uma lista cujo primeiro
elemento e o nome da cha e cujos restantes elementos s~ao os valores
dos campos da cha. Isto permite manter a mesma logica de acesso
e modi cac~ao dos campos desde que os ndices desses campos na lista
sejam incrementados de uma unidade.
Assim sendo, o construtor cara qualquer coisa do genero:
(defun novo-automovel (&key marca modelo portas)
(list 'automovel marca modelo portas))

Um dado selector sera :


(defun automovel-marca (automovel)
(nth 1 automovel))

Um modi cador sera:


(defun muda-automovel-marca! (automovel nova-marca)
(muda-n-esimo! 1 automovel nova-marca))

O reconhecedor de automoveis cara:


(defun automovel? (obj)
(and (listp obj) (eql (first obj) 'automovel)))

Podemos agora de nir uma macro designada ficha, cuja utilizac~ao


sera da seguinte forma:
(ficha automovel
marca modelo portas)

A expans~ao da macro devera ser qualquer coisa da forma:

69
(progn
(defun novo-automovel (&key marca modelo portas) ...)
(defun automovel-marca (automovel) ...)
(defun automovel-modelo (automovel) ...)
(defun automovel-portas (automovel) ...)
(defun muda-automovel-marca! (automovel nova-marca) ...)
(defun muda-automovel-modelo! (automovel novo-modelo) ...)
(defun muda-automovel-portas! (automovel novo-portas) ...)
(defun automovel? (obj) ...)
'carro)

A de nic~ao da macro e, assim, relativamente simples:


(defmacro ficha (nome &rest campos)
`(progn
,(construtor-ficha nome campos)
,@(selectores-ficha nome campos)
,@(modificadores-ficha nome campos)
,(reconhecedor-ficha nome)
',nome))

Para de nirmos as varias operac~oes vamos necessitar de concatenar


smbolos. Para isso, e necessario criar um smbolo, atraves da func~ao
intern, cujo nome seja a concatena c~ao, atraves da func~ao concatenate,
dos nomes dos smbolos, obtidos mapeando a func~ao string nesses
smbolos, i.e.:
(defun junta-nomes (&rest nomes)
(intern (apply #'concatenate
'string
(mapcar #'string nomes))))

Com a ajuda desta func~ao, ja podemos de nir as restantes func~oes


da macro.

70
(defun construtor-ficha (nome campos)
`(defun ,(junta-nomes 'novo- nome) (&key ,@campos)
(list ',nome ,@campos)))

(defun selectores-ficha (nome campos)


(mapcar
#'(lambda (campo)
`(defun ,(junta-nomes nome '- campo) (,nome)
(nth ,(1+ (position campo campos)) ,nome)))
campos))

(defun modificadores-ficha (nome campos)


(mapcar
#'(lambda (campo)
`(defun ,(junta-nomes 'muda- nome '- campo '!)
(,nome novo)
(muda-n-esimo! ,(1+ (position campo campos))
,nome novo)))
campos))

(defun reconhecedor-ficha (nome)


`(defun ,(junta-nomes nome '?) (obj)
(and (listp obj) (eql (first obj) ',nome))))

Podemos agora experimentar a macro e visualizar os resultados:


> (macroexpand-1 '(ficha automovel marca modelo portas)))
(PROGN
(DEFUN NOVO-AUTOMOVEL (&KEY MARCA MODELO PORTAS)
(LIST 'AUTOMOVEL MARCA MODELO PORTAS))
(DEFUN AUTOMOVEL-MARCA (AUTOMOVEL)
(NTH 1 AUTOMOVEL))
(DEFUN AUTOMOVEL-MODELO (AUTOMOVEL)
(NTH 2 AUTOMOVEL))
(DEFUN AUTOMOVEL-PORTAS (AUTOMOVEL)
(NTH 3 AUTOMOVEL))
(DEFUN MUDA-AUTOMOVEL-MARCA! (AUTOMOVEL NOVO)
(MUDA-N-ESIMO! 1 AUTOMOVEL NOVO))
(DEFUN MUDA-AUTOMOVEL-MODELO! (AUTOMOVEL NOVO)
(MUDA-N-ESIMO! 2 AUTOMOVEL NOVO))
(DEFUN MUDA-AUTOMOVEL-PORTAS! (AUTOMOVEL NOVO)
(MUDA-N-ESIMO! 3 AUTOMOVEL NOVO))
(DEFUN AUTOMOVEL? (OBJ)
(AND (LISTP OBJ) (EQL (first OBJ) 'AUTOMOVEL)))
'AUTOMOVEL)

Exerc
cio 12.7.1 Um dos melhoramentos que seria interessante incluir
na de nic~ao de chas seria a inclus~ao de valores por omiss~ao para qual-

71
quer campo. Poderiamos indicar que um automovel possui geralmente
quatro portas da seguinte forma:
(ficha automovel
marca
modelo
(portas 4))

Assim, se se criasse um automovel sem especi car o numero de por-


tas, ele seria de 4. Implemente essa alterac~ao.
Soluca~o do Exerc cio 12.7.1 Uma vez que os par^ ametros &key podem ser inicializados
e a sintaxe e igual a usada na cha, basta modi car o construtor e passar apenas os nomes
dos campos (esquecendo as inicializac~oes por omiss~ao) as restantes func~oes.
(defun nome-campo (campo)
(if (listp campo) (first campo) campo))

(defmacro ficha (nome &rest campos)


(let ((nomes-campos (mapcar #'nome-campo campos)))
`(progn
,(construtor-ficha nome nomes-campos campos)
,@(selectores-ficha nome nomes-campos)
,@(modificadores-ficha nome nomes-campos)
,(reconhecedor-ficha nome)
',nome))

(defun construtor-ficha (nome nomes-campos campos)


`(defun ,(junta-nomes 'novo- nome) (&key ,@campos)
(list ',nome ,@nomes-campos)))

> (macroexpand-1 '(ficha automovel marca modelo (portas 4))))


(PROGN
(DEFUN NOVO-AUTOMOVEL (&KEY MARCA MODELO (PORTAS 4))
(LIST 'AUTOMOVEL MARCA MODELO PORTAS))
(DEFUN AUTOMOVEL-MARCA (AUTOMOVEL)
(NTH 1 AUTOMOVEL))
...

Esta forma de criac~ao de chas ja existe em Common Lisp atraves da macro defstruct.

72
Indice Remissivo
(), 38 intern, 71
1+, 19 labels, 28
1-, 19 last, 44
<=, 13 length, 41
<, 13 let, 27, 48, 49, 53, 54, 66
=, 13, 32 list, 39, 44, 46
>=, 13 loop, 52
>, 13 mapcar, 42
&key, 60, 73 max, 26
&optional, 58 member, 43
&rest, 59 nconc, 50
and, 13, 17 nil, 13, 38
append, 42, 44 notany, 45
apply, 45 notevery, 45
atom, 31 not, 13
backquote, 64 nth, 41
butlast, 44 null, 38
car, 33, 38 or, 13, 17
case, 66 prog1, 49
cdr, 33, 38 progn, 48
comma-at, 64 quote, 32, 39, 63
comma, 64 read, 64
concatenate, 71 remove-duplicates, 44
cond, 15, 17, 49, 61 remove-if-not, 39
cons, 33, 38 remove, 43
defconstant, 56 rest, 39
defmacro, 61, 62 return, 52
defparameter, 56 reverse, 42
defstruct, 73 rplaca, 49
defun, 10, 17, 26, 49, 55 rplacd, 49
defvar, 56 set-macro-caracter, 63
dolist, 68 setq, 48, 53{55
do, 52 some, 45
endp, 39 string, 71
eql, 33, 66 subst, 43
eq, 32, 34 trace, 21
every, 45 t, 13
first, 39, 44 unless, 65
flet, 28 untrace, 21
funcall, 22, 45 when, 63
function, 22, 26, 40, 63 zerop, 13
gensym, 68
if, 14, 17, 61

73

Vous aimerez peut-être aussi