Você está na página 1de 79

UVV

Centro Universitrio Vila Velha

Linguagens de Programao II
Programao Funcional usando LISP

Professor: Cristiano Biancardi

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

Sumrio
1 - Elementos da Linguagem .............................................................................................. 4
1.1 - Elementos primitivos .......................................................................................... 4
1.2 - Combinaes ....................................................................................................... 5
1.3 - Avaliao de Combinaes ............................................................................... 7
1.4 - Definio de Funes......................................................................................... 8
1.5 Funes bsicas .............................................................................................. 10
1.6 Testando nmeros ........................................................................................... 12
1.7 Avaliando funes............................................................................................ 14
1.8 - Smbolos............................................................................................................. 15
1.9 Inserindo comentrios.......................................................................................... 16
2 Expresses Condicionais ............................................................................................ 17
2.1 - Predicados ......................................................................................................... 17
2.2 - Operadores Lgicos ......................................................................................... 18
2.3 - Seleo simples ................................................................................................ 19
2.4 - Seleo Mltipla ................................................................................................ 19
3 - Funes........................................................................................................................ 21
3.1 - Funes Recursivas ......................................................................................... 21
3.2 Depurao de funes .......................................................................................... 23
3.3 Funes de ordem superior .................................................................................. 23
3.4 Lambda ................................................................................................................ 25
3.5 Variveis Locais................................................................................................... 25
3.6 Funes Locais..................................................................................................... 27
4 mbito e Durao ....................................................................................................... 29
4.1 mbito de uma referncia.................................................................................... 29
4.2 Durao de uma referncia .................................................................................. 30
5 Dados .......................................................................................................................... 32
5.1 - tomos.................................................................................................................. 32
5.2 Combinao de dados .......................................................................................... 33
5.3 Abstrao de dados .............................................................................................. 34
5.4 - Tipos Abstratos de Informao............................................................................. 36
6 Entrada e sada ............................................................................................................ 38
6.1 Leitura de dados a partir do teclado..................................................................... 38
6.1 Impresso de dados .............................................................................................. 39
7 Listas ........................................................................................................................... 41
7.1 Operaes sobre listas............................................................................................. 41
7.2 Funes teis........................................................................................................ 43
7.4 Usando as operaes ............................................................................................ 46
7.5 Listas de argumentos............................................................................................ 49
7.6 Tipos aglomerados ............................................................................................... 50
8 Programao imperativa ............................................................................................. 52
8.1 Atribuio ............................................................................................................ 52
8.2 Sequenciao........................................................................................................ 53
8.3 Alterao de dados............................................................................................... 54
8.4 Repetio.............................................................................................................. 56

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


9 Modelos de ambientes................................................................................................. 59
9.1 mbito lxico....................................................................................................... 60
9.2 mbito dinmico ................................................................................................. 62
10 Parmetros especiais ................................................................................................. 64
10.1 Parmetros opcionais ......................................................................................... 64
10.2 Parmetros de resto ............................................................................................ 64
10.3 Parmetros de chave ......................................................................................... 65
11 Macros....................................................................................................................... 68
11.1 Avaliao de macros .......................................................................................... 68
11.2 Escrita de macros ............................................................................................... 68
11.3 Depurao de macros......................................................................................... 69
11.4 Caracteres de macro........................................................................................... 70
11.5 Macros utis ....................................................................................................... 71
11.6 Iteradores............................................................................................................ 73
11.7 Fichas ................................................................................................................. 74
Referncias........................................................................................................................ 79

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

1 - Elementos da Linguagem
Em linguagens Funcionais toda a programao realizada por meio da
aplicao de funes a argumentos. As variveis e as operaes de atribuio
usadas pelas linguagens imperativas no so necessrias. Os laos de
repetio so substitudos por chamadas a funes recursivas. Baseiam-se
fortemente nos conceitos das funes matemticas.
Qualquer linguagem de programao lida com duas espcies de objetos: dados
e procedimentos. Os dados so os objectos que pretendemos manipular. Os
procedimentos so descries das regras para manipular esses dados.
Se considerarmos a linguagem da matemtica, podemos identificar os nmeros
como dados e as operaes algbricas como procedimentos e podemos
combinar os nmeros entre si usando aquelas operaes. Por exemplo, 2 2
uma combinao, tal como 2 2 2 e 2 2 2 2. No entanto, a menos que
pretendamos ficar eternamente a resolver problemas de aritmtica elementar,
somos obrigados a considerar operaes mais elaboradas que representam
padres de clculos. Neste ltimo exemplo, evidente que o padro que est a
emergir o da operao de potenciao, i.e, multiplicao sucessiva, tendo esta
operao sido definida na matemtica h j muito tempo.
Tal como a linguagem da matemtica, uma linguagem de programao 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 padres de clculo de modo a permitir
trat-los como operaes simples, definindo novas operaes que representem
esses padres de clculo.

1.1 - Elementos primitivos


Elementos primitivos so as entidades mais simples com que a linguagem lida.
Um nmero, por exemplo, 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 avaliado, i.e., tem de ter
um valor, valor esse que o Lisp escreve no ecran.
Assim, se dermos um nmero ao avaliador, ele devolve-nos o valor desse
nmero. Quanto vale um nmero? O melhor que podemos dizer que ele vale
aquilo que vale. Por exemplo, o nmero 1 vale 1.
>1
1
> 12345
12345

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


> 4.5
4.5

Como se v no exemplo, em Lisp, os nmeros podem ser inteiros ou reais.


Exerccio 2
Descubra qual o maior real que o seu Lisp aceita. Consegue fazer o mesmo
para os inteiros?

1.2 - Combinaes
Combinaes so entidades complexas feitas a partir de entidades mais
simples. Por exemplo, na matemticas nmeros podem ser combinados usando
operaes como a soma ou o produto. Como exemplo de combinaes
matemticas, temos 1 + 2 e 1 + 2 3. A soma e o produto de nmeros so
exemplos
de
operaes
extremamente
elementares
consideradas
procedimentos primitivos.
Em Lisp, criam-se combinao escrevendo uma sequncia de expresses entre
um par de parnteses. Uma expresso um elemento primitivo ou uma outra
combinao. A expresso (+ 1 2) uma combinao dos elementos primitivos 1 e
2 atravs do procedimento +. J no caso (+ 1 (* 2 3)) a combinao ocorre entre
1 e (* 2 3), sendo esta ltima expresso uma outra combinao.
No difcil de ver que as nicas combinaes com utilidade so aquelas em
que as expresses correspondem a operadores e operandos. Por conveno, o
Lisp considera que o primeiro elemento de uma combinao um operador e os
restantes so os operandos.
A notao que o Lisp utiliza para construir expresses (operador primeiro e
operandos a seguir) designada por notao prefixa. Esta notao costuma
causar alguma perplexidade a quem inicia o estudo da linguagem, que espera
uma notao mais prxima da que aprendeu em aritmtica e que usada
habitualmente nas outras linguagens de programao. Nestas, a expresso (+ 1
(* 2 3)) usualmente escrita na forma 1 + 2 * 3 (designada notao infixa) que,
normalmente, mais simples de ler por um ser humano. No entanto, a notao
prefixa usada pelo Lisp tem largas vantagens sobre a notao infixa:
!
!
!
!

muito fcil usar operadores que tm um nmero varivel de


argumentos, como por exemplo:
> (+ 1 2 3)
6
> (+ 1 2 3 4 5 6 7 8 9 10)

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


!

55

Numa linguagem do tipo Pascal apenas existem operadores unrios ou


binrios, e necessrio explicitar os operador binrios entre cada dois
operandos: 1+2+3 ou 1+2+3+4+5+6+7+8+9+10. Um operador deste tipo diz-se
infixo (in significa entre). Se se pretender um operador ternrio (ou outro)
j no se consegue escrever do mesmo modo, sendo necessrio
implement-lo como uma funo.
!

No existe precedncia entre os operadores. Em Pascal, a expresso


1+2*3 tem de ser calculada como se se tivesse escrito 1+(2*3), e no
(1+2)*3, i.e., foi criada uma precedncia que elimina as ambiguidades.
Essa precedncia pode ser alterada atravs do emprego de parnteses.
Em Lisp, as expresses seriam necessariamente distintas, (+ 1 (* 2 3)) ou (*
(+ 1 2) 3), no podendo haver qualquer ambiguidade.
Os operadores que ns definimos usam-se exatamente da mesma
maneira que os operadores da linguagem: operador primeiro e operandos
a seguir. Em Pascal, os operadores so infixos, i.e., esto entre os
operandos, e as funes e procedimentos por ns definidos so prefixos,
i.e., primeiro a funo ou procedimento e depois os operandos. Isto
impede a extenso da linguagem de uma forma coerente.
Para exemplificar este ltimo aspecto, consideremos a operao de
exponenciao 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 potncia de 3. Como esse operador no existe na
linguagem (standard), somos obrigados a criar uma funo que o
substitua mas, neste caso, a sintaxe muda radicalmente. Esta funo,
como se usa de forma prefixa no se pode tornar coerente com as
restantes operaes do Pascal, como a soma e a multiplicao. Em Lisp,
pelo contrrio, tanto podemos escrever (* 3 3 3 3) como definir uma funo
que permita escrever (** 3 4).

A desvantagem da notao prefixa est na escrita de combinaes complexas.


A expresso Pascal 1+2*3-4/5*6, equivale expresso Lisp (- (+ 1 (* 2 3)) (* (/ 4 5) 6))
que embora seja mais simples de ler para uma mquina, pode ser mais difcil de
ler para um ser humano devido acumulao de parnteses. No entanto, esta
expresso pode ser escrita de forma mais clara usando indentao.
A regra para indentao de combinaes Lisp extremamente simples. Numa
linha coloca-se o operador e o primeiro operando. Os restantes operandos vm
alinhados por debaixo do primeiro.
(- (+ 1
(* 2 3))
(* (/ 4 5)
6))

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Quando a regra de indentao no suficiente, usam-se pequenas variaes,
como seja colocar o operador numa linha e os operandos por debaixo, por
exemplo:
(umaoperacaocomumnomemuitogrande
1 2 3 4)

A indentao fundamental em Lisp pois muito fcil escrever cdigo


complexo. A grande maioria dos editores preparados para Lisp (Emacs, por
exemplo) formatam automaticamente os programas medida que os
escrevemos e mostram o emparelhamento de parnteses. Desta forma, aps
algum tempo de prtica, torna-se muito fcil escrever e ler os programas, por
mais complexa que possa parecer a sua estrutura.
Exerccio 3
Converta as seguintes expresses da notao infixa da aritmtica para a
notao prefixa do Lisp:
1+2-3
1-2*3
1*2-3
1*2*3
(1 - 2) * 3
(1 - 2) + 3
1 - (2 + 3)
2*2+3*3*3

Exerccio 4
Converta as seguintes expresses da notao prefixa do Lisp para a notao
infixa da aritmtica:
(* (/ 1 2) 3)
(* 1 (- 2 3))
(/ (+ 1 2) 3)
(/ (/ 1 2) 3)
(/ 1 (/ 2 3))
(- (- 1 2) 3)
(- 1 2 3)

1.3 - Avaliao de Combinaes


Como j vimos, o Lisp considera que o primeiro elemento de uma combinao
um operador e os restantes so os operandos.
O avaliador determina o valor de uma combinao como o resultado de aplicar o
procedimento especificado pelo operador ao valor dos operandos. O valor de
cada operando designado de argumento do procedimento. Assim, o valor da
combinao (+ 1 (* 2 3)) o resultado de somar o valor de 1 com o valor de (* 2 3).
7

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Como j se viu, 1 vale 1 e (* 2 3) uma combinao cujo valor o resultado de
multiplicar o valor de 2 pelo valor de 3, o que d 6, e que somado a 1 d 7.
> (+ 1 2)
3
> (+ 1 (* 2 3))
7

Exerccio 5
Calcule o valor das seguintes expresses Lisp:
(* (/ 1 2) 3)
(* 1 (- 2 3))
(/ (+ 1 2) 3)
(/ (/ 1 2) 3)
(/ 1 (/ 2 3))
(- (- 1 2) 3)
(- 1 2 3)
(- 1)

1.4 - Definio de Funes


Tal como em matemtica, pode-se definir numa linguagem de programao a
operao de elevar um nmero ao quadrado. Assim, se pretendermos
determinar o produto de um nmero por ele prprio, escrevemos a combinao
(* x x) sendo x o nmero, i.e.
> (* 5 5)
25
> (* 6 6)
36

Os nmeros 5 e 6 e a operao * so elementos primitivos. As expresses (* 5 5)


e (* 6 6) so combinaes. combinao genrica (* x x) queremos associar um
nome, por exemplo, quadrado. Isso equivale a acrescentar uma nova funo
linguagem.
A ttulo de exemplo, vamos definir a funo quadrado:
> (defun quadrado (x)
(* x x))
quadrado

Para se definirem novas funes em Lisp, necessrio criar uma combinao


de quatro elementos. O primeiro elemento desta combinao a palavra defun,
que informa o avaliador que estamos a definir uma funo. O nome defun uma
abreviatura de ``define function''. O segundo elemento o nome da funo que
queremos definir, o terceiro elemento uma combinao com os parmetros da

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


funo e o quarto elemento a combinao que determina o valor da funo
para aqueles parmetros.
Quando se d uma expresso desta forma ao avaliador, ele acrescenta a funo
ao conjunto de funes da linguagem, associando-a ao nome que lhe demos e
devolve como valor da definio o nome da funo definida.
A definio da funo quadrado diz que para se determinar o quadrado de um
nmero x, devemos multiplicar esse nmero por ele prprio (* x x). Esta definio
associa a palavra quadrado a um procedimento. Este procedimento possui
parmetros e um corpo de expresses. De forma genrica temos:
(defun nome (parmetro-1 ...parmetro-n)
corpo)

Os parmetros de um procedimento so designados parmetros formais e so


os nomes usados no corpo de expresses para nos referirmos aos argumentos
correspondentes. Quando escrevemos no avaliador de Lisp (quadrado 5), 5 o
argumento. Durante o clculo da funo este argumento est associado ao
parmetro formal x. Os argumentos de uma funo so tambm designados
parmetros atuais.
> (quadrado 5)
25
> (quadrado 6)
36

Note-se que a regra de avaliao de combinaes tambm vlida para as


funes por ns definidas. Assim, a avaliao da expresso (quadrado (+ 1 2))
passa pela avaliao do operando (+ 1 2). Este operando tem como valor 3, valor
esse que usado pela funo no lugar do parmetro x. O corpo da funo
ento avaliado, i.e., o valor final ser o da combinao (* 3 3).
O seguinte exemplo mostra um caso um pouco mais complexo. Nele esto
apresentadas as etapas de avaliao dos operandos e de avaliao do corpo da
funo.
(quadrado (quadrado (+ 1 2)))
(quadrado (quadrado 3))
(quadrado (* 3 3))
(quadrado 9)
(* 9 9)
81

A definio de funes permite-nos associar um procedimento a um nome. Isto


implica que o Lisp tem de possuir uma memria onde possa guardar o
procedimento e a sua associao ao nome dado. Esta memria do Lisp designase ambiente.

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Note-se que este ambiente apenas existe enquanto estamos a trabalhar com a
linguagem. Quando terminamos, perde-se todo o ambiente. Isto implica que, se
no queremos perder o trabalho que estivemos a escrever, devemos escrever
as funes num ficheiro e ir passando-as para o Lisp. A grande maioria das
implementaes do Lisp permite fazer isto de forma automtica.

