Académique Documents
Professionnel Documents
Culture Documents
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 tipicac~ao de dados, a possibilidade de tratar dados
e programas de um mesmo modo e a indistinc~ao entre func~oes denidas
pela linguagem e func~oes denidas pelo programador s~ao algumas das
raz~oes da sua
exibilidade.
A linguagem Lisp nasceu como uma ferramenta matematica, 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 inecientes. Esta ineci^encia resultava
de os programas Lisp serem interpretados, sendo por isso muito mais
lentos do que o que uma compilac~ao permite ao reescrever um progra-
ma na linguagem do processador. No entanto, com o aparecimento de
compiladores ecazes e de um suporte cada vez maior da parte dos pro-
cessadores, Lisp possui, actualmente, uma eci^encia comparavel a das
restantes linguagens.
Lisp da ao programador a capacidade de extender a linguagem como
entender, permitindo incorporar outros estilos de programac~ao, como
programac~ao orientada para objectos, programac~ao orientada para 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!
>
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 identicar
os numeros como dados e as operac~oes algebricas como procedimentos e
podemos combinar os numeros entre si usando aquelas operac~oes. Por
exemplo, 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 denida 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, denindo
novas operac~aoes que representem esses padr~oes de calculo.
3.1 Elementos primitivos
Elementos primitivos s~ao as entidades mais simples com que a linguagem
lida. Um numero, por exemplo, e um dado primitivo.
Como dissemos anteriormente, o Lisp executa um ciclo read-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
5
> (+ 1 2 3)
6
> (+ 1 2 3 4 5 6 7 8 9 10)
55
(- (+ 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 prexa
do Lisp para a notac~ao inxa da aritmetica:
(* (/ 1 2) 3)
(* 1 (- 2 3))
(/ (+ 1 2) 3)
(/ (/ 1 2) 3)
(/ 1 (/ 2 3))
(- (- 1 2) 3)
(- 1 2 3)
7
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
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
8
> (* 5 5)
25
> (* 6 6)
36
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 denic~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
Solu
ca~o do Exerc
cio 4.2.1
T
T
T
3
1
nil
3
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
A func~ao fact e um exemplo de uma func~ao recursiva, i.e., que se refere a ela propria.
14
(defun teste (x y z w)
(if (> x y)
z
(if (< (+ x w) (* y z))
x
(if (= w z)
(+ x y)
777))))
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 indenidamente 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 especica ao valor dos restantes elementos. Nesta optica,
antes de se escolher a opc~ao a seguir, o avaliador deveria avaliar todas
elas, i.e., 100 cujo valor e 100 e a aplicac~ao da func~ao inexistente que
devia produzir um erro pois a func~ao ainda n~ao foi denida. Porque
sera que quando testamos isto n~ao e produzido nenhum erro?
E evidente que, de algum modo, o if n~ao seguiu as regras do modelo
de avaliac~ao de combinac~oes, caso contrario, teria mesmo produzido um
erro. Isto sugere que if n~ao e uma func~ao normal mas sim algo que
possui a sua propria regra de avaliac~ao.
Uma forma especial e uma express~ao da linguagem que possui a sua
propria sintaxe e a sua propria regra de avaliac~ao. E de salientar que
uma forma especial n~ao e uma func~ao. Ela faz parte da estrutura do
avaliador e n~ao do seu ambiente.
O defun, o if e o cond s~ao algumas das formas especiais. Mas o and
e o or tambem s~ao, pois so avaliam os operandos que forem necessarios
para determinar o resultado. O and para de avaliar quando um deles
produzir falso, o or quando um deles produzir verdade.
Como uma forma especial possui a sua propria regra de avaliac~ao,
nem tudo o que se faz com uma func~ao se pode fazer com uma forma
especial, e nem tudo o que se faz com uma forma especial se pode fazer
com uma func~ao.
16
Ao contrario das outras linguagens que possuem muitas formas es-
peciais, Lisp tem muito poucas. Desta forma, a linguagem possui uma
regra de avaliac~ao muito simples e muito bem denida, e em que as
pequenas excepc~oes s~ao implementadas pelas formas especiais.
No entanto, e ao contrario do que acontece na maioria das outras
linguagens, Lisp permite ao utilizador denir novas formas cujo 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 denir 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 indenidamente 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 innita e o equivalente
das func~oes recursivas aos ciclos innitos dos metodos iterativos do tipo
while-do e repeat-until.
Repare-se que uma func~ao recursiva que funciona perfeitamente 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, dena 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 Dena 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, dena 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
Exerc
cio 5.1.5 Dena 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))
Exerc
cio 5.1.6 Dena a func~ao simetrico de um numero qualquer
na linguagem Lisp.
Solu
ca~o do Exerc
cio 5.1.6
Exerc
cio 5.1.7 E possvel denir 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. Dena 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))))
(defun soma-geral (a b)
(soma-iter a a b b))
19
Exerc
cio 5.1.9 Do mesmo modo que a soma pode ser denida exclu-
sivamente em termos de sucessor 1+ e predecessor 1-, a multiplicac~ao
pode ser denida exclusivamente em termos da soma. Dena a func~ao
mult que recebe dois 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)))
20
(defun soma-quadrados (a b)
(if (> a b)
0
(+ (quadrado a) (soma-quadrados (1+ a) b))))
> (soma-quadrados 1 4)
30
> (soma-raizes 1 4)
6.146264369941973
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))))
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 denir a func~ao identidade.
(defun identidade (x) x)
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. Dena
esta func~ao. Dena o somatorio e o produtorio em termos de acumu-
latorio.
22
Solu
ca~o do Exerc
cio 5.3.3
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 denir o produto em termos do acumulatorio
xando a operac~ao de combinac~ao no * e o valor inicial no 1.
(defun produto (func a suc b)
(acumulatorio (function *) func 1 a suc b))
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.3 Sabe-se que a soma 113 + 517 + 9111 + converge
(muito lentamente) para 8 . Dena a func~ao que calcula a aproximac~ao
a ate ao n-esimo termo da soma. Determine ate ao termo 2000.
Solu
ca~o do Exerc
cio 5.4.3
(defun pi (n)
(* 8 (soma (function pi/8-func)
1
(function pi/8-seguinte)
n)))
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 deni-las no ambiente do avaliador. O que pretendiamos era t~ao
somente que fosse possvel especicar as express~oes que aquelas func~oes
calculam, independentemente do nome que se lhes pudesse atribuir.
Para isso, a linguagem Lisp fornece as lambdas. Uma lambda e
uma func~ao com todas as caractersticas das restantes mas que n~ao esta
associada a nenhum smbolo. Pode ser vista como uma func~ao sem
nome. A sintaxe da lambda e igual a da defun, mas em que se omite o
nome.
> ((lambda (z) (+ z 3)) 2)
5
Como se v^e, uma lambda pode ser usada onde se usaria uma func~ao.
Isto permite simplicar o exerccio da aproximac~ao a sem ter de denir
quaisquer outras func~oes:
(defun pi (n)
(* 8 (soma (function (lambda (x) (/ 1.0 (* x (+ x 2)))))
1
(function (lambda (x) (+ x 4)))
n)))
24
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 f* (x y temp)
(+ (* (quadrado temp) x)
(* temp y)))
Mas como ja vimos, n~ao ha necessidade de se denir uma func~ao f*
no ambiente pois podemos usar as lambdas directamente.
(defun f (x y)
((lambda (temp)
(+ (* (quadrado temp) x)
(* temp y)))
(+ 1 (* (quadrado x) y))))
(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
> (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 denimos s~ao
recursivas, independentemente de serem locais ou n~ao, interessa possuir
um meio de o podermos fazer. A forma especial labels providencia esta
possibilidade. A sua sintaxe e igual a do flet, mas a sua sem^antica e
ligeiramente diferente. Para o flet, o ^ambito do nome das func~oes
denidas e apenas o corpo do flet. Para o labels, esse ^ambito e
extendido a propria forma especial. Isto permite que se possam denir
func~oes locais recursivas ou mutuamente recursivas.
6 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 denem 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
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 indenido) 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.
29
variaveis livres tomam como valor o valor da primeira variavel igual no
contexto em que a lambda e denida. 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 denida.
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 denic~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
denic~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
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
31
> (eq 1 1)
t
> (eq 111111111111111111111111111111111111
111111111111111111111111111111111111)
nil
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 Dena 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
33
(defun numerador (racional)
(car 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))))
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
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))))
(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 denic~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 classicar em categorias: construtores,
selectores, reconhecedores e testes. Estas func~oes s~ao denidas em ter-
mos dos objectos mais primitivos que implementam o tipo de dados que
se quer denir.
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 denir. 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 denir. A func~ao =racional e um exemplo de uma func~ao
desta categoria. Como se pode vericar 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 denir 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 Dena 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))))
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)
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
(defun enumera (a b)
(if (> a b)
nil
(cons a (enumera (1+ a) b))))
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 vericam um determinado criterio.
Utilize-a para encontrar os numeros pares entre 1 e 20.
Solu
ca~o do Exerc
cio 8.1.2
38
Uma vez que as formas especiais quote e function s~ao bastante
utilizadas, Lisp fornece um meio de se simplicar 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
39
Solu
ca~o do Exerc
cio 8.2.1
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
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
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
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
40
Solu
ca~o do Exerc
cio 8.2.6
Exerc
cio 8.2.7 Dena 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
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
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
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
Exerc
cio 8.2.11 Escreva uma func~ao membro? que recebe um objecto
e uma lista e verica se aquele objecto existe na lista.
Solu
ca~o do Exerc
cio 8.2.11
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
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
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
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
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)))))
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 denidos 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 deni-los com a forma
especial record, enquanto que a linguagem C usa, para o mesmo efeito,
o struct.
Para exemplicarmos a utilizac~ao de tipos aglomerados podemos
considerar a denic~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 simplicar, 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))
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 modicadas. 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 modicar seja o que for e
46
a maior. A partir do momento em que introduzimos a modicac~ao
de objectos, estamos a introduzir o conceito de destruic~ao. A forma
anterior do objecto que foi modicado deixou de existir, passando a
existir apenas a nova forma. A modicac~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 modicar
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 signicado 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)))
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 denic~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))))
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)
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 indenidamente.
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
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 modicar. Na denic~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
50
de modo que x e y acabam por representar um mesmo automovel (mo-
dicado). 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 exemplica 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))))
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)))
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
simplicac~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
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 denida. E em relac~ao a este novo
ambiente que se avalia o corpo da func~ao.
Note-se que a forma especial defun dene 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 denida 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 denic~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, dena 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 deni-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 denic~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 denida 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
55
> (defconstant acelaracao-gravidade 9.8)
ACELARACAO-GRAVIDADE
> (defvar *y*)
*Y*
> (defparameter *z* 10)
*Z*
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 deni 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 suciente 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 denic~ao de func~oes, permitindo que ela possa assumir
certos par^ametros por omiss~ao.
Para denirmos func~oes que t^em par^ametros opcionais temos de usar
um qualicador especial designado &optional na lista de par^ametros
formais. Esse qualicador 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 denir 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))
>(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 qualicador &key informa o avaliador que os par^ametros qualicados
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)
59
Para alem da possibilidade de alterac~ao da ordem dos argumentos, o
qualicador &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 denir 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 diculdades 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 dene 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 denir 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 denir numa express~ao
que use outras formas especiais ja denidas.
A ttulo de exemplo vamos denir a forma especial meu-if cujo
objectivo e simplicar 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 )
61
Solu
ca~o do Exerc
cio 12.3.1 A sintaxe da macro e:
(quando teste
expr 1 expr 2 ... expr n )
A macro sera:
(defmacro quando (teste &rest exprs)
(list 'cond
(cons teste exprs)
'(t nil)))
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)))
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
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
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 denic~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 denir 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
Dena a macro seja de modo a implementar essa correspond^encia.
Solu
ca~o do Exerc
cio 12.5.5
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
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
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 denic~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))
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 diculdades.
Exerc cio 12.6.1 Dena 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
Exerc
cio 12.6.2 Dena 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
Note-se que a denic~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 denida 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. Redena a macro de forma a evitar esse perigo.
Solu
ca~o do Exerc
cio 12.6.3
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 modicador 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) ...)
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)
70
(defun construtor-ficha (nome campos)
`(defun ,(junta-nomes 'novo- nome) (&key ,@campos)
(list ',nome ,@campos)))
Exerc
cio 12.7.1 Um dos melhoramentos que seria interessante incluir
na denic~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))
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