Escolar Documentos
Profissional Documentos
Cultura Documentos
Jo~ao Cachopo
Outubro 1995
Indice
1 Linguagem de Programac~ao
Uma linguagem de programac~ao n~ao e apenas um meio de indicar a
um computador uma serie de operac~oes a executar. Uma linguagem de
programac~ao e, sobretudo, um meio de exprimirmos ideias acerca de
metodologias.
Uma linguagem de programac~ao deve ser feita para seres humanos
dialogarem acerca de programas e, so incidentalmente, para computadores os executarem. Como tal, deve possuir ideias simples, deve ser
capaz de combinar ideias simples para formar ideias mais complexas e
deve ser capaz de realizar abstracc~oes de ideias complexas para as tornar
simples.
Existe uma enorme diversidade de linguagens de programac~ao, umas
mais adaptadas a um determinado tipo de processos, outras mais adaptadas a outros. A escolha de uma linguagem de programac~ao deve
estar condicionada, naturalmente, pelo tipo de problemas que queremos resolver, mas n~ao deve ser um comprometimento total. Para um
programador e muito mais importante compreender os fundamentos e
tecnicas da programac~ao do que dominar esta ou aquela linguagem.
Lisp e uma linguagem din^amica, cujos programas s~ao constitudos
por pequenos modulos, de funcionalidade generica e que cumprem um
objectivo muito simples. E a sua combinac~ao que produz um programa
completo. Os modulos desenvolvidos em Lisp possuem, geralmente, uma
funcionalidade que ultrapassa largamente os objectivos para que foram
concebidos. A n~ao tipicac~ao de dados, a possibilidade de tratar dados
e programas de um mesmo modo e a indistinc~ao entre func~oes denidas
pela linguagem e func~oes denidas pelo programador s~ao algumas das
raz~oes da sua
exibilidade.
A linguagem Lisp nasceu como uma ferramenta matematica, independente de qualquer computador e so posteriormente se procedeu a sua
adaptac~ao a uma maquina. Uma vez que em Lisp n~ao existia qualquer
depend^encia, a priori, do processador que iria executar a linguagem,
a linguagem tambem n~ao podia tirar partido das suas potencialidades,
sendo as primeiras vers~oes muito inecientes. Esta ineci^encia resultava
de os programas Lisp serem interpretados, sendo por isso muito mais
lentos do que o que uma compilac~ao permite ao reescrever um programa na linguagem do processador. No entanto, com o aparecimento de
compiladores ecazes e de um suporte cada vez maior da parte dos processadores, Lisp possui, actualmente, uma eci^encia comparavel a das
restantes linguagens.
Lisp da ao programador a capacidade de extender a linguagem como
entender, permitindo incorporar outros estilos de programac~ao, como
programac~ao orientada para objectos, programac~ao orientada para regras, etc. Quando surgem novos paradigmas de programac~ao, o Lisp
incorpora-os facilmente enquanto as antigas linguagens morrem. Isto
permite adaptar a linguagem ao problema que estamos a resolver em
vez de termos de adaptar o problema a linguagem que estamos a usar.
2
2 O Avaliador
Em Lisp, qualquer express~ao tem um valor. Este conceito e de tal modo
importante que todas as implementac~oes da linguagem Lisp apresentam
um avaliador, i.e., um pequeno programa destinado a interagir com o
utilizador de modo a avaliar express~oes por este fornecidas. Assim,
quando o utilizador comeca a trabalhar com o Lisp, e-lhe apresentado
um sinal e o Lisp ca a espera que o utilizador lhe forneca uma express~ao.
Welcome to Common Lisp!
>
O caracter \>" e a \prompt" do Lisp, a frente da qual v~ao aparecer as express~oes que o utilizador escrever. O Lisp interacciona com
o utilizador executando um ciclo em que l^e uma express~ao, determina o seu valor e escreve o resultado. Este ciclo de acc~oes designa-se,
tradicionalmente, de ciclo read-eval-print.
Quando o Lisp l^e uma express~ao, constroi internamente um objecto
que a representa. Esse e o papel da fase de leitura. Na fase de avaliac~ao,
3
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 entanto, a menos que pretendamos car eternamente a resolver problemas
de aritmetica elementar, somos obrigados a considerar operac~oes mais
elaboradas que representam padr~oes de calculos. Neste ultimo exemplo,
e evidente que o padr~ao que esta a emergir e o da operac~ao de potenciac~ao, i.e, multiplicac~ao sucessiva, tendo esta operac~ao sido denida na
matematica ha ja muito tempo.
Tal como a linguagem da matematica, uma linguagem de programac~ao deve possuir dados e procedimentos primitivos, deve ser capaz
de combinar quer os dados quer os procedimentos para produzir dados
e procedimentos mais complexos e deve ser capaz de abstrair padr~oes de
calculo de modo a permitir trata-los como operac~oes simples, denindo
novas operac~aoes que representem esses padr~oes de calculo.
3.2 Combinaco~es
> (+ 1 2 3)
6
> (+ 1 2 3 4 5 6 7 8 9 10)
55
Numa linguagem do tipo Pascal apenas existem operadores unarios ou binarios, e e necessario explicitar os operador binarios entre
cada dois operandos: 1+2+3 ou 1+2+3+4+5+6+7+8+9+10. Um operador deste tipo diz-se inxo (in signica entre). Se se pretender
um operador ternario (ou outro) ja n~ao se consegue escrever do
mesmo modo, sendo necessario implementa-lo como uma func~ao.
N~ao existe preced^encia entre os operadores. Em Pascal, a express~ao 1+2*3 tem de ser calculada como se se tivesse escrito
1+(2*3), e n~
ao (1+2)*3, i.e., foi criada uma preced^encia que elimina as ambiguidades. Essa preced^encia pode ser alterada atraves
do emprego de par^enteses. Em Lisp, as express~oes seriam necessariamente distintas, (+ 1 (* 2 3)) ou (* (+ 1 2) 3), n~ao
podendo haver qualquer ambiguidade.
Os operadores que nos denimos usam-se exactamente da mesma
maneira que os operadores da linguagem: operador primeiro e
operandos a seguir. Em Pascal, os operadores s~ao inxos, i.e.,
est~ao entre os operandos, e as func~oes e procedimentos por nos
denidos s~ao prexos, i.e., primeiro a func~ao ou procedimento e
depois os operandos. Isto impede a extens~ao da linguagem de uma
forma coerente.
Para exemplicar este ultimo aspecto, consideremos a operac~ao
de exponenciac~ao em Pascal. Para ser coerente com o resto da
linguagem, deveria existir um operador, por exemplo ** que permitisse escrever 3**4 para indicar a quarta pot^encia de 3. Como
esse operador n~ao existe na linguagem (standard), somos obrigados a criar uma func~ao que o substitua mas, neste caso, a sintaxe
muda radicalmente. Esta func~ao, como se usa de forma prexa
n~ao se pode tornar coerente com as restantes operac~oes do Pascal,
como a soma e a multiplicac~ao. Em Lisp, pelo contrario, tanto podemos escrever (* 3 3 3 3) como denir uma func~ao que
permita escrever (** 3 4).
A desvantagem da notac~ao prexa esta na escrita de combinac~oes
complexas. A express~ao Pascal 1+2*3-4/5*6, equivale a express~ao Lisp
(- (+ 1 (* 2 3)) (* (/ 4 5) 6)) que embora seja mais simples de
ler para uma maquina, pode ser mais difcil de ler para um ser humano
devido a acumulac~ao de par^enteses. No entanto, esta express~ao pode
ser escrita de forma mais clara usando indentac~ao.
A regra para indentac~ao de combinac~oes Lisp e extremamente simples. Numa linha coloca-se o operador e o primeiro operando. Os restantes operandos v^em alinhados por debaixo do primeiro.
6
(- (+ 1
(* 2 3))
(* (/ 4 5)
6))
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
1 (*
(* 1
(* 1
(- 1
(- 1
1 (+
(* 2
2) 3) ou (+ 1 (- 2 3)) ou (+ 1 2 (- 3))
2 3))
2) 3)
2) 3) ou (* 1 (* 2 3)) ou (* 1 2 3)
2) 3)
2) 3)
2 3))
2) (* 3 3 3)
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)
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
Como ja vimos, o Lisp considera que o primeiro elemento de uma combinac~ao e um operador e os restantes s~ao os operandos.
O avaliador determina o valor de uma combinac~ao como o resultado de aplicar o procedimento especicado pelo operador ao valor dos
operandos. O valor de cada operando e designado de argumento do procedimento. Assim, o valor da combinac~ao (+ 1 (* 2 3)) e o resultado
de somar o valor de 1 com o valor de (* 2 3). Como ja se viu, 1 vale
1 e (* 2 3) e uma combinac~ao cujo valor e o resultado de multiplicar
o valor de 2 pelo valor de 3, o que da 6, e que somado a 1 da 7.
> (+ 1 2)
3
> (+ 1 (* 2 3))
7
Exerc
cio 3.3.1
(*
(*
(/
(/
(/
(((-
(/ 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
Tal como em matematica, pode-se denir numa linguagem de programac~ao a operac~ao de elevar um numero ao quadrado. Assim, se pretendermos determinar o produto de um numero por ele proprio, escrevemos
a combinac~ao (* x x) sendo x o numero, i.e.
8
> (* 5 5)
25
> (* 6 6)
36
Para se denirem novas func~oes em Lisp, e necessario criar uma combinac~ao de quatro elementos. O primeiro elemento desta combinac~ao
e a palavra defun, que informa o avaliador que estamos a denir uma
func~ao. O nome defun e uma abreviatura de \dene function". O segundo elemento e o nome da func~ao que queremos denir, o terceiro
elemento e uma combinac~ao com os par^ametros da func~ao e o quarto
elemento e a combinac~ao que determina o valor da func~ao para aqueles
par^ametros.
Quando se da uma express~ao desta forma ao avaliador, ele acrescenta
a func~ao ao conjunto de func~oes da linguagem, associando-a ao nome
que lhe demos e devolve como valor da denic~ao o nome da func~ao
denida.
A denic~ao da func~ao quadrado diz que para se determinar o quadrado de um numero x, devemos multiplicar esse numero por ele proprio
(* x x). Esta deni
c~ao associa a palavra quadrado a um procedimento. Este procedimento possui par^ametros e um corpo de express~oes. De
forma generica temos:
(defun nome (par^
ametro 1 ... par^
ametro n )
corpo )
Os par^ametros de um procedimento s~ao designados par^ametros formais e s~ao os nomes usados no corpo de express~oes para nos referirmos
aos argumentos correspondentes. Quando escrevemos no avaliador de
Lisp (quadrado 5), 5 e o argumento. Durante o calculo da func~ao este
argumento esta associado ao par^ametro formal x. Os argumentos de
uma func~ao s~ao tambem designados par^ametros actuais.
> (quadrado 5)
25
> (quadrado 6)
36
(quadrado
(quadrado
(quadrado
(quadrado
(* 9 9)
81
(quadrado (+ 1 2)))
(quadrado 3))
(* 3 3))
9)
3.5 Smbolos
s~ao smbolos. O nome da func~ao tambem e um smbolo. Quando escrevemos uma combinac~ao, o avaliador de Lisp usa a denic~ao de func~ao
que foi associada ao smbolo que constitui o primeiro elemento da combinac~ao. Quando, no corpo de uma func~ao, usamos um smbolo para
nos referimos a um par^ametro formal, esse smbolo tem como valor o
respectivo par^ametro actual que lhe foi associado naquela combinac~ao.
Por esta descric~ao se v^e que os smbolos constituem um dos elementos
fundamentais da linguagem Lisp.
4 Express~oes Condicionais
Existem muitas operac~oes cujo resultado depende da realizac~ao de um
determinado teste. Por exemplo, a func~ao matematica abs|que calcula o valor absoluto de um numero|equivale ao proprio numero, se
este e positivo, ou equivale ao seu simetrico se for negativo. Estas
express~oes, cujo valor depende de um ou mais testes a realizar previamente, permitindo escolher vias diferentes para a obtenc~ao do resultado,
s~ao designadas express~oes condicionais.
No caso mais simples de uma express~ao condicional, existem apenas
duas alternativas a seguir. Isto implica que o teste que e necessario
realizar para determinar a via de calculo a seguir deve produzir um de
dois valores, em que cada valor designa uma das vias.
Seguindo o mesmo racioccio, uma express~ao condicional com mais
de duas alternativas devera implicar um teste com igual numero de
possveis resultados. Uma express~ao da forma \caso o valor do teste
seja 1, 2 ou 3, o valor da express~ao e 10, 20 ou 30, respectivamente"
e um exemplo desta categoria de express~oes que, embora seja considerada pouco intuitiva, existe nalgumas linguagens (Basic, por exemplo).
Contudo, estas express~oes podem ser facilmente reduzidas a primeira.
Basta decompor a express~ao condicional multipla numa composic~ao de
express~oes condicionais simples, em que o teste original e tambem decomposto numa serie de testes simples. Assim, poderiamos transformar
o exemplo anterior em: \se o valor do teste e 1, o resultado e 10, caso
contrario, se o valor e 2, o resultado e 20, caso contrario e 30".
4.1 Predicados
Desta forma, reduzimos todos os testes a express~oes cujo valor pode ser
apenas um de dois, e a express~ao condicional assume a forma de \se
. . . ent~ao . . . caso contrario . . . ". Nesta situac~ao, a func~ao usada como
teste e denominada predicado e o valor do teste e interpretado como
sendo verdadeiro ou falso. Nesta optica, o facto de se considerar o valor
como verdadeiro ou falso n~ao implica que o valor seja de um tipo de
dados especial, como o booleano ou logico de algumas linguagens (como
o Pascal), mas apenas que a express~ao condicional considera alguns dos
valores como representando o verdadeiro e os restantes como o falso.
11
que um deles seja falso, devolvendo este valor. Se nenhum for falso
o and devolve o valor do ultimo argumento.
O or avalia os seus argumentos da esquerda para a direita ate
que um deles seja verdade, devolvendo este valor. Se nenhum for
verdade o or devolve o valor do ultimo argumento.
12
O not avalia para verdade se o seu argumento for falso e para falso
em caso contrario.
Note-se que embora o signicado de falso seja claro pois corresponde
necessariamente ao valor nil, o signicado de verdade ja n~ao e t~ao claro
pois, desde que seja diferente de nil, e considerado verdade.
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
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))))
Quando testamos o meu-if, tudo parece bem. No entanto, se escrevermos um exemplo ligeiramente diferente, algo corre mal.
> (defun divide (x y)
(meu-if (zerop y)
0
(/ x y)))
divide
> (divide 2 0)
Error:.....
Segundo o modelo de avaliac~ao que tnhamos apresentado, uma combinac~ao e avaliada aplicando o procedimento que o primeiro elemento da
combinac~ao especica ao valor dos restantes elementos. Nesta optica,
antes de se escolher a opc~ao a seguir, o avaliador deveria avaliar todas
elas, i.e., 100 cujo valor e 100 e a aplicac~ao da func~ao inexistente que
devia produzir um erro pois a func~ao ainda n~ao foi denida. Porque
sera que quando testamos isto n~ao e produzido nenhum erro?
E evidente que, de algum modo, o if n~ao seguiu as regras do modelo
de avaliac~ao de combinac~oes, caso contrario, teria mesmo produzido um
erro. Isto sugere que if n~ao e uma func~ao normal mas sim algo que
possui a sua propria regra de avaliac~ao.
Uma forma especial e uma express~ao da linguagem que possui a sua
propria sintaxe e a sua propria regra de avaliac~ao. E de salientar que
uma forma especial n~ao e uma func~ao. Ela faz parte da estrutura do
avaliador e n~ao do seu ambiente.
O defun, o if e o cond s~ao algumas das formas especiais. Mas o and
e o or tambem s~ao, pois so avaliam os operandos que forem necessarios
para determinar o resultado. O and para de avaliar quando um deles
produzir falso, o or quando um deles produzir verdade.
Como uma forma especial possui a sua propria regra de avaliac~ao,
nem tudo o que se faz com uma func~ao se pode fazer com uma forma
especial, e nem tudo o que se faz com uma forma especial se pode fazer
com uma func~ao.
16
Ao contrario das outras linguagens que possuem muitas formas especiais, Lisp tem muito poucas. Desta forma, a linguagem possui uma
regra de avaliac~ao muito simples e muito bem denida, e em que as
pequenas excepc~oes s~ao implementadas pelas formas especiais.
No entanto, e ao contrario do que acontece na maioria das outras
linguagens, Lisp permite ao utilizador denir novas formas cujo comportamento e semelhante ao das formas especiais. Isso e feito atraves de
macros, que s~ao formas que s~ao transformadas noutras formas (especiais
ou n~ao) durante a interpretac~ao ou a compilac~ao.
5 Func~oes
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:
Dado este padr~ao, os erros mais comuns associados as func~oes recursivas
s~ao, naturalmente:
17
No caso de erro em func~ao recursivas, o mais usual e a recurs~ao nunca parar. O numero de chamadas recursivas cresce indenidamente ate
esgotar a memoria (stack), e o programa gera um erro. Em certas linguagens (Scheme) e implementac~oes do Common Lisp, isto n~ao e assim,
e pode nunca ser gerado um erro. A recurs~ao innita e o equivalente
das func~oes recursivas aos ciclos innitos dos metodos iterativos do tipo
while-do e repeat-until.
Repare-se que uma func~ao recursiva que funciona perfeitamente para os casos para que foi prevista pode estar completamente errada para
outros casos. A func~ao fact e um exemplo. Quando o argumento e negativo, o problema torna-se cada vez mais complexo, cada vez mais longe
do caso simples. (fact -1) ! (fact -2) ! (fact -3) !
Considere uma vers~ao extremamente primitiva da linguagem Lisp, em que as unicas func~oes numericas existentes s~ao zerop
e duas func~oes que incrementam e decrementam o seu argumento em
uma unidade, respectivamente, 1+ e 1-. Isto implica que as operac~oes
>, <, = e similares n~
ao podem ser utilizadas. Nesta linguagem, que
passaremos a designar por 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.
Exerc
cio 5.1.1
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)))))
Dena a operac~ao igual? que testa igualdade numerica de inteiros positivos na linguagem Lisp.
Exerc
cio 5.1.2
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)))))
Solu
ca
~o do Exerc
cio 5.1.3
18
Solu
ca
~o do Exerc
cio 5.1.4
Dena o teste de igualdade de dois numeros na linguagem Lisp contemplando a possibilidade de trabalhar tambem com
numeros inteiros negativos.
Exerc
cio 5.1.5
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
Solu
ca
~o do Exerc
cio 5.1.6
E possvel denir a soma de dois numeros inteiros positivos em Lisp, i.e., apenas recorrendo as func~oes 1+ e 1- que somam
e subtraem uma unidade, respectivamente. Dena a operac~ao soma.
Exerc
cio 5.1.7
Solu
ca
~o do Exerc
cio 5.1.7
(defun soma1 (a b)
(if (zerop a)
b
(1+ (soma1 (1- a) b))))
Exerc
cio 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
Do mesmo modo que a soma pode ser denida exclusivamente em termos de sucessor 1+ e predecessor 1-, a multiplicac~ao
pode ser denida exclusivamente em termos da soma. Dena a func~ao
mult que recebe dois n
umero e os multiplica usando a func~ao soma.
Exerc
cio 5.1.9
Solu
ca
~o do Exerc
cio 5.1.9
(defun mult (a b)
(if (zerop a 0)
0
(soma (mult (1- a) b) b)))
Solu
ca
~o do Exerc
cio 5.2.1
(defun soma-quadrados (a b)
(if (> a b)
0
(+ (quadrado a) (soma-quadrados (1+ a) b))))
> (soma-quadrados 1 4)
30
21
Como se v^e, a func~ao somatorio representa a abstracc~ao associada ao somatorio matematico. Para isso, ela recebe uma func~ao como
argumento e aplica-a aos sucessivos inteiros includos no somatorio.
As func~oes que recebem e manipulam outras func~oes s~ao designadas
func~oes de ordem superior.
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
Solu
ca
~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.
22
Solu
ca
~o do Exerc
cio 5.3.3
5.4 Especializac~ao
O produto e uma abstrac~ao de ordem superior ou inferior a do produtorio? Qual delas e que e um caso particular da outra?
Exerc
cio 5.4.1
23
Solu
ca
~o do Exerc
cio 5.4.2
Solu
ca
~o do Exerc
cio 5.4.3
5.5 Lambdas
Como se viu pelo exemplo anterior, para que pudessemos implementar =8 em func~ao de acumulatorio tivemos de escrever as func~oes
pi/8-seguinte e pi/8-func, cuja utilidade
e muito limitada. Elas apenas servem para este exemplo. Por esse motivo, n~ao tem muito sentido
estar a deni-las no ambiente do avaliador. O que pretendiamos era t~ao
somente que fosse possvel especicar as express~oes que aquelas func~oes
calculam, independentemente do nome que se lhes pudesse atribuir.
Para isso, a linguagem Lisp fornece as lambdas. Uma lambda e
uma func~ao com todas as caractersticas das restantes mas que n~ao esta
associada a nenhum smbolo. Pode ser vista como uma func~ao sem
nome. A sintaxe da lambda e igual a da defun, mas em que se omite o
nome.
> ((lambda (z) (+ z 3)) 2)
5
Como se v^e, uma lambda pode ser usada onde se usaria uma func~ao.
Isto permite simplicar o exerccio da aproximac~ao a sem ter de denir
quaisquer outras func~oes:
(defun pi (n)
(* 8 (soma (function (lambda (x) (/ 1.0 (* x (+ x 2)))))
1
(function (lambda (x) (+ x 4)))
n)))
24
Solu
ca
~o do Exerc
cio 5.5.1
Como se v^e, a express~ao (+ 1 (* (quadrado x) y)) aparece repetida duas vezes. Isto, para alem de dicultar a leitura da func~ao torna-a
menos eciente pois aquela express~ao vai ter de ser calculada duas vezes.
Quase todas as linguagens de programac~ao fornecem os meios para se criarem variaveis locais, temporarias, para guardarem resultados
parciais que v~ao ser utilizados noutros stios. Em Lisp, isso pode ser
obtido denindo func~oes intermedias:
25
(defun f (x y)
(f* x y (+ 1 (* (quadrado x) y))))
(defun f* (x y temp)
(+ (* (quadrado temp) x)
(* temp y)))
Mas como ja vimos, n~ao ha necessidade de se denir uma func~ao f*
no ambiente pois podemos usar as lambdas directamente.
(defun f (x y)
((lambda (temp)
(+ (* (quadrado temp) x)
(* temp y)))
(+ 1 (* (quadrado x) y))))
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
Solu
ca
~o do Exerc
cio 5.6.2
Tal como se podem criar variaveis locais com a forma especial let,
tambem e possvel criar func~oes locais com a forma especial flet. A
sua sintaxe e extremamente parecida com a do let, so que o valor de
cada variavel e a denic~ao de uma func~ao.
A ttulo de exemplo, estude-se a seguinte denic~ao:
(defun teste (x)
(flet ((f-local1 (y) (+ x y))
(f-local2 (z) (* x z))
(f-local3 (x) (+ x 2)))
(+ (f-local1 x) (f-local2 x) (f-local3 x))))
> (teste 2)
12
> (f-local1 2)
Error: Undefined function F-LOCAL1
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
Uma vez que o ^ambito de um par^ametro e o corpo da lambda correspondente, e possvel escrever:
> ((lambda (z) ((lambda (w) (+ w z)) 3) 4)
7
Neste exemplo, cada lambda (ou cada let) estabelece um valor para
uma variavel. Quando se encontra uma refer^encia a uma variavel, o seu
valor e dado pela ligac~ao correspondente ao contexto mais pequeno. Se
n~ao existe qualquer ligac~ao em nenhum dos contextos, a variavel diz-se
n~ao ligada. A avaliac~ao de variaveis n~ao ligadas produz um erro.
Quando uma mesma variavel aparece ligada repetidamente em contextos sucessivos, a ligac~ao mais \interior" obscurece todas as \exteriores". Isto n~ao quer dizer que as ligac~oes exteriores sejam destrudas.
Elas s~ao apenas localmente substitudas durante a avaliac~ao do corpo
mais interior. Assim, temos o seguinte exemplo:
> (let ((x 10))
(+ (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) quando ela pode ser correctamente referida a partir de qualquer regi~ao do
programa.
Que tipo de ^ambito possui uma variavel de um let?
Que tipo de ^ambito possui o nome de uma func~ao?
Exerc
cio 6.1.1
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.
Exerc
cio 6.2.2
Exerc
cio 6.2.3
Solu
ca
~o do Exerc
cio 6.2.3
7 Dados
Em todos os exemplos anteriores temos apresentado func~oes essencialmente numericas. Os numeros s~ao um exemplo dos dados que os procedimentos podem usar e produzir. Vamos agora apresentar outros tipos
de dados que se podem utilizar.
7.1 A tomos
> (atom 1)
T
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
> (eq 1 1)
t
> (eq 111111111111111111111111111111111111
111111111111111111111111111111111111)
nil
> (cons 1 2)
(1 . 2)
> (cons (cons 1 2) 3)
((1 . 2) . 3)
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 arranjar 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
> (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
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))))
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
Solu
ca
~o do Exerc
cio 7.3.2
Exerc
cio 7.3.3 Escreva uma fun
c~ao que calcule o maior divisor comum 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 m
etodo de Euclides para reescrever a func~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 operac~oes que usavam numeros racionais, como +racional ou *racional.
35
A teoria dos tipos abstractos de informac~ao diz que o conceito fundamental 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 constituida por func~oes que se podem classicar em categorias: construtores,
selectores, reconhecedores e testes. Estas func~oes s~ao denidas em termos 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 fundamental 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 programas que o utilizam.
A implementac~ao de estruturas de dados complexas so e correctamente 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
comparar os numeradores.
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.
Esta notac~ao designa-se de lista e e esta que o Lisp usa para simplicar 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
Exerc
cio 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
Escreva uma func~ao que ltra uma lista, devolvendo uma lista com os elementos que vericam um determinado criterio.
Utilize-a para encontrar os numeros pares entre 1 e 20.
Exerc
cio 8.1.2
Solu
ca
~o do Exerc
cio 8.1.2
38
O que e que o avaliador de Lisp devolve para a seguinte express~ao: (first ''(1 2 3))?
Exerc
cio 8.1.3
Solu
ca
~o do Exerc
cio 8.1.3
'(1 2 3))
'(1 2 3))
(cdr '(1 2 3))
(cdr (cdr '(1 2 3))))
Exerc
cio 8.2.1
39
Solu
ca
~o do Exerc
cio 8.2.1
Escreva uma func~ao muda-n-esimo 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.
Exerc
cio 8.2.2
Solu
ca
~o do Exerc
cio 8.2.2
Exerc
cio 8.2.3
Solu
ca
~o do Exerc
cio 8.2.3
Exerc
cio 8.2.4
Solu
ca
~o do Exerc
cio 8.2.4
Exerc
cio 8.2.5
Solu
ca
~o do Exerc
cio 8.2.5
40
Solu
ca
~o do Exerc
cio 8.2.6
Solu
ca
~o do Exerc
cio 8.2.7
Escreva uma func~ao designada inverte-tudo que recebe 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.:
Exerc
cio 8.2.8
Solu
ca
~o do Exerc
cio 8.2.9
41
Exerc
cio 8.2.11
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 fun
c~ao elimina que recebe um elemento e uma lista como argumentos e devolve outra lista onde esse elemento
n~ao aparece.
Solu
ca
~o do Exerc
cio 8.2.12
42
Solu
ca
~o do Exerc
cio 8.2.14
Escreva as func~oes inversas do cons, car e cdr, designadas 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.
Exerc
cio 8.2.15
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)))))
43
Solu
ca
~o do Exerc
cio 8.2.16
44
$
$
$
Estando na posse destas func~oes, podemos criar um automovel especco, por exemplo:
> (novo-automovel 'honda 'civic 2)
(honda civic 2)
Dado aquele objecto do tipo automovel, podemos estar interessados 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 modicar um objecto. Para o tipo
automovel poderiamos denir:
(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))
9 Programac~ao Imperativa
Todas as func~oes que apresentamos anteriormente realizam operac~oes
muito variadas e algumas s~ao ate relativamente complexas, mas nenhuma 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 inicializadas 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 argumentos 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 valores. 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
9.1 Atribuic~ao
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 qualquer 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 sucessivos 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
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.
Escreva uma func~ao junta! (note-se o ponto de exclamac~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.
Exerc
cio 9.3.1
Solu
ca
~o do Exerc
cio 9.3.1
Solu
ca
~o do Exerc
cio 9.3.2
49
Exerc
cio 9.3.3
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.
Reescreva as operac~oes do tipo abstracto de informac~ao automovel que alteravam as caractersticas de um elemento do tipo
de forma a torna-las destrutivas.
Exerc
cio 9.3.4
Solu
ca
~o do Exerc
cio 9.3.4
Repare-se que no primeiro exemplo (funcional), o automovel modicado e, logicamente, diferente do automovel original. x e y representam
automoveis diferentes. No segundo exemplo (imperativo), a modicac~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 (modicado). 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 supercial do codigo sugira que ele foi criado com apenas duas.
9.4 Repetic~ao
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 passar 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 enquadramentos. 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)))
Como se v^e pelo exemplo. A partir do momento em que se estabeleceu 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 encontrar uma destas formas de utilizac~ao. A utilizac~ao do setq deve ser
restricta a modicac~oes do valor de variaveis previamente estabelecidas.
As regras de avaliac~ao do modelo de ambientes s~ao, em tudo, equivalentes as do modelo classico, excepto no que diz respeito a aplicac~ao
de func~oes.
53
Solu
ca
~o do Exerc
cio 10.1.1
E de salientar o caracter evolutivo da func~ao incrementa. Na metodologia da programac~ao funcional, o resultado de uma func~ao so depende 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 funcional. 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.
Exerc
cio 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 logicamente a todos os que foram gerados anteriormente. Seguindo este
paradigma, dena o gerador da sequ^encia de Fibonacci, que e representada pela sucess~ao crescente 1, 2, 3, 5, 8, ..., em que cada numero e a
soma dos dois ultimos que o precedem.
54
Infelizmente, a func~ao gera-fib n~ao sabe recomecar. Para isso, e necessario deni-la (compila-la) outra vez. Este processo, 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.
Exerc
cio 10.1.3
Solu
ca
~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.
(gera-fib)
(gera-fib)
(repoe-fib)
(gera-fib)
55
Note-se a convenc~ao adoptada para as variaveis globais de usar nomes compreendidos entre um par de asteriscos. Quando se denem
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 qualquer 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 produzido um erro quando tentarmos determinar o seu valor, uma vez que
ele ainda esta indenido. 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*
56
11 Par^ametros Especiais
Exerc
cio 11.2.1
Solu
ca
~o do Exerc
cio 11.2.1
58
A grande maioria das func~oes pre-denidas na linguagem para manipular listas possui par^ametros opcionais e de chave. Repare-se que
as chaves s~ao tratadas de forma especial pelo avaliador. Efectivamente, se assim n~ao fosse, quando especicavamos 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
59
12 Macros
Como referimos na apresentac~ao da linguagem Lisp, existem certas formas 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 forma 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, superando assim as diculdades inerentes a avaliac~ao dos argumentos que as
func~oes realizam. Na realidade, Lisp possui muito poucas formas especiais 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.
61
Solu
ca
~o do Exerc
cio 12.3.1
(quando teste
expr 1 expr 2 ... expr n )
A macro sera:
(defmacro quando (teste &rest exprs)
(list 'cond
(cons teste exprs)
'(t nil)))
Geralmente, a parte mais difcil na escrita de uma macro e a determinac~ao das express~oes Lisp que quando avaliadas produzem uma nova
express~ao Lisp que realiza o nosso objectivo. Para simplicar esta tarefa 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 conveniente.
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 isso, 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 denilo. A express~ao 'ola representa, como sabemos, (quote ola), logo a
62
Exerc
cio 12.5.1
comma
Solu
ca
~o do Exerc
cio 12.5.1
63
backquote,
Exerc
cio 12.5.2
Solu
ca
~o do Exerc
cio 12.5.2
Exerc
cio 12.5.3
Solu
ca
~o do Exerc
cio 12.5.3
A implementac~ao da macro meu-cond anterior implica 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.
Exerc
cio 12.5.4
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.
e a sua expans~ao:
((lambda (x y) (+ x y)) 10 20)
64
Solu
ca
~o do Exerc
cio 12.5.6
Exerc
cio 12.5.7
65
12.6 Iteradores
Como se pode depreender dos exemplos apresentados, as macros destinam-se essencialmente a criac~ao de acucar sintatico, i.e., de express~oes
que sejam mais simples de utilizar que outras ja existentes. Esta caracterstica 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))
O problema esta no facto de a macro estabelecer uma variavel denominada lista, que interfere com a variavel exterior do segundo exemplo, 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 diculdades.
Exerc
cio 12.6.1 Dena a macro itera-lista usando a referida func~ao gensym para proteger as variaveis do utilizador de uma captura
indevida.
Solu
ca
~o do Exerc
cio 12.6.1
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
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 elemento 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 capacidades 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 denido 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) ...)
(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
Exerc
cio 12.7.1
71
Assim, se se criasse um automovel sem especicar o numero de portas, ele seria de 4. Implemente essa alterac~ao.
Solu
ca
~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 modicar o construtor e passar apenas os nomes
dos campos (esquecendo as inicializac~oes por omiss~ao) as restantes func~oes.
Esta forma de criac~ao de chas ja existe em Common Lisp atraves da macro defstruct.
72
Indice Remissivo
(), 38
1+, 19
1-, 19
<=, 13
<, 13
=, 13, 32
>=, 13
>, 13
&key, 60, 73
&optional, 58
&rest, 59
and, 13, 17
append, 42, 44
apply, 45
atom, 31
backquote, 64
butlast, 44
car, 33, 38
case, 66
cdr, 33, 38
comma-at, 64
comma, 64
concatenate, 71
cond, 15, 17, 49, 61
cons, 33, 38
defconstant, 56
defmacro, 61, 62
defparameter, 56
defstruct, 73
defun, 10, 17, 26, 49,
defvar, 56
dolist, 68
do, 52
endp, 39
eql, 33, 66
eq, 32, 34
every, 45
first, 39, 44
flet, 28
funcall, 22, 45
function, 22, 26, 40,
gensym, 68
if, 14, 17, 61
intern, 71
labels, 28
last, 44
length, 41
let, 27, 48, 49, 53, 54, 66
list, 39, 44, 46
loop, 52
mapcar, 42
max, 26
member, 43
nconc, 50
nil, 13, 38
notany, 45
notevery, 45
not, 13
nth, 41
null, 38
or, 13, 17
prog1, 49
progn, 48
quote, 32, 39, 63
read, 64
remove-duplicates, 44
remove-if-not, 39
remove, 43
rest, 39
return, 52
reverse, 42
rplaca, 49
rplacd, 49
set-macro-caracter, 63
setq, 48, 53{55
some, 45
string, 71
subst, 43
trace, 21
t, 13
unless, 65
untrace, 21
when, 63
zerop, 13
55
63
73