1.5 Funes bsicas


(+)

( Soma )
( + 3.1 2.7 ) " 5.800000000000001
( + 4 7 9 ) " 20
( + 9/2 4 ) " 17/2

Note que 9/2 e 17/2 so nmeros racionais que podem ser manipulados da mesma
forma de inteiros e floats, e que a funo soma no se restringe a apenas dois parmetros.
(-)

( Subtrao )
( - 3.1 2.7 ) " 0.3999999999999999
( - 1 3 5 7 ) " -14
( - -4 )
" 4

(*)

( Multiplicao )
( * 3.1 7.0 )
" 21.7
( * 3.1 7.1 10.0 ) " 220.09999999999997
(*43)
" 12

(/)

( Diviso )
( / 21.7 7.0 ) " 3.1
( / 9 2 ) " 9/2
( / 30 2 3 )
"5
(/2)
" 1/2 - inverso do nmero

Note que se houver mais de dois argumentos na diviso, o primeiro deles ser dividido
sucessivamente pelos demais.
sqrt

( Raiz quadrada )

10

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

( sqrt 4 ) " 2.0


( sqrt 4.0 ) " 2.0
( sqrt 4 ) " #C(0,0 2.0)
Se o argumento for um nmero negativo, a funo no retornar um erro mas sim
um nmero complexo na forma #C(3 2) que equivalente a 3 + 2i.
expt

( Exponencial )
( expt 2 10 ) " 1024
( expt 2 10.0 ) " 1024.0
( expt 2/3 3 ) " 8/27
( expt 4 ) " #C(1.22514845490862E-16 2.0)

Se algum dos argumentos for do tipo float, o resultado tambm ser convertido
para float, podendo ocorrer aproximaes do nmero. No caso de 0.0, o resultado
aproximado para #C(1.22514845490862E-16 2.0).
log

( Logaritmo )
( log 1 ) " 0.0
( log 10 ) " 2.302585092994046
( log 10 2 ) " 3.3219280948873626

abs

( Valor Absoluto )
( abs 8 )
"8
( abs 8 ) " 8
( abs 8.9 ) " 8.9

truncate

( Trunca )

( truncate 13.5 )
( truncate 0.7 ) " 0
( truncate 1.7 )

" 13 0.5
0.7
" -1 -0.7

Truncar retornar o valor inteiro de um nmero. Note que a funo retorna dois
valores: o primeiro o valor truncado, a parte inteira, e a segunda a parte decimal do
nmero, o resto.

11

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

round ( Arredonda )
( round 3.1 ) " 3 0.1
( round 3.7 ) " 4 -0.3

( round 3.5 ) " 4 -0.5

Esta funo arredonda um nmero ao inteiro mais prximo. Novamente, h o


retorno de dois valores: o nmero inteiro arredondado e a parte decimal restante do
arredondamento, que pode ser positiva ou negativa.
rem

( Resto )
( rem 9 3 )
"0
( rem 3 9 )
"3
( rem 17 4 ) " -1
Esta funo retorna o resto inteiro da diviso.

float

( Float )
( float 4 )
( float 135 )

" 4.0
" 135.0

Converte um dado do tipo inteiro em ponto flutuante ( float).


Atribuio
O comando ( setf varivel valor ) faz a atribuio de valores a variveis.
( setf x 4 )
" 4 ;a varivel x recebe o valor 4
( setf zero 0 one 1 two 2 three 3 four 4 )
" 4 ; como foram feitas atribuies
mltiplas, o
; interpretador retorna apenas o ltimo
valor.

1.6 Testando nmeros


A linguagem Lisp possui algumas funes de testes que so chamadas predicados.
Esses predicados so funes que retornam apenas um dos dois valores: Verdadeiro (t) ou
Falso (nil).
Os predicados listados a seguir podem ser usados com nmeros e smbolos que
possuam um valor numrico.

12

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

Vamos inicializar quatro variveis com quatro valores distintos para que os
exemplos a seguir sejam executados corretamente.
( setf zero 0 one 1 two 2 three 3 four 4 )
"4
(>)

( Ordem Descendente )
( > 100 10 one 0.1 )
( > 10 100 1 0.1 )

"
"

T
NIL

Retorna verdadeiro se os elementos esto dispostos em ordem descendente.


(<)

( Ordem Ascendente )
(<1352) "
( < 1 two 4 ) "

NIL
T

Retorna verdadeiro se os elementos esto dispostos em ordem ascendente.


(=)

