Escolar Documentos
Profissional Documentos
Cultura Documentos
Linguagens de Programao II
Programao Funcional usando LISP
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
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.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:
!
!
!
!
55
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)
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)
( 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
( 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
round ( Arredonda )
( round 3.1 ) " 3 0.1
( round 3.7 ) " 4 -0.3
( 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
12
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
( Ordem Ascendente )
(<1352) "
( < 1 two 4 ) "
NIL
T
( 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
( Mnimo )
( min 1 2 3 4 5 6 7 )
"
( min ( expt 2 10 ) ( expt 10 2 ) )
-2
"
100
( Par )
13
"
"
NIL
T
( mpar )
( oddp 17 )
( oddp two )
"
"
T
NIL
( Negativo )
( minusp 17 )
( minusp 13 ) "
"
T
NIL
( 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).
14
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
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
16
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
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).
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
19
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
20
3 - Funes
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 :
!
!
Dado este padro, os erros mais comuns associados s funes recursivas so,
naturalmente:
!
!
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
22
(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
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
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
25
26
28
4 mbito e Durao
Exerccio 24
Que tipo de mbito possui uma varivel de um let? Que tipo de mbito possui o nome
de uma funo?
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
31
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
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
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
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
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
Assim, j j podemos escrever a funo que calcula a soma de dois racionais, usando a
frmula
.
34
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)))
Exerccio 28
35
36
37
6 Entrada e sada
38
39
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
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.
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
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)
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
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
43
Exerccio 43
44
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
46
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
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
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
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
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
Estando na posse destas funes, podemos criar um automvel especfico, por exemplo:
> (novo-automovel 'honda 'civic 2)
(honda civic 2)
50
51
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
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)))
53
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
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
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
57
58
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
60
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
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
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
10 Parmetros especiais
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.
64
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
66
67
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.
68
A definio da macro :
(defmacro meu-if (teste consequente alternativa)
(list 'cond
(list teste consequente)
(list t alternativa)))
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
71
e a sua expanso:
((lambda (x y) (+ x y)) 10 20)
72
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))
73
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
11.7 Fichas
74
e pelos selectores:
(defun automovel-marca (automovel) ...)
(defun automovel-modelo (automovel) ...)
(defun automovel-portas (automovel) ...)
75
Um modificador ser:
(defun muda-automovel-marca! (automovel nova-marca)
(muda-n-esimo! 1 automovel nova-marca))
Podemos agora definir uma macro designada ficha, cuja utilizao ser da seguinte
forma:
(ficha automovel
marca modelo portas)
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))))
77
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))
Esta forma de criao de fichas j existe em Common Lisp atravs da macro defstruct.
78
Referncias
Steelem, Guy L..Commom Lisp the Language. Ed. Digital Press, 1990. dispovel
online: http://www.supelec.fr/docs/cltl/cltl2.html.
79