( Igual )
( = ( / 2.0 3.0) ( / 4.0 6.0 ) )
( = ( 3.141592653 ( /22 7 ) ) "

max

"
NIL

( Mximo )
( max 1 -2 3 4 5 6 7 ) "
( max ( * 3 7) ( * 2 3 ) )

7
"

21

Retorna o argumento de maior valor.


min

( Mnimo )
( min 1 2 3 4 5 6 7 )
"
( min ( expt 2 10 ) ( expt 10 2 ) )

-2
"

100

Retorna o argumento de menor valor.


evenp

( Par )

13

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


( evenp 17 )
( evenp ( * 3 2 ) )

"
"

NIL
T

Testa se o argumento par.


oddp

( mpar )
( oddp 17 )
( oddp two )

"
"

T
NIL

Testa se o argumento mpar.


minusp

( Negativo )

( minusp 17 )
( minusp 13 ) "

"
T

NIL

Verifica se o argumento um nmero negativo.


zerop

( Zero )
( zerop ( * 2 0 ) )
( zerop ( + 2 0 ) )
( zerop zero ) "

"
"
T

T
NIL

Testa se o argumento igual a zero. zerop mais eficiente que seu equivalente ( =
number 0).

1.7 Avaliando funes


Em Lisp, + uma funo, assim uma expresso como (+ 2 3) uma chamada de
funo envolvendo dois argumentos.
Quando Lisp avalia uma chamada de funo, ele o faz em duas etapas:
! primeiramente os argumentos so avaliados, da esquerda para a direita. Neste
contexto, cada argumento se "autoavalia". Logo, 2 avalia para 2 e 3 valia
para 3, e, assim, os valores dos argumentos so 2 e 3, respectivamente;
! os valores dos argumentos so passados a funo designada pelo operador.
Neste caso, a funo +, que retorna 5.

14

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Se alguns dos argumentos so chamadas de funes, suas avaliaes seguem as
mesmas regras que as enunciadas anteriormente. Assim, quando (/ (- 7 1) (- 4 2))
avaliada, observa-se as seguintes etapas:
! Lisp avalia (- 7 1), onde 7 avalia para 7 e 1 avalia para 1. Estes valores so
passados para a funo -, que retorna 6;
! Lisp valia (- 4 2), onde 4 avalia para 4 e 2 avalia para 2. Estes valores so
passados para a funo -, que retorna 2;
! Os valores 6 e 2 so enviados para a funo /, que retorna 3.
Note que todos os operadores em Common Lisp so de fato funes, exceto para
alguns casos (por exemplo, o quote comentado mais adiante). As chamadas de funes
so sempre avaliadas da mesma maneira. Os argumentos so avaliados da esquerda para a
direita, e seus valores passados para a funo, que retorna o valor da expresso como um
todo. Isto conhecido em Common Lisp como a evaluation rule (regra de avaliao).

1.8 - Smbolos
A definio de funes em Lisp passa pela utilizao dos seus nomes. Nomes
para as funes e nomes para os parmetros das funes.
Uma vez que o Lisp, antes de avaliar qualquer expresso, tem de a ler e
converter internamente para um objeto, os nomes que criamos tm de ter um
objeto associado. Esse objeto designado por smbolo. Um smbolo , pois, a
representao interna de um nome.
Em Lisp, quase no existem limitaes para a escrita de nomes. Um nome como
quadrado to bom como + ou como 1+2*3 pois o que separa um nome dos outros

elementos de uma combinao so apenas parnteses e espaos em branco.


Por exemplo, perfeitamente correto definir e usar a seguinte funo:
> (defun x+y*z (x y z)
(+ (* y z) x))
x+y*z
> (x+y*z 1 2 3)
7

Lisp atribui um significado muito especial aos smbolos. Reparemos que, quando
definimos uma funo, os parmetros formais da funo so smbolos. O nome
da funo tambm um smbolo. Quando escrevemos uma combinao, o
avaliador de Lisp usa a definio de funo que foi associada ao smbolo que
constitui o primeiro elemento da combinao. Quando, no corpo de uma funo,
usamos um smbolo para nos referimos a um parmetro formal, esse smbolo
tem como valor o respectivo parmetro atual que lhe foi associado naquela
combinao. Por esta descrio se v que os smbolos constituem um dos
elementos fundamentais da linguagem Lisp.

15

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Exerccio 6
- Crie uma funo que some trs nmeros.
- Crie uma funo que calcule o delta de uma equao do segundo grau. Delta =
b2 4*a*c
Resposta

1.9 Inserindo comentrios


Para facilitar a compreenso de um cdigo faz-se uso de comentrios. Em Lisp, os
comentrios devem ser precedidos de ponto-e-vrgula ; , caracter que indica que a
respectiva linha de cdigo no deve ser avaliada pelo interpretador.
( defun novogosto ( nome )
; adiciona nome no incio da lista gosta
( setf gosta ( cons nome gosta ) )
; deleta nome da lista detesta
( setf detesta ( remove nome detesta) ) )

16

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

2 Expresses Condicionais
Existem muitas operaes cujo resultado depende da realizao de um determinado teste.
Por exemplo, a funo matemtica abs --que calcula o valor absoluto de um nmero-equivale ao prprio nmero, se este positivo, ou equivale ao seu simtrico se for
negativo. Estas expresses, cujo valor depende de um ou mais testes a realizar
previamente, permitindo escolher vias diferentes para a obteno do resultado, so
designadas expresses condicionais.
No caso mais simples de uma expresso condicional, existem apenas duas alternativas a
seguir. Isto implica que o teste que necessrio realizar para determinar a via de clculo
a seguir deve produzir um de dois valores, em que cada valor designa uma das vias.
Seguindo o mesmo raciocnio, uma expresso condicional com mais de duas alternativas
dever implicar um teste com igual nmero de possveis resultados. Uma expresso da
forma ``caso o valor do teste seja 1, 2 ou 3, o valor da expresso 10, 20 ou 30,
respectivamente'' um exemplo desta categoria de expresses que, embora seja
considerada pouco intuitiva, existe nalgumas linguagens (Basic, por exemplo). Contudo,
estas expresses podem ser facilmente reduzidas primeira. Basta decompor a expresso
condicional mltipla numa composio de expresses condicionais simples, em que o
teste original tambm decomposto numa srie de testes simples. Assim, poderamos
transformar o exemplo anterior em: ``se o valor do teste 1, o resultado 10, caso
contrrio, se o valor 2, o resultado 20, caso contrrio 30''.

2.1 - Predicados
Desta forma, reduzimos todos os testes a expresses cujo valor pode ser apenas um de
dois, e a expresso condicional assume a forma de ``se ...ento ...caso contrrio ...''. Nesta
situao, a funo usada como teste denominada predicado e o valor do teste
interpretado como sendo verdadeiro ou falso. Nesta ptica, o fato de se considerar o valor
como verdadeiro ou falso no implica que o valor seja de um tipo de dados especial,
como o booleano ou lgico de algumas linguagens (como o Pascal), mas apenas que a
expresso condicional considera alguns dos valores como representando o verdadeiro e
os restantes como o falso.
Em Lisp, as expresses condicionais consideram como falso um nico valor. Esse valor
representado por nil. Qualquer outro valor diferente de nil considerado como
verdadeiro. Assim, do ponto de vista de uma expresso condicional, qualquer nmero
um valor verdadeiro. Contudo, no faz muito sentido para o utilizador humano considerar
um nmero como verdadeiro ou falso, pelo que se introduziu uma constante na
linguagem para representar verdade. Essa constante representa-se por t.

17

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Note-se que, se t diferente de nil, e se nil o nico valor que representa a falsidade,
ento t representa verdade. Desta forma, existem muitos predicados em Lisp cujo valor
t quando a expresso por eles designada considerada verdadeira.
> (> 4 3)
t
> (< 4 3)
nil

Existem muitos predicados em Lisp. Os predicados numricos mais usados so o zerop,


=, >, <, >=, <=. O zerop testa se um nmero zero ou diferente de zero.
> (zerop 1)
nil
> (zerop 0)
t

O fato de zerop terminar com a letra ``p'' deve-se a uma conveno adotada em Common
Lisp segundo a qual os predicados devem ser distinguidos das restantes funes atravs
da concatenao da letra ``p'' (de predicate) ao seu nome.
Apesar da adoo dos smbolos t e nil, convm alertar que nem todos os predicados
devolvem t ou nil exclusivamente. Alguns h que, quando querem indicar verdade,
devolvem valores diferentes de t (e de nil, obviamente).

2.2 - Operadores Lgicos


Para se poder combinar expresses lgicas entre si existem os operadores and, or e not.
O and e o or recebem qualquer nmero de argumentos. O not s recebe um. O valor das
combinaes que empregam estes operadores lgicos determinado do seguinte modo:
!

O and avalia os seus argumentos da esquerda para a direita at que um deles seja
falso, devolvendo este valor. Se nenhum for falso o and devolve o valor do ltimo
argumento.
O or avalia os seus argumentos da esquerda para a direita at que um deles seja
verdade, devolvendo este valor. Se nenhum for verdade o or devolve o valor do
ltimo argumento.
O not avalia para verdade se o seu argumento for falso e para falso em caso
contrrio.

Note-se que embora o significado de falso seja claro pois corresponde necessariamente ao
valor nil, o significado de verdade j no to claro pois, desde que seja diferente de
nil, considerada verdade.
Exerccio 7
Qual o valor das seguintes expresses?
18

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


(and (or (> 2 3) (not (= 2 3))) (< 2 3))
(not (or (= 1 2) (= 2 3)))
(or (< 1 2) (= 1 2) (> 1 2))
(and 1 2 3)
(or 1 2 3)
(and nil 2 3)
(or nil nil 3)

2.3 - Seleo simples


O if a expresso condicional mais simples do Lisp. O if determina a via a seguir em
funo do valor de uma expresso lgica. A sintaxe do if :
(if condio
consequente
alternativa)

Para o if, a condio o primeiro argumento, o consequente no caso de a condio ser


verdade o segundo argumento e a alternativa no caso de ser falso o terceiro
argumento.
> (if (> 4 3)
5
6)
5

Uma expresso if avaliada determinando o valor da condio. Se ela for verdade,


avaliado o consequente. Se ela for falsa avaliada a alternativa.
Exerccio 8
Defina uma funo soma-grandes que recebe trs nmeros como argumento e determina
a soma dos dois maiores.
Exerccio 9
Escreva uma funo que calcule o factorial de um nmero.
A funo fact um exemplo de uma funo recursiva, i.e., que se refere a ela prpria.

2.4 - Seleo Mltipla


Alm do if, existe outra expresso condicional em Lisp. O cond uma verso mais
potente que o if. uma espcie de switch-case do C. A sua sintaxe :
(cond (condio-1 expresso-1)

19

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


(condio-2 expresso-2)
(condio-n expresso-n))

Designa-se o par (condio-i expresso-i) por clusula. O cond testa cada uma das
condies em sequncia, e quando uma delas avalia para verdade, devolvido o valor da
expresso correspondente, terminando a avaliao. Um exemplo ser:
> (cond ((> 4 3) 5)
(t 6))
5

O cond permite uma anlise de casos mais simples do que o if.


(defun teste (x y z w)
(cond ((> x y) z)
((< (+ x w) (* y z)) x)
((= w z) (+ x y))
(t 777)))

A funo equivalente usando if seria mais complicada.


(defun teste (x y z w)
(if (> x y)
z
(if (< (+ x w) (* y z))
x
(if (= w z)
(+ x y)
777))))

20

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

3 - Funes

3.1 - Funes Recursivas


Uma funo recursiva uma funo que se refere a si prpria. A idia consiste em
utilizar a prpria funo que estamos a definir na sua definio.
Em todas as funes recursivas existe:
!
!

Um passo bsico (ou mais) cujo resultado imediatamente conhecido.


Um passo recursivo em que se tenta resolver um subproblema do problema
inicial.

Se analisarmos a funo fatorial, o caso bsico o teste de igualdade a zero (zerop n), o
resultado imediato 1, e o passo recursivo (* n (fact (- n 1))).
Geralmente, uma funo recursiva s funciona se tiver uma expresso condicional, mas
no obrigatrio que assim seja. A execuo de uma funo recursiva consiste em ir
resolvendo subproblemas sucessivamente mais simples at se atingir o caso mais simples
de todos, cujo resultado imediato. Desta forma, o padro mais comum para escrever
uma funo recursiva :
!
!

Comear por testar os casos mais simples.


Fazer chamadas (ou chamada) recursivas com subproblemas cada vez mais
prximos dos casos mais simples.

Dado este padro, os erros mais comuns associados s funes recursivas so,
naturalmente:
!
!

No detectar os casos simples


A recurso no diminuir a complexidade do problema.

No caso de erro em funo recursiva, o mais usual a recurso nunca parar. O nmero de
chamadas recursivas cresce indefinidamente at esgotar a memria (stack), e o programa
gera um erro. Em certas linguagens (Scheme) e implementaes do Common Lisp, isto
no assim, e pode nunca ser gerado um erro. A recurso infinita o equivalente das
funes recursivas aos ciclos infinitos dos mtodos iterativos do tipo while-do e repeatuntil.
Repare-se que uma funo recursiva que funciona perfeitamente para os casos para que
foi prevista pode estar completamente errada para outros casos. A funo fact um
exemplo. Quando o argumento negativo, o problema torna-se cada vez mais complexo,
cada vez mais longe do caso simples. (fact -1) => (fact -2) => (fact -3) =>

21

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Exerccio 10
Considere uma verso extremamente primitiva da linguagem Lisp, em que as nicas
funes numricas existentes so zerop e duas funes que incrementam e decrementam
o seu argumento em uma unidade, respectivamente, 1+ e 1-. Isto implica que as
operaes >, <, = e similares no podem ser utilizadas. Nesta linguagem, que passaremos
a designar por nanoLisp, abreviadamente nanoLisp , defina a funo menor, que recebe
dois nmero inteiros positivos e determina se o primeiro argumento numericamente
inferior ao segundo.
Exerccio 11
Defina a operao igual? que testa igualdade numrica de inteiros positivos na
linguagem nanoLisp .
Exerccio 12
At ao momento, a linguagem nanoLisp apenas trabalha com nmeros inteiros positivos.
Admitindo que as operaes 1+, 1- e zerop tambm funcionam com nmeros negativos,
defina a funo negativo que recebe um nmero inteiro positivo e retorna o seu
simtrico. Assim, pretendemos obter: (negativo 3) => -3.
Exerccio 13
Agora que a linguagem nanoLisp pode tambm trabalhar com nmeros inteiros
negativos, defina o predicado positivo?, que recebe um nmero e indica se ele
positivo ou no.
Exerccio 14
Defina o teste de igualdade de dois nmeros na linguagem nanoLisp contemplando a
possibilidade de trabalhar tambm com nmeros inteiros negativos.
Exerccio 15
Defina a funo simtrico de um nmero qualquer na linguagem nanoLisp .
Exerccio 16
possvel definir a soma de dois nmeros inteiros positivos em nanoLisp , i.e., apenas
recorrendo s funes 1+ e 1- que somam e subtraem uma unidade, respectivamente.
Defina a operao soma.
Exerccio 17

22

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Generalize a funo de soma de modo a poder receber nmeros inteiros positivos e
negativos.
Exerccio 18
Do mesmo modo que a soma pode ser definida exclusivamente em termos de sucessor 1+
e predecessor 1-, a multiplicao pode ser definida exclusivamente em termos da soma.
Defina a funo mult que recebe dois nmero e os multiplica usando a funo soma.

3.2 Depurao de funes


Em Lisp, possvel analisar as chamadas s funes atravs da forma especial trace. Ela
recebe o nome das funes que se pretendem analisar e altera essas funes de forma a
que elas escrevam no terminal as chamadas com os respectivos argumentos em cada
chamada, e os valores retornados. Esta informao extremamente til para a depurao
das funes.
Para se parar a depurao de uma funo, usa-se a forma especial untrace, que recebe o
nome da funo ou funes de que se pretende tirar o trace.
Se se usar a forma especial trace sem argumentos ela limita-se a indicar quais as
funes que esto em trace. Se se usar a forma especial untrace sem argumentos, so
retiradas de trace todas as funes que estavam em trace.
Exerccio 19
Experimentar o trace do fact.

3.3 Funes de ordem superior


Vimos que as funes permitem-nos dar um nome a um conjunto de operaes e trat-lo
como um todo. Muitas vezes, porm, h um padro que se repete, variando apenas uma
ou outra operao. Por exemplo, consideremos uma funo que soma os quadrados de
todos os inteiros entre a e b,

(defun soma-quadrados (a b)
(if (> a b)
0
(+ (quadrado a) (soma-quadrados (1+ a) b))))
> (soma-quadrados 1 4)
30

Consideremos agora uma outra funo que soma as razes quadradas de todos os inteiros
entre a e b,

(defun soma-raizes (a b)
23

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


(if (> a b)
0
(+ (sqrt a) (soma-raizes (1+ a) b))))
> (soma-raizes 1 4)
6.146264369941973

Em ambas as funes existe uma soma de expresses matemticas entre dois limites, por
. O somatrio uma abstrao matemtica para
exemplo, existe um somatrio
uma soma de nmeros. Dentro do somatrio possvel colocar qualquer operao
matemtica relativa ao ndice do somatrio. Esse ndice varia desde o limite inferior at
ao limite superior.
Para que se possa definir o processo do somatrio na nossa linguagem de programao
ela deve ser capaz de fazer abstrao sobre as prprias operaes a realizar, e deve poder
us-las como parmetros do processo. O padro a executar seria qualquer coisa do estilo:
(defun soma-??? (a b)
(if (> a b)
0
(+ (aplica-??? a) (soma-??? (1+ a) b))))
O smbolo ??? representa a operao a realizar dentro do somatrio, e que pretendemos
transformar num parmetro.
Em Lisp, para se aplicar uma funo que o valor de um argumento, usa-se a funo
funcall, que recebe essa funo e os seus parmetros atuais. Para se indicar que
pretende-se passar a funo associada a um determinado smbolo, usa-se a forma especial
function.
> (funcall (function 1+) 9)
10
> (defun teste (f x y) (funcall f x y))
teste
> (teste (function +) 1 2)
3
Deste modo pode-se escrever a implementao do nosso somatrio:
(defun somatorio (fun a b)
(if (> a b)
0
(+ (funcall fun a) (somatorio fun (1+ a) b))))
Pode-se testar a funo para o exemplo anterior.
> (somatorio (function quadrado) 1 4)
30

24

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Como se v, a funo somatorio representa a abstrao associada ao somatrio
matemtico. Para isso, ela recebe uma funo como argumento e aplica-a aos sucessivos
inteiros includos no somatrio.
As funes que recebem e manipulam outras funes so designadas funes de ordem
superior.
Exerccio 20
Repare-se que, tal como a funo somatrio, podemos escrever a abstrao
correspondente ao produtrio (tambm designado piatrio)
. Esta abstrao
corresponde ao produto dos valores de uma determinada expresso para todos os inteiros
de um intervalo. Escreva uma funo Lisp que a implemente.
Exerccio 21
Escreva a funo fatorial usando o produtrio.

3.4 Lambda
Se voc somente deseja criar uma funo temporria e no deseja perder tempo dandolhe um nome, lambda justamente o que voc precisa. Lambda pode ser vista como uma
funo sem nome. A sintaxe da lambda igual da defun, mas em que se omite o nome.
> ((lambda (z) (+ z 3)) 2)
5
> (defun teste (fun x y) (funcall fun x y))
> (teste (function (lambda (x y) (* x y))) 2 2)
4

As lambdas so a essncia do Lisp. A qualquer funo corresponde uma lambda. Na


realidade, a forma especial defun no faz mais do que criar uma lambda com os
parmetros e o corpo da funo e associ-la ao nome da funo que se est a definir.
Quando a forma especial function recebe um nome (um smbolo) ela devolve a lambda
associada a esse nome.
A designao de lambda ( ) deriva duma rea da matemtica que se dedica ao estudo dos
conceitos de funo e de aplicao de funo, e que se designa por clculo- . O clculo uma ferramenta muito utilizada para estudar a semntica das linguagens de
programao.

3.5 Variveis Locais


Imagine-se que pretendemos escrever uma funo que calcula a seguinte expresso:
. Em Lisp, temos a seguinte traduo:
(defun f (x y)

25

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


(+ (* (quadrado (+ 1 (* (quadrado x) y))) x)
(* (+ 1 (* (quadrado x) y)) y)))
Como se v, a expresso (+ 1 (* (quadrado x) y)) aparece repetida duas vezes. Isto,
alm de dificultar a leitura da funo torna-a menos eficiente pois aquela expresso vai
ter de ser calculada duas vezes.
Quase todas as linguagens de programao fornecem os meios para se criarem variveis
locais, temporrias, para guardarem resultados parciais que vo ser utilizados em outros
stios. Em Lisp, isso pode ser obtido definindo funes intermdias:
(defun f (x y)
(f* x y (+ 1 (* (quadrado x) y))))
(defun f* (x y temp)
(+ (* (quadrado temp) x)
(* temp y)))

Mas como j vimos, no h necessidade de se definir uma funo f* no ambiente pois


podemos usar as lambdas diretamente.
(defun f (x y)
((lambda (temp)
(+ (* (quadrado temp) x)
(* temp y)))
(+ 1 (* (quadrado x) y))))
Repare-se que dentro do corpo da lambda h referncias quer aos parmetros da lambda
(temp) quer aos parmetros da funo f em que a lambda est inserida.
Uma vez que no muito conveniente separar os valores das variveis, Lisp providencia
uma forma especial designada let que convertida para uma lambda. A sua sintaxe :
(let ((var-1 exp-1)
(var-2 exp-2)
(var-n exp-n))
corpo)
Quando se avalia um let, cada smbolo var-i associado ao valor da expresso
correspondente exp-i (em paralelo) e em seguida o corpo avaliado como se as
referncias a var-i estivessem substitudas pelos valores correspondentes de exp-i. Esta
forma especial absolutamente equivalente a escrever:
((lambda (var-1 var-2 ...var-n)
corpo)
exp-1exp-2 ...exp-n)

26

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Embora equivalentes, a utilizao da forma let mais fcil de ler. Este gnero de formas
especiais se limita a ser uma traduo mais agradvel para outras formas especiais e so
designadas por acar sinttico. O let aucar sinttico para uma lambda.
Exerccio 22
Usando o let, reescreva a funo f anterior.
Exerccio 23
Qual o valor das seguintes expresses:
a) > (let ((x 10))
(+ (let ((x 20))
(+ x 5))
(+ x 2)))
b) > (let ((x 10))
(+ (let ((x 11) (y (+ x 4)))
(+ y x))
(+ x 2)))

3.6 Funes Locais


Tal como se podem criar variveis locais com a forma especial let, tambm possvel
criar funes locais com a forma especial flet. A sua sintaxe extremamente parecida
com a do let, s que o valor de cada varivel a definio de uma funo.
A ttulo de exemplo, estude-se a seguinte definio:
(defun teste (x)
(flet ((f-local1 (y) (+ x y))
(f-local2 (z) (* x z))
(f-local3 (x) (+ x 2)))
(+ (f-local1 x) (f-local2 x) (f-local3 x))))
> (teste 2)
12
> (f-local1 2)
Error: Undefined function F-LOCAL1

As funes f-local1, f-local2 e f-local3 so locais funo teste, sendo


estabelecidas a cada aplicao desta funo. Tal como as variveis do let, as funes
locais de um flet no se podem referir umas s outras, pois so avaliadas em paralelo.
Alm disso, tambm no se podem referir a si prpias, impedindo a criao de funes
locais recursivas.
27

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Atendendo a que a maioria das vezes as funes que definimos so recursivas,
independentemente de serem locais ou no, interessa possuir um meio de o podermos
fazer. A forma especial labels providencia esta possibilidade. A sua sintaxe igual do
flet, mas a sua semntica ligeiramente diferente. Para o flet, o mbito do nome das
funes definidas apenas o corpo do flet. Para o labels, esse mbito extendido
prpria forma especial. Isto permite que se possam definir funes locais recursivas ou
mutuamente recursivas.
Observe o seguinte exemplo:
(defun teste (x)
(labels ((f-local1 (y) (if (zerop y)
1
(* y (f-local1 (- y 1))))
)
(f-local2 (z) (* x z))
(f-local3 (x) (+ x 2)))
(+ (f-local1 x) (f-local2 x) (f-local3 x))))
Para f-local1 calculado o fatorial do valor associado a x.
> teste 2
10

28

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

4 mbito e Durao

4.1 mbito de uma referncia


Designa-se por mbito de uma referncia, a zona textual em que ela pode ser
corretamente referida. Assim, o mbito de um parmetro de uma lambda a zona textual
correspondente ao corpo da funo. Isto implica que qualquer parmetro da lambda pode
ser referido dentro desse corpo, mas no fora dele.
> ((lambda (z) (+ z z)) 3)
6
> (+ z z)
Error: Unbound variable Z
Uma vez que o mbito de um parmetro o corpo da lambda correspondente, possvel
escrever:
> ((lambda (z) ((lambda (w) (+ w z)) 3) 4)
7
Reescrevendo o exemplo usando o let, temos
> (let ((z 4))
(let ((w 3))
(+ w z)))
7
Neste exemplo, cada lambda (ou cada let) estabelece um valor para uma varivel.
Quando se encontra uma referncia a uma varivel, o seu valor dado pela ligao
correspondente ao contexto menor. Se no existe qualquer ligao em nenhum dos
contextos, a varivel diz-se no ligada. A avaliao de variveis no ligadas produz um
erro.
Quando uma mesma varivel aparece ligada repetidamente em contextos sucessivos, a
ligao mais ``interior'' obscurece todas as ``exteriores''. Isto no quer dizer que as
ligaes exteriores sejam destrudas. Elas so apenas localmente substitudas durante a
avaliao do corpo mais interior. Assim, temos o seguinte exemplo:
> (let ((x 10))
(+ (let ((x 20))
x)
x))
30
Diz-se que uma referncia de mbito lxico quando ela s pode ser corretamente
referida dentro da regio textual da expresso que a criou.
Diz-se que uma referncia de mbito vago (ou indefinido) quando ela pode ser
corretamente referida a partir de qualquer regio do programa.
29

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

Exerccio 24
Que tipo de mbito possui uma varivel de um let? Que tipo de mbito possui o nome
de uma funo?

4.2 Durao de uma referncia


Designa-se por durao de uma referncia o intervalo de tempo durante o qual ela pode
ser corretamente referida.
Diz-se que uma referncia de durao dinmica quando s pode ser corretamente
referida no intervalo de tempo que decorre durante a avaliao da expresso que a criou.
Diz-se que uma referncia de durao vaga (ou indefinida) quando pode ser
corretamente referida em qualquer instante aps a avaliao da expresso que a criou.
Em Pascal, os parmetros de uma funo ou procedimento tm mbito lxico e durao
dinmica. A ligao dos parmetros formais aos parmetros atuais existe apenas durante
a execuo da funo ou procedimento.
Em Scheme ou Common Lisp, os parmetros das lambdas tm mbito lxico e durao
vaga. Isto implica que possvel aceder a uma varivel mesmo depois de a funo que a
criou ter terminado, desde que essa varivel seja acedida dentro da regio textual dessa
funo.
A ttulo de exemplo, se tentarmos escrever a funo que determina o mximo de uma
funo numrica mas de forma a que ela possa receber uma tolerncia como parmetro,
podemos ser conduzidos a qualquer coisa do gnero:
(defun maximo-func (func a b tol)
(acumulatorio (function max)
func
(funcall func a)
a
(function (lambda (x) (+ x tol)))
b))

Repare-se que neste exemplo a funo que estabelece o incremento refere-se varivel
livre tol. Uma das capacidades fundamentais das lambdas a sua referncia a variveis
livres. Uma varivel diz-se livre numa lambda quando no um dos parmetros da
lambda onde referida.
Quando se aplica uma lambda aos seus argumentos, os parmetros tomam como valor os
argumentos correspondentes, enquanto que as variveis livres tomam como valor o valor
da primeira varivel igual no contexto em que a lambda definida. por esse motivo que
quando a lambda que realiza o incremento aplicada a um nmero, ela sabe qual o valor
30

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


correto de tol. Ele dado pelo contexto lxico (e.x. textual) em que a lambda foi
definida.
Exerccio 25
Analise os seguintes casos:
a) ((lambda (x) (+ x y)) 10)
b) (let ((y 5)) ((lambda (x) (+ x y)) 10))

31

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

5 Dados
Em todos os exemplos anteriores, so apresentadas funes essencialmente numricas.
Os nmeros so um exemplo dos dados que os procedimentos podem usar e produzir.
Neste ponto sero apresentados outros tipos de dados que se podem utilizar.

5.1 - tomos
Os nmeros so um dos elementos primitivos do Lisp. Os smbolos (nomes de funes e
variveis) so outro dos exemplos. Estes elementos dizem-se atmicos, pois no podem
ser decompostos.
Para se testar se um elemento atmico pode-se usar a funo atom:
> (atom 1)
T

Exerccio 26
Ao testar se o smbolo xpto atmico, escreve-se a expresso (atom xpto) e recebe-se
um erro. Explique o que se passa.
Para que o Lisp possa considerar um smbolo por si s, e.x., sem o considerar uma
varivel, preciso usar a forma especial quote, que devolve o seu argumento sem o
avaliar.
> (quote xpto)
XPTO
> (atom (quote xpto))
T

Existem vrias funes para se testar a igualdade de elementos primitivos. Como j se


viu, a igualdade de nmeros dada pela funo =. Esta funo compara nmeros de todos
os tipos.
> (= 1 1)
t
> (= 1 1.0)
t

Em Lisp, existe unicidade de smbolos, e.x., dados dois smbolos com o mesmo nome,
eles representam necessariamente o mesmo objeto, ou seja, o mesmo espao da memria
do computador. Isto permite que a comparao entre dois smbolos possa ser feita
testando se eles representam o mesmo espao, e.x., se apontam para a mesma zona da
memria. A funo eq realiza essa operao.
> (eq (quote a) (quote a))

32

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


t
> (eq (quote a) (quote b))
nil

Para se testar smbolos e nmeros do mesmo tipo existe uma outra funo designada eql.
> (eql (quote a) (quote a))
t
> (eql 111111111111111111111111111111
111111111111111111111111111111)
t
> (eql 1 1.0)
nil

5.2 Combinao de dados


Para se combinar dados, preciso que a linguagem possua uma ``cola'' que permita
agrupar esses dados. Em Lisp, essa ``cola'' implementada pela funo cons.
A funo cons cria um novo objeto que consiste na aglomerao de dois outros objetos,
argumentos do cons. O cons para o Lisp o mesmo que as tabelas (arrays) e estruturas
(records, structs) so para as outras linguagens como Pascal ou C.
> (cons 1 2)
(1 . 2)
> (cons (cons 1 2) 3)
((1 . 2) . 3)

Dada uma combinao de objetos (um ``cons'') podemos obter o primeiro elemento da
combinao usando a funo car e o segundo usando a funo cdr.
> (car (cons 1 2))
1
> (cdr (cons 1 2))
2

Note-se que aplicar o car ou o cdr a um cons no destri esse cons. O cons de dois
objetos designado um par com ponto (dotted pair). Como natural, um cons no um
objeto atmico:
> (atom (cons 1000 2000))
nil

Note-se que para combinar dois objetos quaisquer necessrio arranjar espao na
memria para indicar qual o primeiro objeto e qual o segundo. a funo cons que
arranja esse espao. De cada vez que ela chamada, mesmo que seja para juntar os

33

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


mesmos objetos, ela arranja um novo espao de memria. Isto implica que a funo eq
sempre falsa para o cons.
> (eq (cons 1 2) (cons 1 2))
nil

J a funo equal teste se dois objetos (com relao ao seu contedo) so iguais.
> (equal (cons 1 2) (cons 1 2))
t
> (equal (cons (cons 1 2) (cons 2 3))
(cons (cons 1 2) (cons 2 3)))
t

5.3 Abstrao de dados


A abstrao de dados uma forma de aumentar a modularidade. Se decidirmos
implementar nmeros racionais, teremos de pensar em combinar dois nmeros--o
numerador e o denominador, e de os tratar como um todo. Se no fosse possvel
considerar aquela combinao de nmeros como uma abstrao (um racional), toda a sua
utilizao seria extremamente difcil. Por exemplo, para se somar dois nmeros racionais,
seria necessrio usar uma operao para o clculo do numerador, e outra operao para o
clculo do denominador, em vez de se pensar numa operao genrica, soma-racional,
que receberia dois argumentos-- dois racionais--e calcularia um terceiro nmero--um
racional.
Para nos abstrairmos da complexidade de um nmero racional, devemos definir funes
que os manipulam internamente. Podemos comear por definir uma funo que constri
um nmero racional a partir do numerador e do denominador.
(defun racional (numerador denominador)
(cons numerador denominador))

Para sabermos qual o numerador ou o denominador de um dado nmero racional


podemos definir:
(defun numerador (racional)
(car racional))

(defun denominador (racional)


(cdr racional))

Assim, j j podemos escrever a funo que calcula a soma de dois racionais, usando a
frmula

.
34

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


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

Para simplificar a escrita de racionais, podemos definir uma funo que escreve um
racional de acordo com uma conveno qualquer.
(defun escreve-racional (racional)
(format t "~a/~a" (numerador racional)
(denominador racional)))

Agora, j podemos calcular a seguinte expresso:


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

Obs: Veja captulo 6: Impresso


Exerccio 27
Defina as restantes funes do tipo abstrato de informao racional: -racional,
*racional, e /racional.
Como se v, tratamos um nmero racional como um s objeto, e separamos a parte do
programa que usa os racionais da parte que os implementa como pares de inteiros. Esta
tcnica designa-se por abstrao de dados.
A abstrao a melhor maneira de lidar com a complexidade. A abstrao de dados
permite-nos isolar a utilizao dos dados do modo como eles esto implementados,
atravs da utilizao de barreiras de abstrao. Essas barreiras consistem em limitar a
utilizao dos dados a um pequeno conjunto de funes (racional, numerador e
denominador) que escondem a maneira como eles esto implementados. Ao utilizador de
um dado tipo de dados, apenas se diz quais as funes que ele pode usar para os
manipular, e no qual o funcionamento das funes que implementam aquele tipo de
dados.
Seguindo esta metodologia, se precisarmos testar a igualdade de racionais, devemos
escrever uma funo que o faa usando apenas as funes de manipulao de racionais,
i.e., racional, numerador e denominador:
(defun =racional (r1 r2)
(and (= (numerador r1) (numerador r2))
(= (denominador r1) (denominador r2))))

Exerccio 28

35

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


A funo que compara dois racionais no funciona corretamente para todos os casos.
Assim,
> (=racional (racional 4 6) (racional 4 6))
t
> (=racional (racional 4 6) (racional 2 3))
nil

Qual o problema? Como que se pode resolver?


Exerccio 29
Escreva uma funo que calcule o maior divisor comum entre dois nmeros. Para isso,
use o algoritmo de Euclides que diz que se r o resto da diviso de a por b, ento o maior
divisor comum entre a e b tambm o maior divisor comum entre b e r:
mdc(a,b)=mdc(b,r). Como natural, quando o resto zero, o maior divisor comum o
prprio b.
Exerccio 30
Empregue o mtodo de Euclides para reescrever a funo racional de modo a s
construir nmeros na forma reduzida.

5.4 - Tipos Abstratos de Informao


A teoria dos tipos abstratos de informao diz que o conceito fundamental para a
abstrao de dados a definio de uma interface entre a implementao dos dados e a
sua utilizao. Essa interface constituda por funes que se podem classificar em
categorias: construtores, seletores, reconhecedores e testes. Estas funes so definidas
em termos dos objetos mais primitivos que implementam o tipo de dados que se quer
definir.
Os construtores so as funes que criam um objeto composto a partir dos seus elementos
mais simples. Por exemplo, a funo racional um construtor para o tipo racional.
Os seletores so as funes que recebem um objeto composto e devolvem as suas partes.
As funes numerador e denominador so exemplos de seletores.
Os reconhecedores so as funes que reconhecem certos objetos especiais do tipo de
dados que se est a definir. A funo zerop um reconhecedor para o tipo nmero do
Lisp.
Finalmente, os testes so funes que comparam objetos do tipo que se est a definir. A
funo =racional um exemplo de uma funo desta categoria. Como se pode verificar
pela funo =racional, por vezes, os testes so implementados usando os prprios
seletores.

36

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Para que abstrao de dados seja corretamente realizada, fundamental definir o
conjunto de construtores, seletores, reconhecedores e testes. Todos os programas que
pretenderem utilizar aquele tipo de dados so obrigados a usar apenas aquelas funes.
Isso permite que se possa alterar a implementao do tipo de dados sem afetar os
programas que o utilizam.
A implementao de estruturas de dados complexas s corretamente realizada quando
se segue esta metodologia com extremo rigor.
Exerccio 31
Defina o teste >racional.
Quando um tipo abstrato de informao tem de interagir com um utilizador, quer para lhe
pedir uma descrio de um elemento do tipo, quer para lhe apresentar uma descrio de
um elemento do tipo, usa os denominados transformadores de entrada/sada. A funo
escreve-racional um exemplo de um transformador de sada para o tipo racional. Ela
limita-se a a apresentar uma representao compreensvel de um nmero racional. O
transformador de entrada realiza a operao inversa, i.e., constri um elemento do tipo
abstrato a partir de uma representao fornecida.

37

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

6 Entrada e sada

6.1 Leitura de dados a partir do teclado


Esta funo no recebe nenhum argumento. O prprio comando no avalia sua entrada,
apenas recebe os dados que o usurio digitar no teclado (sempre seguidos de <enter>).
Geralmente vem combinado com outras funes.
Definindo uma funo retorna a diferena de dois valores lidos
(defun diferenca ()
(setf x (read))
(setf y (read))
(print (- x y)))
Avaliao da funo diferena
>(diferena)
3
4
-1
-1
Definindo uma funo retorna a mdia de quadro valores lidos a partir do teclado
(defun mediaquatro ()
;a funo no recebe nenhum argumento.
(/ (+ (read)
;soma quatro valores e divide-os por 4.
(read)
(read)
(read)) 4))
Avaliao da funo mediaquatro
>(mediaquatro)
2
;Digite quatro nmeros,
4
;aps cada nmero tecle Enter.
6
8
5
Para chamar esta funo, basta digitar (mediaquatro) seguido por seus parmetros
(recebidos pelos comandos read dentro do corpo da funo).
Os quatro valores passados para a funo so somados e divididos por 4,
resultando em 5.

38

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

6.1 Impresso de dados


Algumas funes podem provocar uma sida. A mais simples print, que imprime o seu
argumento e ento o retorna.
> (print 3)
3
3

O primeiro 3 acima foi impresso, o segundo retornado.


Se voc deseja um output mais complexo, voc necessita utilizar format:
>(format t "An atom: ~S~%and a list: ~S~%and an integer:~D~%"
nil (list 5) 6)
An atom: NIL
and a list: (5)
and an integer: 6

O primeiro argumento a format ou t, ou NIL ou um arquivo.


!
!
!

T especifica que a sada deve ser dirigida para o terminal,


NIL especifica que no deve ser impresso nada, mas que format deve retornar um
string com o contedo ao invs,
uma referncia a um arquivo especifica o arquivo para onde a sada vai ser
redirigida.

O segundo argumento um template de formatao, o qual um string contendo


opcionalmente diretivas de formatao, de forma similar Linguagem "C": "An atom:
~S~%and a list: ~S~%and an integer:~D~%"

Todos os argumentos restantes devem ser referenciados a partir do string de formatao.


!

As diretivas de formatao do string sero repostas por LISP por caracteres


apropriados com base nos valores dos outros parmetros a que eles se referem e
ento imprimir o string resultante.
Format sempre retorna NIL, a no ser que seu primeiro argumento seja NIL, caso
em que no imprime nada e retorna o string resultante.

No exemplo acima, h trs diretivas de formatao: ~S, ~D e ~%.:


!
!
!
!

A primeira,~S, aceita qualquer objeto LISP e substituda por uma representao


passvel de ser impressa deste objeto (a mesma produzida por print).
A segunda, ~D, s aceita inteiros.
A terceira, ~%, no aceita nada. Sempre reposta por uma quebra de linha.
Outra diretiva til ~~, que substituda por um simples ~.

39

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Algumas Derivaes de Print
A funo print possui algumas funes derivadas que tambm realizam a
impresso dos dados, mas de uma forma diferente. Entre elas, podemos citar:
!

terpri imprime uma linha em branco (no recebe nenhum argumento).

prin1 imprime o dado na linha corrente e inicia uma nova linha.

princ imprime o dado na linha corrente e no inicia nova linha.

print inicia nova linha e imprime o dado.

Exemplo:
(defun mediaquatro (primeiro segundo terceiro quarto)
(princ "A mdia de")
(terpri) ; salto de linha
(princ primeiro)
(princ " ")
(princ segundo)
(princ " ")
(princ terceiro)
(princ " ")
(princ quarto)
(terpri)
(princ "")
(/ (+ primeiro
segundo
terceiro
quarto)
4))
MEDIAQUATRO
> (mediaquatro 2 4 6 8)
A mdia de
2468

40

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

7 Listas
As listas so um dos componentes fundamentais da linguagem Lisp. O nome da
linguagem , alis, uma abreviao de ``list processing''. Como iremos ver, as listas
constituem uma estrutura de dados extremamente flexvel.

7.1 Operaes sobre listas


Em Lisp, quando o segundo elemento de um cons outro cons, o Lisp escreve o
resultado sob a forma de uma lista:
> (cons 1 (cons 2 (cons 3 (cons 4 5))))
(1 2 3 4 . 5)

Se o ltimo elemento a constante nil, o Lisp considera que ela representa a lista vazia,
pelo que escreve:
> (cons 1 (cons 2 (cons 3 (cons 4 nil))))
(1 2 3 4)

Esta notao designa-se de lista e esta que o Lisp usa para simplificar a leitura e a
escrita. Uma lista ento uma sequncia de elementos. Nesta ptica, a funo car
devolve o primeiro elemento de uma lista, enquanto a funo cdr devolve o resto da lista.
A funo cons pode ser vista como recebendo um elemento e uma lista e devolve como
resultado uma nova lista correspondente juno daquele elemento no princpio daquela
lista. Segundo esta abordagem, a funo cons um construtor do tipo abstrato de
informao lista, enquanto as funes car e cdr so seletores.
Uma lista vazia uma sequncia sem qualquer elemento e pode ser escrita como nil ou
ainda mais simplesmente (). A lista vazia o elemento mais primitivo do tipo lista. nil
o construtor do elemento primitivo. Pode-se testar se uma lista vazia com a funo
null. A funo null , portanto, um reconhecedor do tipo lista.
> (null nil)
t
> (null (cons 1 (cons 2 nil)))
nil

Exerccio 32
Escreva uma funo que calcula uma lista de todos os nmeros desde a at b.
> (enumera 1 10)
(1 2 3 4 5 6 7 8 9 10)

Embora as listas no sejam mais do que uma estruturao particular de clulas cons,
podendo por isso ser acedidas com as funes car e cdr, considerado melhor estilo de
41

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


programao usar as funes 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 pelo seu equivalente endp.
> (first (enumera 1 10))
1
> (rest (enumera 1 10))
(2 3 4 5 6 7 8 9 10)

Exerccio 33
Escreva uma funo que filtra uma lista, devolvendo uma lista com os elementos que
verificam um determinado critrio. Utilize-a para encontrar os nmeros pares entre 1 e
20.
> (filtra (function par?) (enumera 1 20))
(2 4 6 8 10 12 14 16 18 20)

Obs.: lembre-se de definir a funo par?.


Esta funo j existe em Lisp e denomina-se remove-if-not.
Quando se pretendem construir listas pode-se usar tambm a funo list. Esta funo
recebe qualquer nmero de argumentos e constri uma lista com todos eles.
> (list 1 2 3 4)
(1 2 3 4)
> (first (list 1 2 3 4))
1
> (rest (list 1 2 3 4))
(2 3 4)
> (list 1 2 (list 10 20) 3 4)
(1 2 (10 20) 3 4)

Como se v possvel construir listas dentro de listas. Lisp permite tambm a construo
de listas diretamente no avaliador. Idealmente, bastaria escrever (1 2 3 ...), s que isso
seria avaliado segundo as regras de avaliao das combinaes. O nmero 1 seria
considerado um operador e os restantes elementos da lista os operandos. Para evitar que
uma lista possa ser avaliada podemos usar a forma especial quote, que devolve o seu
argumento sem o avaliar.
> (quote (1 . (2 . (3 . nil))))
(1 2 3)
> (quote (1 2 3 4 5 6 7 8 9 10))
(1 2 3 4 5 6 7 8 9 10)
> (filtro (function par?) (quote (1 2 3 4 5 6 7 8 9 10)))
(2 4 6 8 10)

Uma vez que as formas especiais quote e function so bastante utilizadas, Lisp fornece
um meio de se simplificar a sua utilizao. Se dermos ao avaliador uma expresso
42

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


precedida por uma plica (quote em Ingls), como se tivessemos empregue a forma
especial quote. A substituio feita durante a leitura da expresso. Do mesmo modo, se
precedermos uma funo ou uma lambda por #' (cardinal-plica) como se tivssemos
empregue a forma especial function. 'exp equivalente a (quote exp), enquanto que
#'exp 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)

Exerccio 44
O que que o avaliador de Lisp devolve para a seguinte expresso: (first ''(1 2
3))?
Como natural, as operaes car e cdr podem ser encadeadas:
> (car
1
> (cdr
(2 3)
> (car
2
> (car
3

'(1 2 3))
'(1 2 3))
(cdr '(1 2 3))
(cdr (cdr '(1 2 3))))

Dado que aquele gnero de expresses muito utilizado em Lisp, foram compostas as
vrias combinaes, e criaram-se funes do tipo (caddr exp), que correspondem a
(car (cdr (cdr exp))). O nome da funo indica quais as operaes a realizar. Um
``a'' representa um car e um ``d'' representa um cdr.
> (cadr '(1 2 3))
2
> (cdddr '(1 2 3))
nil

7.2 Funes teis


Exerccio 35
Escreva uma funo n-esimo que devolva o n-simo elemento de uma lista. Note que o
primeiro elemento da lista corresponde a n igual a zero.
Esta funo j existe em Lisp e denomina-se nth.
Exerccio 36

43

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Escreva uma funo muda-n-esimo que recebe um nmero n, uma lista e um elemento, e
substitui o n-simo elemento da lista por aquele elemento. Note que o primeiro elemento
da lista corresponde a n igual a zero.
Exerccio 37
Escreva uma funo que calcula o comprimento de uma lista, i.e., determina quantos
elementos ela tem.
Esta funo j existe em Lisp e denomina-se length.
Exerccio 38
Escreva uma funo que recebe um elemento e uma lista que contm esse elemento e
devolve a posio desse elemento na lista.
Exerccio 39
Escreva uma funo que calcula o nmero de tomos que uma lista (possivelmente com
sublistas) tem.
Exerccio 40
Escreva uma funo junta que recebe duas listas como argumento e devolve uma lista
que o resultado de as juntar uma frente da outra.
Esta funo j existe em Lisp e denomina-se append.
Exerccio 41
Defina uma funo inverte que recebe uma lista e devolve outra lista que possui os
mesmos elementos da primeira s que por ordem inversa.
Esta funo j existe em Lisp e denomina-se reverse.
Exerccio 42
Escreva uma funo designada inverte-tudo que recebe uma lista (possivelmente com
sublistas) e devolve outra lista que possui os mesmo elementos da primeira s que por
ordem inversa, e em que todas as sublistas esto tambm por ordem inversa, i.e.:
> (inverte-tudo '(1 2 (3 4 (5 6)) 7))
(7 ((6 5) 4 3) 2 1)

Exerccio 43

44

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Escreva uma funo mapear que recebe uma funo e uma lista como argumentos e
devolve outra lista com o resultado de aplicar a funo a cada um dos elementos da lista.
Esta funo j existe em Lisp e denomina-se mapcar.
Exerccio 44
Escreva uma funo denominada alisa que recebe uma lista (possivelmente com
sublistas) como argumento e devolve outra lista com todos os tomos da primeira e pela
mesma ordem, i.e.
> (alisa '(1 2 (3 4 (5 6)) 7))
(1 2 3 4 5 6 7)

Exerccio 45
Escreva uma funo membro? que recebe um objecto e uma lista e verifica se aquele
objecto existe na lista.
Esta funo j 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)

Exerccio 46
Escreva uma funo elimina que recebe um elemento e uma lista como argumentos e
devolve outra lista onde esse elemento no aparece.
Esta funo j existe em Lisp e denomina-se remove.
Exerccio 47
Escreva uma funo substitui que recebe dois elementos e uma lista como argumentos
e devolve outra lista com todas as ocorrncias do segundo elemento substitudas pelo
primeiro.
Esta funo j existe em Lisp e denomina-se subst.
Exerccio 48
Escreva uma funo 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)

45

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Esta funo no mantm a anterior ordenao da lista. Se pretendermos preservar a
ordem original, temos de testar se um elemento j existe na lista que estamos a construir e
no na que estamos a analisar.
> (remove-duplicados2 '(1 2 3 3 2 4 5 4 1))
(1 2 3 4 5)

Esta funo j existe em Lisp e denomina-se remove-duplicates.


Exerccio 49
Escreva as funes inversas do cons, car e cdr, designadas snoc, rac e rdc. O snoc
recebe um elemento e uma lista e junta o elemento ao fim da lista. O rac devolve o
ltimo elemento da lista. O rdc devolve todos os elementos da lista menos o primeiro.
A funo snoc j existe em Lisp atravs da combinao das funes append e list.
A funo rac j existe em Lisp atravs da combinao das funes first e last.
A funo rdc j existe em Lisp e denomina-se butlast.
Exerccio 50
As funes todos?, algum?, nenhum? e nem-todos? so predicados que recebem uma
funo e uma lista e verificam, respectivamente, se a funo verdade para todos os
elementos da lista, se verdade para pelo menos um, se no verdade para todos e se no
verdade para pelo menos um. Defina estas funes.
Esta funo j existe em Lisp e denomina-se every.
Esta funo j existe em Lisp e denomina-se some.
Esta funo j existe em Lisp e denomina-se notany.
Esta funo j existe em Lisp e denomina-se notevery.

7.4 Usando as operaes


Quando se pretendem construir listas pode-se usar tambm a funo list. Esta funo
recebe qualquer nmero de argumentos e constri uma lista com todos eles.
> (list 1 2 3 4)
(1 2 3 4)
Retorna o primeiro elemento:
> (first (list 1 2 3 4))

46

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


1
Retorna o resto de uma lista: a lista restante a partir do primeiro
elemento:
> (rest (list 1 2 3 4))
(2 3 4)
Construindo uma lista dentro de outra lista:
> (list 1 2 (list 10 20) 3 4)
(1 2 (10 20) 3 4)

Como se v possvel construir listas dentro de listas. Lisp permite tambm a construo
de listas diretamente no avaliador. Idealmente, bastaria escrever (1 2 3 ...), s que isso
seria avaliado segundo as regras de avaliao das combinaes. O nmero 1 seria
considerado um operador e os restantes elementos da lista os operandos. Para evitar que
uma lista possa ser avaliada podemos usar a forma especial quote, que devolve o seu
argumento sem o avaliar.
> (quote (1 2 3)
(1 2 3)
> (quote (1 2 3 4 5 6 7 8 9 10))
(1 2 3 4 5 6 7 8 9 10)

Uma vez que as formas especiais quote e function so bastante utilizadas, Lisp fornece
um meio de se simplificar a sua utilizao. Se dermos ao avaliador uma expresso
precedida por uma plica (quote em Ingls), como se tivessemos empregue a forma
especial quote. A substituio feita durante a leitura da expresso. Do mesmo modo, se
precedermos uma funo ou uma lambda por #' (cardinal-plica) como se tivssemos
empregue a forma especial function. 'exp equivalente a (quote exp), enquanto que
#'exp equivalente a (function exp).
> '(1 2 3 4 5)
(1 2 3 4 5)
> (remove-if-not #'evenp '(1 2 3 4 5 6))
(2 4 6)

Devolve o n-simo elemento de uma lista. Note que o primeiro elemento da lista
corresponde a n igual a zero.
nth 0 '(1 2 3)
1
Devolve o comprimento de uma lista, i.e., determina quantos elementos ela tem.
length '(1 2 3 4)
4

47

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

Recebe duas listas como argumento e devolve uma lista que o resultado de as juntar
uma frente da outra.
append (list 1 2 3) (list 4 5 6)
(1 2 3 4 5 6)
Inverte uma lista e devolve outra lista que possui os mesmos elementos da primeira s

que por ordem inversa.


reverse (list 1 2 3)
(3 2 1)

Escreva uma funo designada inverte-tudo que recebe uma lista (possivelmente com
sublistas) e devolve outra lista que possui os mesmo elementos da primeira s que por
ordem inversa, e em que todas as sublistas esto tambm por ordem inversa, i.e.:
Recebe uma funo e uma lista como argumentos e devolve outra lista com o resultado
de aplicar a funo a cada um dos elementos da lista.
mapcar (function sqrt) (list 1 2 3)
(1.0 1.4142135623730951 1.7320508075688772)

Recebe um objeto e uma lista e verifica se aquele objeto existe na lista. Esta funo j
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)

Recebe um elemento e uma lista como argumentos e devolve outra lista onde esse
elemento no aparece.
remove 3 (list 12 3 4)
(12 4)
Recebe dois elementos e uma lista como argumentos e devolve outra lista com todas as
ocorrncias do segundo elemento substitudas pelo primeiro.
subst 1 2 (list 2 3 4 3 2 2)
(1 3 4 3 1 1)
Recebe uma lista como argumento e devolve outra lista com todos os elementos da
primeira mas sem duplicados.
remove-duplicates (list 1 1 1 2 2 2 3 3 3 4 4 4)
(1 2 3 4)
Recebe uma lista e devolve uma outra lista sem o ltimo elemento.
48

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

butlast (list 2 3 4)
(2 3)
Recebe uma funo e uma lista e verifica se se a funo verdade para todos os
elementos da lista
every (function zerop) (list 0 1 2)
NIL
every (function zerop) (list 0 0 0)
T
Recebe uma funo e uma lista e verifica se se a funo verdade para pelo menos um
elemento da lista
some (function zerop) (list 0 1 2)
T
some (function zerop) (list 2 3 4)
NIL

7.5 Listas de argumentos


Sendo as listas uma das estruturas bsicas do Lisp, a linguagem permite aplicar funes
directamente a listas de argumentos atravs da funo apply. Esta em tudo idntica
funo funcall, mas em vez de ter os argumentos da funo a aplicar como argumentos
da funo funcall, tem-nos numa lista, i.e.:
(funcall func arg-1 arg-2 ...arg-n) <=>
(apply func (list arg-1 arg-2 ...arg-n))

Na linguagem Common Lisp, a funo apply uma fuso entre a funo funcall e a
funo apply primitiva, pois da forma:
(apply func arg-1 arg-2 ...rest-args)

Nesta aplicao o ltimo argumento rest-args uma lista com os restantes argumentos.
Desta forma, pode-se escrever qualquer das seguintes equivalncias:
(funcall func arg-1 arg-2 ...arg-n) <=>
(apply func (list arg-1 arg-2 ...arg-n))
(apply func arg-1 (list arg-2 ...arg-n))
(apply func arg-1 arg-2 ...(list arg-n))
(apply func arg-1 arg-2 ...arg-n nil))

<=>
<=>
<=>

49

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

7.6 Tipos aglomerados


Um tipo aglomerado um tipo abstrato de informao que composto exclusivamente
pela aglomerao de outros tipos abstratos. O conjunto dos racionais um exemplo pois,
como vimos, um racional no mais do que uma aglomerao de dois inteiros. As
operaes fundamentais de um tipo aglomerado so os seus construtores e seletores,
embora possam existir outras. Como vimos, para um racional, as operaes mais
utilizadas eram o construtor racional e os seletores numerador e denominador, mas
tambm foram definidos alguns testes e os transformadores de entrada/sada.
Os tipos aglomerados so extremamente utilizados. Por este motivo costume as
linguagens de alto nvel fornecerem ferramentas prprias para os tratar. Pascal, por
exemplo, permite defini-los com a forma especial record, enquanto que a linguagem C
usa, para o mesmo efeito, o struct.
Para exemplificarmos a utilizao de tipos aglomerados podemos considerar a definio
de um automvel. Um automvel caracterizado por uma marca, um modelo, um dado
nmero de portas, uma cilindrada, uma potncia, etc. Para simplificar, podemos
considerar s as trs primeiras. O construtor de um objeto do tipo automvel no tem
mais que agrupar as informaes relativas a cada uma daquelas caractersticas. Para isso,
podemos usar a funo list. Assim, criamos o construtor do tipo da seguinte forma:
(defun novo-automovel (marca modelo portas)
(list marca modelo portas))

Os seletores do tipo automvel limitam-se a determinar de que que um dado objeto


daquele tipo composto:
(defun automovel-marca (automovel)
(nth 0 automovel))
(defun automovel-modelo (automovel)
(nth 1 automovel))
(defun automovel-portas (automovel)
(nth 2 automovel))

Estando na posse destas funes, podemos criar um automvel especfico, por exemplo:
> (novo-automovel 'honda 'civic 2)
(honda civic 2)

Dado aquele objeto do tipo automvel, podemos estar interessados em alterar-lhe o


nmero de portas, passando-as de 2 para 4, por exemplo. Contudo, para manipularmos
um tipo abstrato devemos restringirmo-nos s operaes desse tipo. Precisamos, portanto,

50

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


de criar novas operaes que nos permitem modificar um objeto. Para o tipo automvel
poderamos definir:
(defun muda-automovel-marca (automovel nova-marca)
(muda-n-esimo 0 automovel nova-marca))
(defun muda-automovel-modelo (automovel novo-modelo)
(muda-n-esimo 1 automovel novo-modelo))
(defun muda-automovel-portas (automovel novo-portas)
(muda-n-esimo 2 automovel novo-portas))

A funo muda-n-esimo recebia um nmero n, uma lista e um novo elemento, e substitua


o n-simo elemento da lista pelo novo elemento. Esta funo no alterava a lista original,
produzindo uma nova lista. Desta forma, qualquer destas funes do tipo automvel
deixa o automvel a modificar absolutamente inalterado, produzindo um novo
automvel. Por este motivo, estas operaes devem ser vistas como construtores do tipo
automvel, pois elas criam um novo automvel a partir de um outro j existente. Elas no
permitem alterar um automvel j criado.

51

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

8 Programao imperativa
Todas as funes que apresentadas anteriormente realizam operaes muito variadas e
algumas so at relativamente complexas, mas nenhuma afeta os seus argumentos. Elas
limitam-se a produzir novos objetos a partir de outros j existentes, sem alterar estes
ltimos seja de que forma for. At as prprias variveis que introduzimos nas funes e
que se destinavam a guardar valores temporrios no eram mais do que parmetros de
uma lambda, e a sua inicializao correspondia a invocar a lambda com os valores
iniciais como argumentos, sendo por isso inicializadas uma nica vez e nunca
modificadas. Por este motivo, nem sequer foi apresentado nenhum operador de
atribuio, to caracterstico em linguagens como C e Pascal.
Este estilo de programao, sem atribuio, sem alterao dos argumentos de funes, e
em que estas se limitam a produzir novos valores, designado programao funcional.
Neste paradigma de programao, qualquer funo da linguagem considerada uma
funo matemtica pura que, para os mesmos argumentos produz sempre os mesmos
valores. Nunca nada destrudo. Uma funo que junta duas listas produz uma nova lista
sem alterar as listas originais. Uma funo que muda o nmero de portas de um
automvel produz um novo automvel.
A programao funcional tem muitas vantagens sobre outros estilos de programao, em
especial no que diz respeito a produzir programas muito rapidamente e minimizando os
erros. Contudo, tem tambm as suas limitaes, e a sua incapacidade em modificar seja o
que for a maior. A partir do momento em que introduzimos a modificao de objetos,
estamos a introduzir o conceito de destruio. A forma anterior do objeto que foi
modificado deixou de existir, passando a existir apenas a nova forma. A modificao
implica a introduo do conceito de tempo. Os objetos passam a ter uma histria, e isto
conduz a um novo estilo de programao.

8.1 Atribuio
Para se alterar o valor de uma varivel Lisp possui um operador de atribuio. A forma
especial setq recebe uma varivel e um valor e atribui o valor varivel.
> (let ((x 2))
(setq x (+ x 3))
(setq x (* x x))
(setq x (- x 5))
x)
20

Cada vez que se realiza uma atribuio, perde-se o valor anterior que a varivel possua.
Muito embora a forma especial setq, como todas as funes e forma especiais, retorne
um valor, esse valor no possui qualquer interesse. O operador de atribuio serve apenas
para modificar o estado de um programa, neste exemplo, alterando o valor de x. Por este
motivo, a atribuio assemelha-se mais a um comando do que a uma funo. A atribuio
uma ordem que tem de ser cumprida e de que no interessa o resultado. A ordem a
52

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


operao fundamental da programao imperativa, em que um programa composto por
sucessivos comandos. Neste estilo de programao, os comandos funcionam por efeitos
secundrios. O valor de cada comando no tem interesse e muitas vezes nem sequer tem
significado falar dele.

8.2 Sequenciao
A forma especial progn est especialmente vocacionada para este gnero de utilizao.
Ela recebe um conjunto de expresses que avalia sequencialmente retornando o valor da
ltima. Desta forma possvel incluir vrias aes no consequente ou alternativa de um
if, por exemplo.
(if (> 3 2)
(progn
(print 'estou-no-consequente)
(+ 2 3))
(progn
(print 'estou na alternativa)
(* 4 5)))

Algumas das formas especiais do Lisp incluem um progn implcito. Vimos atrs um
exemplo de um let que realizava quatro operaes em sequncia. Isto implica que o let
e, por arrastamento, as lambdas e tudo o que for definido com defun, possuem tambm a
capacidade de realizar operaes em sequncia. O cond est tambm nesta categoria. A
sua sintaxe , na realidade, a seguinte:
(cond (condio-1 expresso-11...expresso-1l)
(condio-2 expresso-21...expresso-2m)
(condio-n expresso-n1...expresso-nk))

O cond testa cada uma das condies em sequncia, e quando uma delas avalia para
verdade, so avaliadas todas as expresses da clusula correspondente sendo devolvido o
valor da ltima dessas expresses.
Usando o cond, o exemplo anterior ficar mais elegante:
(cond ((> 3 2)
(print 'estou-no-consequente)
(+ 2 3))
(t
(print 'estou na alternativa)
(* 4 5)))

A forma especial prog1 semelhante ao progn. Ela recebe um conjunto de expresses


que avalia sequencialmente retornando o valor da primeira. A expresso equivalente ao
exemplo anterior mas utilizando o prog1 ser:
(if (> 3 2)

53

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


(prog1
(+ 2 3)
(print 'estou-no-consequente))
(prog1
(* 4 5)
(print 'estou na alternativa)))

Note-se que a ordem de avaliao das expresses de um prog1 igual de um progn.


Apenas o valor retornado diferente: o primeiro no caso do prog1 e o ltimo no caso
do progn.
A sequenciao tambm suportada por qualquer lambda. Em consequncia, as formas
especiais que implicam a criao de lambdas, como o let e o prprio defun permitem
tambm especificar mais do que uma expresso, sendo estas avaliadas em sequncia e
devolvido o valor da ltima.

8.3 Alterao de dados


A atribuio no est restricta a variveis. tambm possvel alterar o contedo da
maioria dos tipos de dados Lisp. Uma clula cons, por exemplo, pode ser alterada com as
funes rplaca e rplacd, significando, respectivamente ``replace-car'' e ``replacecdr''.
> (let ((x (cons 1 2)))
(rplaca x 3)
x)
(3 . 2)
> (let ((x (cons 1 2)))
(rplacd x 3)
x)
(1 . 3)

Note-se que estas funes so uma forma de atribuio e, como tal, destroem o contedo
anterior da estrutura a que se aplicam. Por este motivo, este gnero de funes diz-se
destrutivo. Na sequncia lgica da conveno usual em Lisp para a definio de
reconhecedores, que terminam sempre com um ponto de interrogao (ou com a letra ``p''
de predicado), deve-se colocar um ponto de exclamao no fim do nome das funes
destrutivas para salientar o seu carcter imperativo.
Exerccio 51
Escreva uma funo junta! (note-se o ponto de exclamao) que recebe duas listas
como argumento e devolve uma lista que o resultado de as juntar destrutivamente uma
frente da outra, i.e., faz a cauda da primeira lista apontar para a segunda.
Esta funo j existe em Lisp e denomina-se nconc.

54

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Exerccio 52
Analise o seguinte exemplo funcional e imperativo:
> (let ((x '(1 2 3)))
(junta x x))
(1 2 3 1 2 3)
> (let ((x '(1 2 3)))
(junta! x x))
(1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 ...

Exerccio 53
Escreva uma funo muda-n-esimo! (note-se o ponto de exclamao) que recebe um
nmero n, uma lista e um elemento, e substitui o n-simo elemento da lista por aquele
elemento. Note que o primeiro elemento da lista corresponde a n igual a zero.
Exerccio 54
Reescreva as operaes do tipo abstrato de informao automvel que alteravam as
caractersticas de um elemento do tipo de forma a torn-las destrutivas.
Quando as operaes de um tipo abstrato alteram um elemento do tipo, essas operaes
so classificadas como modificadores do tipo abstrato. Os modificadores, como caso
especial da atribuio, so muito empregues em programao imperativa. Note-se que os
modificadores possuem todos os problemas da atribuio simples, nomeadamente a
alterao ser destrutiva. Isto levanta problemas quando se testa igualdade em presena de
modificao.
> (let ((x (novo-automovel 'honda 'civic 2)))
(let ((y (muda-automovel-portas x 4)))
(eql x y)))
NIL
> (let ((x (novo-automovel 'honda 'civic 2)))
(let ((y (muda-automovel-portas! x 4)))
(eql x y)))
T

Repare-se que no primeiro exemplo (funcional), o automvel modificado , logicamente,


diferente do automvel original. x e y representam automveis diferentes. No segundo
exemplo (imperativo), a modificao do automvel que x representa realizada sobre
esse prprio automvel, de modo que x e y acabam por representar um mesmo automvel
(modificado). Muito embora esta situao possa ter vantagens, ela permite tambm a
introduo de erros muito subtis e extremamente difceis de tirar. Para dar apenas um
pequeno exemplo, repare-se que o automvel que x representa passou a ter quatro portas
embora uma leitura superficial do cdigo sugira que ele foi criado com apenas duas.
55

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

8.4 Repetio
Para alm dos operadores de atribuio (setq, rplaca, rplacd, etc.) e de sequenciao
(progn, prog1, etc.) a linguagem Common Lisp possui muitas outras formas especiais
destinadas a permitir o estilo de programao imperativa. De destacar so as estruturas de
controle de repetio, tais como o loop, o do, o dotimes e ainda outras adequadas para
iterar ao longo de listas.
O loop a mais genrica de todas as formas de repetio. Ela recebe um conjunto de
expresses que avalia sequencialmente, repetindo essa avaliao em ciclo at que seja
avaliada a forma especial return. Esta ltima recebe uma expresso opcional e termina o
ciclo em que est inserida, fazendo-o devolver o valor daquela expresso.
A seguinte expresso exemplifica um ciclo que escreve todos os nmeros desde 0 at
100, retornando o smbolo fim no final do ciclo.
(let ((n 0))
(loop
(print n)
(setq n (1+ n))
(when (> n 100)
(return 'fim))))

A forma especial do um pouco mais sofisticada que o loop. Ela permite estabelecer
variveis, inicializ-las e increment-las automaticamente, testar condies de paragem
com indicao do valor a retornar e repetir a execuo de cdigo. Se reescrevermos o
exemplo anterior usando a forma especial do, obtemos:
(do ((n 0 (1+ n)))
((> n 100) 'fim)
(print n))

Tal como o loop, a forma especial do pode ser interrompida em qualquer altura com um
return, retornando o valor opcional fornecido com o return.
Apesar do estilo mais utilizado na maioria das linguagens de programao ser o
imperativo, ele muito pouco natural em Lisp.
A falta de naturalidade resulta, por um lado, de os programas em Lisp se decomporem
geralmente em pequenas funes que calculam valores, invalidando uma abordagem
baseada em ciclos de alterao de variveis, tpica da programao imperativa.
Por outro lado, a grande maioria de tipos de dados existentes em Lisp so inerentemente
recursivos, o que dificulta o seu tratamento segundo o estilo imperativo.

56

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Apesar de muito pouco prtico para usar em Lisp, a programao imperativa tem algumas
vantagens, das quais a possibilidade de atribuio a maior (e tambm a mais perigosa).

57

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

58

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

9 Modelos de ambientes
At agora vimos que as variveis eram apenas designaes para valores. Quando se
avaliava uma expresso, as variveis desapareciam, sendo substitudas pelos seus valores.
A partir do momento em que podemos alterar o valor de uma varivel, o seu
comportamento torna-se menos claro.
Para se explicar correctamente este comportamento necessrio passar para um modelo
de avaliao mais elaborado designado modelo de avaliao em ambientes.
Neste modelo, uma varivel j no uma designao de um valor mas sim uma
designao de um objecto que contm um valor. Esse objecto pode ser visto como uma
caixa onde se guardam coisas. Em cada instante, a varivel designa sempre a mesma
caixa, mas esta pode guardar coisas diferentes. Segundo o modelo de ambientes, o valor
de uma varivel o contedo da caixa que ela designa. A forma especial setq a
operao que permite meter valores dentro da caixa.
As variveis so guardadas em estruturas denominadas enquadramentos. Por exemplo,
cada vez que usamos a forma let criado um novo enquadramento para conter as
variveis estabelecidas pelo let. Todas as expresses pertencentes ao corpo do let sero
avaliadas em relao a este enquadramento. Imaginemos agora a seguinte situao:
(let ((x 1))
(let ((y 2)
(z 3))
(+ x y z)))

Neste exemplo, o corpo do primeiro let um novo let. Existem portanto dois
enquadramentos. Estes enquadramentos esto organizados de modo a que o corpo do
segundo let consiga fazer referncia s trs variveis x, y e z.
Para isso, os enquadramentos so estruturados sequencialmente, desde aquele que for
textualmente mais interior at ao mais exterior. Essa sequncia de enquadramentos
designada por ambiente.
Cada enquadramento uma tabela de ligaes, que associa as variveis aos seus valores
correspondentes. Uma varivel nunca pode estar repetida num enquadramento, embora
possa aparecer em vrios enquadramentos de um ambiente. Cada enquadramento aponta
para o ambiente envolvente, excepto o ambiente global, que composto por um nico
enquadramento sem ambiente envolvente. no ambiente global que esto guardadas
todas as funes que usamos normalmente.

59

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

9.1 mbito lxico


A regra de avaliao de variveis em ambientes diz que o valor de uma varivel em
relao a um ambiente dado pela ligao dessa varivel no primeiro enquadramento em
que ela surja ao longo da sequncia de enquadramentos que constituem esse ambiente. Se
nenhum enquadramento possui uma ligao para essa varivel ela diz-se no ligada. um
erro avaliar variveis no ligadas.
Uma vez que os enquadramentos de um ambiente esto associados lexicamente s formas
que os criaram, possvel determinar o mbito de uma varivel qualquer simplesmente
observando o texto do programa.
Usando o modelo de avaliao em ambientes muito fcil perceber o comportamento da
forma especial let (que no mais do que uma simplificao de uma lambda) e da forma
especial setq. Cada let aumenta o ambiente em que avaliado com um novo
enquadramento, estabelecendo ligaes para as suas variveis. Quando se pretende saber
o valor de uma varivel percorre-se o ambiente, comeando pelo primeiro enquadramento
at se encontrar a ligao correspondente. Se ela no aparecer, vai-se passando de
enquadramento em enquadramento at se atingir o ambiente global, e se a tambm no
existir nenhuma ligao para aquela varivel gerado um erro de varivel no ligada. O
setq altera o valor da varivel que aparece estabelecida no enquadramento mais prximo
do ponto onde o setq avaliado. Se se atingir o ambiente global, e se a tambm no
existir nenhuma ligao para aquela varivel, criada essa ligao no ambiente global.
> (let ((x 10))
(+ x y))
Error: Unbound variable: Y
> (setq y 20)
20
> (let ((x 10))
(+ x y))
30

Como se v pelo exemplo. A partir do momento em que se estabeleceu uma ligao no


ambiente global para a varivel y, j possvel avaliar aquele let apesar de ele fazer
referncia a uma varivel livre. No entanto, a utilizao da forma especial setq para criar
variveis globais considerada pouca correcta e o compilador emitir um aviso se
encontrar uma destas formas de utilizao. A utilizao do setq deve ser restricta a
modificaes do valor de variveis previamente estabelecidas.
As regras de avaliao do modelo de ambientes so, em tudo, equivalentes s do modelo
clssico, excepto no que diz respeito aplicao de funes.
No modelo de ambientes todas as funes possuem um ambiente associado, que
corresponde quele que existia quando a funo foi definida. Quando se aplica uma
funo aos seus argumentos, cria-se um novo ambiente, cujo primeiro enquadramento
contm as ligaes dos parmetros formais da funo aos seus argumentos e cujo

60

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


ambiente envolvente aquele em que a funo foi definida. em relao a este novo
ambiente que se avalia o corpo da funo.
Note-se que a forma especial defun define funes (i.e., cria uma ligao entre o nome
da funo e a lambda correspondente ao seu corpo) sempre no ambiente global, enquanto
que setq altera a ligao de uma varivel no primeiro enquadramento do ambiente em
que a forma especial avaliada. S se a varivel no for encontrada na sequncia de
enquadramentos que o setq cria uma no ambiente global.
Exerccio 55
Interprete o comportamento da seguinte funo:
(let ((valor 0))
(defun incrementa ()
(setq valor (1+ valor))
valor))
> (incrementa)
1
> (incrementa)
2
> (incrementa)
3

Exerccio 56
Uma excelente aplicao de funes com estado local na criao de geradores de
sequncias de nmeros, i.e., funes sem argumentos que, a cada invocao, devolvem o
elemento que sucede logicamente a todos os que foram gerados anteriormente. Seguindo
este paradigma, defina o gerador da sequncia de Fibonacci, que representada pela
sucesso crescente 1, 2, 3, 5, 8, ..., em que cada nmero a soma dos dois ltimos que o
precedem.
> (gera-fib)
1
> (gera-fib)
2
> (gera-fib)
3

Exerccio 57
Infelizmente, a funo gera-fib no sabe recomear. Para isso, necessrio defini-la
(compil-la) outra vez. Este processo, como lgico, muito pouco prtico, em especial
porque obriga o utilizador a ter acesso ao cdigo do gerador. Complemente a definio da
funo gera-fib com uma outra funo denominada repoe-fib que repe o gerador em
condies de recomear de novo desde o princpio.
> (gera-fib)
1

61

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


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

9.2 mbito dinmico


A criao de variveis globais em Common Lisp deve ser feita usando as formas
especiais defconstant para criar constantes, defvar para criar variveis inicializveis
uma nica vez e defparameter para criar variveis inicializveis vrias vezes. Esta
diferena relevante sobretudo durante a fase de desenvolvimento e depurao de
programas.
> (defconstant acelaracao-gravidade 9.8)
ACELARACAO-GRAVIDADE
> (defvar *y*)
*Y*
> (defparameter *z* 10)
*Z*

Note-se a conveno adotada para as variveis globais de usar nomes compreendidos


entre um par de asteriscos. Quando se definem constantes essa conveno no se aplica.
O fato de podermos ter variveis globais introduz uma alterao nas regras de avaliao.
Tnhamos visto que as variveis que eram parmetros de funes (e, como tal, as
variveis introduzidas por um let) tinham mbito lxico, ou seja, apenas podiam ser
referidas dentro da regio textual que as introduziu. No entanto, as variveis globais
como aceleracao-gravidade, *y* ou *z* podem ser referidas de qualquer ponto do
programa, fazendo com que o seu mbito passe a ser vago. No entanto, apesar de ser
possvel refernciar a varivel *y*, ser produzido um erro quando tentarmos determinar
o seu valor, uma vez que ele ainda est indefinido. Ser preciso ligarmos um valor quela
varivel antes de a podermos avaliar e, para isso, podemos usar um let.
> (defvar *y*)
*Y*
> (defun teste () (+ *y* *y*))
TESTE
> (teste)
Error: Unbound variable: *Y*
> (let ((*y* 10)) (teste))
20
>*y*
Error: Unbound variable: *Y*

Repare-se que apesar de, momentaneamente, termos atribudo um valor varivel *y*
por intermdio de um let, ela perdeu esse valor assim que terminou o let. A durao da
varivel *y* , assim, dinmica. Apenas as variveis lxicas possuem durao indefinida.
62

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Variveis como *y* e *z* dizem-se especiais e possuem mbito vago e durao
dinmica. Esta combinao d origem a um comportamento que se designa de mbito
dinmico.
Um dos aspectos mais crticos na utilizao de variveis de mbito dinmico o fato de,
geralmente, no ser suficiente ler o cdigo de um programa para perceber o que que ele
vai fazer-- tambm preciso execut-lo. O seguinte exemplo explica este ponto.
Imaginemos as seguintes definies:
> (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 variveis lxicas. Da que baste observar o texto da
funo soma-2 para se perceber que a varivel x usada em (+ x y) toma sempre o valor
2.
No segundo exemplo, a nica diferena est no fato de a varivel *x* ser especial. Nesta
situao a funo soma-2 no usa o valor de *x* que existia no momento da definio da
funo, mas sim o valor de *x* que existe no momento da execuo da funo. Desta
forma, j no suficiente observar o texto da funo soma-2 para perceber o que ela faz.
Por este motivo, o uso excessivo de variveis dinmicas pode tornar um programa difcil
de ler e, consequentemente, difcil de desenvolver e difcil de corrigir.

63

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

10 Parmetros especiais

10.1 Parmetros opcionais


Como referimos anteriormente, a forma especial return, que se pode utilizar dentro de
um loop, possui um parmetro opcional, que o valor a retornar do ciclo. Isto quer dizer
que o argumento que deveremos passar para esse parmetro pode ser omitido. Esta
caracterstica tambm muito til para a definio de funes, permitindo que ela possa
assumir certos parmetros por omisso.
Para definirmos funes que tm parmetros opcionais temos de usar um qualificador
especial designado &optional na lista de parmetros formais. Esse qualificador indica
que todos os parmetros que se lhe seguem so opcionais e que, se os argumentos
correspondentes forem omitidos, eles valem nil. Se pretendermos um valor diferente
para um parmetro, podemos inserir o parmetro numa lista com o seu valor. A seguinte
funo mostra como se pode definir a funo incr que incrementa o seu argumento de
uma unidade, ou de uma quantidade que lhe seja fornecida.
(defun incr (x &optional (i 1))
(+ x i))

> (incr 10)


11
> (incr 10 5)
15

Exerccio 58
Defina a funo eleva, que eleva um nmero a uma determinada potncia. Se a potncia
no for indicada dever ser considerada 2. Nota: a expresso (expt x y) determina a
potncia y de x, i.e., .
Exerccio 59
Reescreva a funo factorial de forma a gerar um processo iterativo mas sem usar
funes auxiliares.

10.2 Parmetros de resto

64

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Para alm do qualificador &optional existem ainda o &rest e o &key. O &rest s pode
qualificar o ltimo parmetro de uma funo, e indica que esse parmetro vai ficar ligado
a uma lista com todos os restantes argumentos. A ttulo de exemplo, temos:
> ((lambda (x y &rest z) (list x y z)) 1 2 3 4 5 6)
(1 2 (3 4 5 6))

O qualificador &rest permite assim construir funes com qualquer nmero de


argumentos.
Exerccio 60
Defina a funo lista que recebe qualquer nmero de argumentos e constri uma lista
com todos eles.
>(lista 1 2 3 4 5 6 7 8 9 0)
(1 2 3 4 5 6 7 8 9 0)

10.3 Parmetros de chave


O qualificador &key informa o avaliador que os parmetros qualificados so ligados
atravs de uma indicao explcita de quem chama a funo. Essa indicao feita
designando o nome de cada parmetro precedido por dois pontos e indicando qual o valor
a que ele deve estar ligado. Os parmetros que no forem ligados comportam-se como se
fossem opcionais. Este gnero de parmetros dizem-se de chave (keyword).
Desta forma, o &key permite trocar a ordem dos argumentos. O seguinte exemplo mostra
o funcionamento do &key.
> ((lambda (x y &key z (w 4) k) (lista x y z w k))
1 2 :k 5 :z 3)
(1 2 3 4 5)

A grande maioria das funes pr-definidas na linguagem para manipular listas possui
parmetros opcionais e de chave. Repare-se que as chaves so tratadas de forma especial
pelo avaliador. Efetivamente, se assim no fosse, quando especificvamos os argumentos
de uma funo com parmetros de chave, o avaliador iria tentar determinar o valor das
chaves, gerando ento um erro por estas no terem valor. Na realidade, quando um
smbolo qualquer precedido por dois pontos, esse smbolo considerado como especial,
pertencendo ao conjunto dos smbolos chaves, e avaliando para si prprio.
> ola
Error: Unbound variable: OLA
> 'ola
OLA
> :ola
:OLA

65

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Os tipos aglomerados constituem uma das grandes utilizaes dos parmetros de chave.
Nestes tipos de dados, os construtores limitam-se a realizar um agrupamento de valores
para os diversos constituintes do tipo. Vimos o exemplo de um automvel, que era
constitudo por uma marca, um modelo, um nmero de portas, etc. Embora no exista
qualquer razo para que a marca de um automvel seja mais importante que o seu
nmero de portas, infelizmente a ordenao implcita dos argumentos das funes Lisp
impem que assim seja. Podemos resolver este problema usando parmetros de chave, de
forma a eliminar a ordenao dos argumentos e permitir ao utilizador especific-los pela
ordem que entender, por exemplo:
(defun novo-automovel (&key marca modelo portas)
(list marca modelo portas))
> (novo-automovel :portas 2 :marca 'honda :modelo 'civic)
(honda civic 2)

Para alm da possibilidade de alterao da ordem dos argumentos, o qualificador &key


ajuda legibilidade do programa, ao tornar explcito o papel que cada argumento tem na
funo.

66

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

67

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

11 Macros
Como referimos na apresentao da linguagem Lisp, existem certas formas da linguagem
que no obedecem s regras de avaliao usuais. Essas formas designam-se formas
especiais e o if um exemplo. Cada forma especial possui a sua prpria regra de
avaliao. Vimos que, por isso, era impossvel definir o if como se fosse uma funo,
pois todos os operandos (o teste, o consequente e a alternativa) seriam avaliados.
Embora a linguagem Lisp possua muitas formas especiais, possvel ``criar'' outras
formas especiais atravs da utilizao de macros. Uma macro uma forma que a
linguagem expande para outra forma, superando assim as dificuldades inerentes
avaliao dos argumentos que as funes realizam. Na realidade, Lisp possui muito
poucas formas especiais reais. A grande maioria das formas especiais so implementadas
atravs de macros, usando a forma especial defmacro cuja sintaxe igual da defun.

11.1 Avaliao de macros


A utilizao de uma macro implica duas avaliaes. Na primeira, a macro produz uma
expresso Lisp a partir dos seus argumentos, que se designa a expanso da macro. Esta
expresso ento avaliada uma segunda vez para produzir o valor final. A ttulo de
exemplo, se definirmos o if como uma macro que expande para um cond e avaliarmos a
expresso (if (> 3 4) (+ 1 2) (- 5 2)), a primeira avaliao dever produzir a
expresso (cond ((> 3 4) (+ 1 2)) (t (- 5 2))), que ser avaliada segunda vez
para determinar o seu valor.
Note-se que, neste exemplo, se a forma cond fosse, tambm ela, uma macro, o processo
era aplicado recursivamente at que no surgisse mais nenhuma macro. Nessa altura, o
Lisp usava a regra usual de avaliao para determinar o valor final da expresso

11.2 Escrita de macros


A escrita de uma macro inerentemente mais complexa que a escrita de uma funo,
sendo decomposta em quatro fases:
1. Decidir se a macro realmente necessria. Esta fase de grande importncia, pois
cada vez que se define uma macro est-se a aumentar a linguagem com uma nova
forma especial. Quem pretende ler um programa que usa a macro obrigado a
conhecer a sua sintaxe e semntica, e se o nmero de macros muito grande,
pode ser difcil perceber o cdigo.
2. Escrever a sintaxe da macro. Nesta fase pretende-se definir qual vai ser a forma
de utilizao da macro. A sintaxe deve ser o mais simples possvel e o mais
coerente possvel com as restantes formas da linguagem para no complicar a sua
leitura.

68

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


3. Escrever a expanso da macro. Nesta fase determina-se a expresso Lisp que a
macro deve produzir quando expandida. A expanso qualquer expresso Lisp,
que pode inclusive fazer referncia a outras macros.
4. Escrever a macro usando a forma especial defmacro. esta a fase mais delicada
do processo, em que se programa um processo de transformar a forma especial
que queremos definir numa expresso que use outras formas especiais j
definidas.
A ttulo de exemplo vamos definir a forma especial meu-if cujo objetivo simplificar o
uso do cond quando s existe um teste, um consequente e uma alternativa.
A sintaxe da forma meu-if :
(meu-if teste consequente alternativa)

A expanso da macro ser qualquer coisa da forma:


(cond (teste consequente)
(t alternativa))

A definio da macro :
(defmacro meu-if (teste consequente alternativa)
(list 'cond
(list teste consequente)
(list t alternativa)))

11.3 Depurao de macros


Uma vez que a aplicao de uma macro mostra apenas o resultado final, depois de a
macro ter sido expandida e a sua expanso avaliada, necessrio um meio auxiliar para
visualizarmos se a expanso est a ser feita de modo correto. Para isso o Lisp fornece as
funes macroexpand-1 e macroexpand que realizam a expanso da macro uma nica
vez ou todas as vezes possveis, respectivamente.
> (macroexpand-1 '(meu-if (> 3 4) (+ 2 3) (- 5 3)))
(COND ((> 3 4) (+ 2 3)) (T (- 5 3)))

Exerccio 61
Implemente a macro quando, que recebe um teste e um conjunto de expresses. Esta
forma especial avalia o teste e, quando este verdade, avalia sequencialmente as
expresses, devolvendo o valor da ltima. Se o teste falso, a forma retorna nil sem
avaliar mais nada.
Esta macro j existe em Lisp e denomina-se when.

69

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

11.4 Caracteres de macro


Geralmente, a parte mais difcil na escrita de uma macro a determinao das expresses
Lisp que quando avaliadas produzem uma nova expresso Lisp que realiza o nosso
objetivo. Para simplificar esta tarefa usual utilizarem-se caracteres especiais que,
semelhana da plica (quote) e do cardinal-plica (function) so transformados na leitura
para outras expresses. Cada um dos caracteres especiais possui uma funo Lisp
associada que avaliada quando a linguagem, durante a leitura das expresses, encontra
um desses caracteres. Essa funo Lisp pode ento realizar mais leituras e retornar o que
achar mais conveniente.
Estes caracteres especiais so designados caracteres de macro pois eles so transformados
(so expandidos) na leitura em outras expresses, um pouco imagem do que acontecia
com as macros. A difereno est no instante em que a expanso ocorre. Uma macro
expandida em tempo de compilao (ou, em algumas implementaes de Lisp, em tempo
de execuo). Um caracter de macro expandido na leitura de expresses.
Qualquer caracter pode ser considerado especial, bastando, para isso, usar a funo setmacro-caracter que recebe um caracter e uma funo a aplicar sempre que o caracter
for lido. A funo a aplicar deve possuir dois parmetros que recebero, o primeiro, o
local donde o Lisp estava a ler (terminal, ficheiro, etc) para que a funo possa continuar
a leitura do mesmo stio, e o segundo, o prprio caracter de macro.
Para se indicar um caracter em Lisp necessrio preced-lo dos caracteres ``#\''. Por
exemplo, o caracter $ indicado por ``#\$''. Como j sabido, se no se inclussem os
caracteres ``#\'', o Lisp consideraria o objeto lido como um smbolo e no como um
caracter.
Para se compreender a utilizao dos caracteres de macro, podemos admitir que no
existia o caracter de plica e que pretendamos defini-lo. A expresso 'ola representa,
como sabemos, (quote ola), logo a funo que seria invocada quando se encontrasse a
plica necessitaria de ler o objeto que estava aps a plica (usando a funo read) e
construiria uma lista com o smbolo quote cabea e o objeto lido no fim, ou seja:
(defun plica (canal-leitura caracter)
(list (quote quote) (read canal-leitura)))
> (set-macro-character # (function plica))
T
> 'ola
OLA

A linguagem Lisp possui, previamente definidos, vrios caracteres de macro. A plica


um deles, o ponto e vrgula (que representa um comentrio) outro, etc. Alguns dos
caracteres de macro so precedidos por um caracter especial, considerado o caracter de
despacho, permitindo aumentar o nmero de possveis caracteres de macro sem reduzir o
nmero de caracteres normais utilizveis. O caracter de despacho mais usado o
70

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


cardinal, mas pode ser qualquer outro. Como exemplos destes caracteres de macro com
despacho temos o cardinal-plica para indicar funes, o cardinal-barra para indicar
caracteres, etc.
De todos os caracteres de macro, aqueles que so particularmente teis para a escrita de
macros so o plica para trs (`--backquote), a vrgula (,--comma) e o vrgula-arroba (,@-comma-at).
O backquote indica ao avaliador que no deve avaliar uma expresso exceto quando for
indicado em contrrio. Quando usado isoladamente, o backquote funciona exactamente
como o quote. No entanto, a sua combinao com o comma e o comma-at permite
simplificar imenso a escrita de expresses complexas. O comma s pode ser utilizado
numa expresso (uma lista, tipicamente) precedida do backquote, e informa o avaliador
que a expresso que se segue para avaliar e o seu resultado inserido na lista. O commaat idntico mas o resultado da avaliao tem de ser uma lista cujos elementos so
inseridos.
A ttulo de exemplo, temos:
> `((+ 1 2) ,(+ 1 2) (list 1 2) ,(list 1 2) ,@(list 1 2))
((+ 1 2) 3 (LIST 1 2) (1 2) 1 2)

11.5 Macros utis


Exerccio 62
Reescreva a macro quando usando o backquote, o comma e o comma-at.
Exerccio 63
Escreva a macro a-menos-que, que recebe um teste e um conjunto de expresses. Esta
forma especial avalia o teste e, quando este falso, avalia sequencialmente as expresses,
devolvendo o valor da ltima. Se o teste verdade, a forma retorna nil sem avaliar mais
nada.
Esta macro j existe em Lisp e denomina-se unless.
Exerccio 64
Escreva uma implementao da macro meu-cond usando a forma especial if.
Exerccio 65

71

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


A implementao da macro meu-cond anterior implica que a expanso apenas parcial,
i.e., enquanto houver clusulas, o meu-cond expande para outro meu-cond. Implemente a
macro meu-cond de modo a realizar a expanso de uma s vez.
Exerccio 66
A macro seja implementa a mesma funcionalidade que a forma especial let. Como se
sabe, o let no mais do que uma macro que expande para uma lambda. A idia ser
definir a macro seja exatamente da mesma forma, i.e., dever existir uma
correspondncia entre a expresso:
(seja ((x 10) (y 20))
(+ x y))

e a sua expanso:
((lambda (x y) (+ x y)) 10 20)

Defina a macro seja de modo a implementar essa correspondncia.


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

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


Exerccio 67
Escreva a macro enquanto, que recebe um teste e um conjunto de expresses. A forma
enquanto deve avaliar o teste e, caso seja verdade, avaliar sequencialmente as expresses
e voltar ao princpio. Caso o teste seja falso, deve terminar com o valor nil.
Exerccio 68
Escreva a macro caso, que recebe uma expresso e um conjunto de pares tomoexpresses. A forma especial caso avalia a primeira expresso, e compara o resultado
(usando a funo eql) com cada um dos tomos em sequncia. Se um dos tomos
emparelhar, so avaliadas as expresses a ele associadas e retornado o valor da ltima.
Um exemplo de utilizao 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)

72

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


BASICO
1

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

11.6 Iteradores
Como se pode depreender dos exemplos apresentados, as macros destinam-se
essencialmente criao de acar sinttico, i.e., de expresses que sejam mais simples
de utilizar que outras j existentes. Esta caracterstica torna as macros ferramentas
extremamente teis para criao de tipos abstratos de informao capazes de dar ao
utilizador iteradores sobre os objetos desse tipo.
A ttulo de exemplo, vamos considerar a definio de um iterador para os elementos de
uma lista. Este iterador dever ser uma forma especial que recebe um smbolo (uma
varivel), uma lista e um conjunto de expresses, e itera aquelas expresses com o
smbolo ligado a cada elemento da lista. Apresenta-se agora um exemplo da sintaxe da
forma especial:
(itera-lista (elem (list 1 2 3 4 5))
(print elem))

A sua definio relativamente simples:


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

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


1
2
3
NIL

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


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

73

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


(6)
NIL

O problema est no fato de a macro estabelecer uma varivel denominada lista, que
interfere com a varivel exterior do segundo exemplo, pois tem o mesmo nome, ficando
assim obscurecida. A referncia a lista feita no corpo da macro refere-se assim
varivel interna da macro, e no que seria desejvel. Nesta situao diz-se que a macro
capturou variveis.
A soluo para este problema est na utilizao de variveis que no possam interferir de
modo algum com outras j existentes. Um remdio possvel ser criar as variveis
necessrias s macros com nomes estranhos, com pouca probabilidade de serem usados
pelo utilizador da macro, como por exemplo, %%%$$$lista$$$%%%. No entanto esta
soluo no perfeita. O melhor a fazer usar novos smbolos que no se possam
confundir com os j existentes. A funo gensym produz um smbolo novo e nico de
cada vez que chamada, sendo ideal para resolver estas dificuldades.
Exerccio 69
Defina a macro itera-lista usando a referida funo gensym para proteger as variveis
do utilizador de uma captura indevida.
> (let ((lista '(1 2 3)))
(itera-lista (x '(4 5 6))
(print (cons x lista))))
(4 1 2 3)
(5 1 2 3)
(6 1 2 3)
NIL

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


Exerccio 70
Defina de novo a forma especial itera-lista, mas recorrendo desta vez abordagem da
programao funcional, i.e., sem utilizar formas especiais para ciclos nem atribuio de
valores a variveis.
Exerccio 71
A forma especial caso definida anteriormente sofria do mesmo problema do iteralista, pois a varivel usada para guardar o valor temporrio pode obscurecer variveis
idnticas declaradas exteriormente. Redefina a macro de forma a evitar esse perigo.

11.7 Fichas

74

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Como vimos, uma ficha (no sentido da linguagem Pascal) no mais do que um tipo
aglomerado. Uma ficha possui um nome e composta por um conjunto de campos, cada
um com um nome distinto. Cada elemento de um dado tipo de ficha corresponde a um
conjunto de valores apropriados para cada campo desse tipo. A utilizao de fichas de
tal modo simples que todas as linguagens de programao possuem capacidades prprias
para lidar com elas. Um dado tipo de ficha no necessita mais do que um construtor para
os elementos desse tipo, um seletor e um modificador para cada um dos campos da ficha
e, possivelmente, um reconhecedor de fichas de um dado tipo.
Dadas as capacidades da linguagem Lisp em aglomerar facilmente objetos, a
implementao de fichas uma tarefa de tal modo simples que bastante fcil
automatiz-la.
Vimos na descrio do tipo aglomerado automvel que ele era definido por um
construtor:
(defun novo-automovel (&key marca modelo portas) ...)

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

e ainda pelos modificadores:


(defun muda-automovel-marca! (automovel nova-marca) ...)
(defun muda-automovel-modelo! (automovel novo-modelo) ...)
(defun muda-automovel-portas! (automovel novo-portas) ...)

Vamos tambm incluir um reconhecedor de automveis muito til para distinguirmos os


diversos tipos de fichas:
(defun automovel? (obj) ...)

Repare-se que o conjunto de funes que apresentamos constituem um modelo para a


definio de fichas. A ficha automvel possui um construtor criado atravs da
concatenao da palavra ``novo'' ao nome da ficha. Cada seletor dado pela
concatenao do nome da ficha ao nome do campo a que ele diz respeito. Cada
modificador dado pela concatenao da palavra ``muda'' ao nome do seletor
correspondente e terminado com a letra ``!'. O reconhecedor dado pelo nome da ficha
terminado com a letra ``?'. Qualquer outra ficha seria definida de forma idntica, pelo que
podemos fazer uma abstrao da definio de fichas, criando uma macro que encapsule a
definio de todas aquelas funes.

75

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


Um dos problemas que temos de resolver a implementao das fichas. Vimos no
exemplo do automvel que o podamos implementar como uma lista com os valores dos
vrios campos. Infelizmente, aquela implementao no nos permite distinguir o tipo de
registo automvel de outros tipos de registo, pelo que temos de modificar a
implementao de modo a isso ser possvel. Para minimizar as alteraes, podemos
considerar que cada registo implementado por uma lista cujo primeiro elemento o
nome da ficha e cujos restantes elementos so os valores dos campos da ficha. Isto
permite manter a mesma lgica de acesso e modificao dos campos desde que os ndices
desses campos na lista sejam incrementados de uma unidade.
Assim sendo, o construtor ficar qualquer coisa do gnero:
(defun novo-automovel (&key marca modelo portas)
(list 'automovel marca modelo portas))

Um dado selector ser :


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

Um modificador ser:
(defun muda-automovel-marca! (automovel nova-marca)
(muda-n-esimo! 1 automovel nova-marca))

O reconhecedor de automveis ficar:


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

Podemos agora definir uma macro designada ficha, cuja utilizao ser da seguinte
forma:
(ficha automovel
marca modelo portas)

A expanso da macro dever ser qualquer coisa da forma:


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

A definio da macro , assim, relativamente simples:


76

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


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

Para definirmos as vrias operaes vamos necessitar de concatenar smbolos. Para isso,
necessrio criar um smbolo, atravs da funo intern, cujo nome seja a concatenao,
atravs da funo concatenate, dos nomes dos smbolos, obtidos mapeando a funo
string nesses smbolos, i.e.:
(defun junta-nomes (&rest nomes)
(intern (apply #'concatenate
'string
(mapcar #'string nomes))))

Com a ajuda desta funo, j podemos definir as restantes funes da macro.


(defun construtor-ficha (nome campos)
`(defun ,(junta-nomes 'novo- nome) (&key ,@campos)
(list ',nome ,@campos)))
(defun selectores-ficha (nome campos)
(mapcar
#'(lambda (campo)
`(defun ,(junta-nomes nome '- campo) (,nome)
(nth ,(1+ (position campo campos)) ,nome)))
campos))
(defun modificadores-ficha (nome campos)
(mapcar
#'(lambda (campo)
`(defun ,(junta-nomes 'muda- nome '- campo '!)
(,nome novo)
(muda-n-esimo! ,(1+ (position campo campos))
,nome novo)))
campos))
(defun reconhecedor-ficha (nome)
`(defun ,(junta-nomes nome '?) (obj)
(and (listp obj) (eql (first obj) ',nome))))

Podemos agora experimentar a macro e visualizar os resultados:


> (macroexpand-1 '(ficha automovel marca modelo portas)))
(PROGN
(DEFUN NOVO-AUTOMOVEL (&KEY MARCA MODELO PORTAS)
(LIST 'AUTOMOVEL MARCA MODELO PORTAS))
(DEFUN AUTOMOVEL-MARCA (AUTOMOVEL)
(NTH 1 AUTOMOVEL))
(DEFUN AUTOMOVEL-MODELO (AUTOMOVEL)
(NTH 2 AUTOMOVEL))

77

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi


(DEFUN AUTOMOVEL-PORTAS (AUTOMOVEL)
(NTH 3 AUTOMOVEL))
(DEFUN MUDA-AUTOMOVEL-MARCA! (AUTOMOVEL NOVO)
(MUDA-N-ESIMO! 1 AUTOMOVEL NOVO))
(DEFUN MUDA-AUTOMOVEL-MODELO! (AUTOMOVEL NOVO)
(MUDA-N-ESIMO! 2 AUTOMOVEL NOVO))
(DEFUN MUDA-AUTOMOVEL-PORTAS! (AUTOMOVEL NOVO)
(MUDA-N-ESIMO! 3 AUTOMOVEL NOVO))
(DEFUN AUTOMOVEL? (OBJ)
(AND (LISTP OBJ) (EQL (first OBJ) 'AUTOMOVEL)))
'AUTOMOVEL)

Exerccio 71
Um dos melhoramentos que seria interessante incluir na definio de fichas seria a
incluso de valores por omisso para qualquer campo. Poderamos indicar que um
automvel possui geralmente quatro portas da seguinte forma:
(ficha automovel
marca
modelo
(portas 4))

Assim, se se criasse um automvel sem especificar o nmero de portas, ele seria de 4.


Implemente essa alterao.
> (macroexpand-1 '(ficha automovel marca modelo (portas 4))))
(PROGN
(DEFUN NOVO-AUTOMOVEL (&KEY MARCA MODELO (PORTAS 4))
(LIST 'AUTOMOVEL MARCA MODELO PORTAS))
(DEFUN AUTOMOVEL-MARCA (AUTOMOVEL)
(NTH 1 AUTOMOVEL))
...

Esta forma de criao de fichas j existe em Common Lisp atravs da macro defstruct.

78

Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi

Referncias
Steelem, Guy L..Commom Lisp the Language. Ed. Digital Press, 1990. dispovel
online: http://www.supelec.fr/docs/cltl/cltl2.html.

79

Você também pode gostar