intro lisp

74

Upload: adailton-carlos

Post on 04-Jul-2015

223 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Intro Lisp

Introdu�c~ao �a Linguagem LispAnt�onio Menezes Leit~aoRevis~ao deJo~ao CachopoOutubro 1995

Page 2: Intro Lisp

�Indice

1

Page 3: Intro Lisp

1 Linguagem de Programa�c~aoUma linguagem de programa�c~ao n~ao �e apenas um meio de indicar aum computador uma s�erie de opera�c~oes a executar. Uma linguagem deprograma�c~ao �e, sobretudo, um meio de exprimirmos ideias acerca demetodologias.Uma linguagem de programa�c~ao deve ser feita para seres humanosdialogarem acerca de programas e, s�o incidentalmente, para computa-dores os executarem. Como tal, deve possuir ideias simples, deve sercapaz de combinar ideias simples para formar ideias mais complexas edeve ser capaz de realizar abstrac�c~oes de ideias complexas para as tornarsimples.Existe uma enorme diversidade de linguagens de programa�c~ao, umasmais adaptadas a um determinado tipo de processos, outras mais adap-tadas a outros. A escolha de uma linguagem de programa�c~ao deveestar condicionada, naturalmente, pelo tipo de problemas que quere-mos resolver, mas n~ao deve ser um comprometimento total. Para umprogramador �e muito mais importante compreender os fundamentos et�ecnicas da programa�c~ao do que dominar esta ou aquela linguagem.Lisp �e uma linguagem dinamica, cujos programas s~ao constitu��dospor pequenos m�odulos, de funcionalidade gen�erica e que cumprem umobjectivo muito simples. �E a sua combina�c~ao que produz um programacompleto. Os m�odulos desenvolvidos em Lisp possuem, geralmente, umafuncionalidade que ultrapassa largamente os objectivos para que foramconcebidos. A n~ao tipi�ca�c~ao de dados, a possibilidade de tratar dadose programas de um mesmo modo e a indistin�c~ao entre fun�c~oes de�nidaspela linguagem e fun�c~oes de�nidas pelo programador s~ao algumas dasraz~oes da sua exibilidade.A linguagem Lisp nasceu como uma ferramenta matem�atica, inde-pendente de qualquer computador e s�o posteriormente se procedeu �a suaadapta�c~ao a uma m�aquina. Uma vez que em Lisp n~ao existia qualquerdependencia, �a priori, do processador que iria executar a linguagem,a linguagem tamb�em n~ao podia tirar partido das suas potencialidades,sendo as primeiras vers~oes muito ine�cientes. Esta ine�ciencia resultavade os programas Lisp serem interpretados, sendo por isso muito maislentos do que o que uma compila�c~ao permite ao reescrever um progra-ma na linguagem do processador. No entanto, com o aparecimento decompiladores e�cazes e de um suporte cada vez maior da parte dos pro-cessadores, Lisp possui, actualmente, uma e�ciencia compar�avel �a dasrestantes linguagens.Lisp d�a ao programador a capacidade de extender a linguagem comoentender, permitindo incorporar outros estilos de programa�c~ao, comoprograma�c~ao orientada para objectos, programa�c~ao orientada para re-gras, etc. Quando surgem novos paradigmas de programa�c~ao, o Lispincorpora-os facilmente enquanto as antigas linguagens morrem. Istopermite adaptar a linguagem ao problema que estamos a resolver emvez de termos de adaptar o problema �a linguagem que estamos a usar.2

Page 4: Intro Lisp

A facilidade de utiliza�c~ao, adapta�c~ao e extens~ao da linguagem Lispfez com que surgissem dezenas de vers~oes diferentes: FranzLisp, Ze-taLisp, LeLisp, MacLisp, InterLisp, Scheme, T, Nil, XLisp, AutoLisp,etc, para nomear apenas as mais relevantes. Esta multitude de dialec-tos come�cou a tornar dif��cil a livre comunica�c~ao entre os membros dacomunidade Lisp. Para obviar este problema, criou-se um standard de-nominado Common Lisp com o objectivo de facilitar a troca de ideias(e programas).Sendo a linguagem Common Lisp o herdeiro leg��timo de todas asoutras, ela deve suportar a maioria das capacidades que estas possuem.Como �e l�ogico isto impede a estabiliza�c~ao da linguagem, que ainda n~aoparou de evoluir, sendo ampliada de tempos a tempos para incorporarnovos (e velhos) paradigmas de programa�c~ao. Felizmente, essa am-plia�c~ao evita (na medida em que isso �e poss��vel) alterar funcionalidadesdas vers~oes anteriores e assim um programa Common Lisp tem a ga-rantia de funcionar independentemente do estado actual da linguagem.O programa pode n~ao estar tecnologicamente actual mas estar�a semprefuncionalmente actual.Lisp �e uma linguagem interactiva. Cada por�c~ao de um programa po-de ser compilada, corrigida e testada independentemente das restantes.Deste modo, Lisp permite a cria�c~ao, teste e correc�c~ao de programas deuma forma incremental, o que facilita muito a tarefa do programador.Quase todos as vers~oes de Lisp possuem simultaneamente um com-pilador e um interpretador, permitindo misturar livremente c�odigo com-pilado e interpretado e permitindo realizar compila�c~oes incrementais.Embora os exemplos que iremos apresentar tenham sido ensaiadosem Common Lisp, falaremos genericamente da linguagem Lisp.2 O AvaliadorEm Lisp, qualquer express~ao tem um valor. Este conceito �e de tal modoimportante que todas as implementa�c~oes da linguagem Lisp apresentamum avaliador, i.e., um pequeno programa destinado a interagir com outilizador de modo a avaliar express~oes por este fornecidas. Assim,quando o utilizador come�ca a trabalhar com o Lisp, �e-lhe apresentadoum sinal e o Lisp �ca �a espera que o utilizador lhe forne�ca uma express~ao.Welcome to Common Lisp!> O car�acter \>" �e a \prompt" do Lisp, �a frente da qual v~ao apare-cer as express~oes que o utilizador escrever. O Lisp interacciona como utilizador executando um ciclo em que le uma express~ao, determi-na o seu valor e escreve o resultado. Este ciclo de ac�c~oes designa-se,tradicionalmente, de ciclo read-eval-print.Quando o Lisp le uma express~ao, constroi internamente um objectoque a representa. Esse �e o papel da fase de leitura. Na fase de avalia�c~ao,3

Page 5: Intro Lisp

o objecto constru��do �e analisado de modo a produzir um valor. Estaan�alise �e feita empregando as regras da linguagem que determinam,para cada caso, qual o valor do objecto constru��do. Finalmente, o valorproduzido �e apresentado ao utilizador na fase de escrita atrav�es de umarepresenta�c~ao textual desse valor.Dada a existencia do ciclo read-eval-print, em Lisp n~ao �e necess�arioinstruir o computador a escrever explicitamente os resultados de umc�alculo como, por exemplo, em Pascal. Isto permite que o teste e cor-rec�c~ao de erros seja bastante facilitada. A vantagem de Lisp ser uma lin-guagem interactiva est�a na rapidez com que se desenvolvem prot�otiposde programas, escrevendo, testando e corrigindo pequenas fun�c~oes decada vez.Exerc��cio 2.0.1 Descubra como �e que inicia e termina a sua interac�c~aocom o Lisp.3 Elementos da LinguagemQualquer linguagem de programa�c~ao lida com duas esp�ecies de objectos:dados e procedimentos. Os dados s~ao os objectos que pretendemosmanipular. Os procedimentos s~ao descri�c~oes das regras para manipularesses dados.Se considerarmos a linguagem da matem�atica, podemos identi�caros n�umeros como dados e as opera�c~oes alg�ebricas como procedimentos epodemos combinar os n�umeros entre si usando aquelas opera�c~oes. Porexemplo, 2�2 �e uma combina�c~ao, tal como 2�2�2 e 2�2�2�2. No en-tanto, a menos que pretendamos �car eternamente a resolver problemasde aritm�etica elementar, somos obrigados a considerar opera�c~oes maiselaboradas que representam padr~oes de c�alculos. Neste �ultimo exemplo,�e evidente que o padr~ao que est�a a emergir �e o da opera�c~ao de poten-cia�c~ao, i.e, multiplica�c~ao sucessiva, tendo esta opera�c~ao sido de�nida namatem�atica h�a j�a muito tempo.Tal como a linguagem da matem�atica, uma linguagem de progra-ma�c~ao deve possuir dados e procedimentos primitivos, deve ser capazde combinar quer os dados quer os procedimentos para produzir dadose procedimentos mais complexos e deve ser capaz de abstrair padr~oes dec�alculo de modo a permitir trat�a-los como opera�c~oes simples, de�nindonovas opera�c~aoes que representem esses padr~oes de c�alculo.3.1 Elementos primitivosElementos primitivos s~ao as entidades mais simples com que a linguagemlida. Um n�umero, por exemplo, �e um dado primitivo.Como dissemos anteriormente, o Lisp executa um ciclo read-eval-print. Isto implica que tudo o que escrevemos no Lisp tem de ser ava-liado, i.e., tem de ter um valor, valor esse que o Lisp escreve no ecran.4

Page 6: Intro Lisp

Assim, se dermos um n�umero ao avaliador, ele devolve-nos o valordesse n�umero. Quanto vale um n�umero? O melhor que podemos dizer�e que ele vale aquilo que vale. Por exemplo, o n�umero 1 vale 1.> 11> 1234512345> 4.54.5Como se ve no exemplo, em Lisp, os n�umeros podem ser inteiros oureais.Exerc��cio 3.1.1 Descubra qual �e o maior real que o seu Lisp aceita.Consegue fazer o mesmo para os inteiros?3.2 Combina�c~oesCombina�c~oes s~ao entidades complexas feitas a partir de entidades maissimples. Por exemplo, na matem�aticas n�umeros podem ser combina-dos usando opera�c~oes como a soma ou o produto. Como exemplo decombina�c~oes matem�aticas, temos 1 + 2 e 1 + 2 � 3. A soma e o pro-duto de n�umeros s~ao exemplos de opera�c~oes extremamente elementaresconsideradas procedimentos primitivos.Em Lisp, criam-se combina�c~ao escrevendo uma sequencia de ex-press~oes entre um par de parenteses. Uma express~ao �e um elementoprimitivo ou uma outra combina�c~ao. A express~ao (+ 1 2) �e uma com-bina�c~ao dos elementos primitivos 1 e 2 atrav�es do procedimento +. J�ano caso (+ 1 (* 2 3)) a combina�c~ao �e ocorre entre 1 e (* 2 3), sendoesta �ultima express~ao uma outra combina�c~ao.N~ao �e dif��cil de ver que as �unicas combina�c~oes com utilidade s~aoaquelas em que as express~oes correspondem a operadores e operandos.Por conven�c~ao, o Lisp considera que o primeiro elemento de uma com-bina�c~ao �e um operador e os restantes s~ao os operandos.A nota�c~ao que o Lisp utiliza para construir express~oes (operadorprimeiro e operandos a seguir) �e designada por nota�c~ao pre�xa. Estanota�c~ao costuma causar alguma perplexidade a quem inicia o estudoda linguagem, que espera uma nota�c~ao mais pr�oxima da que aprendeuem aritm�etica e que �e usada habitualmente nas outras linguagens deprograma�c~ao. Nestas, a express~ao (+ 1 (* 2 3)) �e usualmente escritana forma 1 + 2 * 3 (designada nota�c~ao in�xa) que, normalmente, �emais simples de ler por um ser humano. No entanto, a nota�c~ao pre�xausada pelo Lisp tem largas vantagens sobre a nota�c~ao in�xa:� �E muito f�acil usar operadores que tem um n�umero vari�avel deargumentos, como por exemplo:5

Page 7: Intro Lisp

> (+ 1 2 3)6> (+ 1 2 3 4 5 6 7 8 9 10)55Numa linguagem do tipo Pascal apenas existem operadores un�ari-os ou bin�arios, e �e necess�ario explicitar os operador bin�arios entrecada dois operandos: 1+2+3 ou 1+2+3+4+5+6+7+8+9+10. Um ope-rador deste tipo diz-se in�xo (in signi�ca entre). Se se pretenderum operador tern�ario (ou outro) j�a n~ao se consegue escrever domesmo modo, sendo necess�ario implement�a-lo como uma fun�c~ao.� N~ao existe precedencia entre os operadores. Em Pascal, a ex-press~ao 1+2*3 tem de ser calculada como se se tivesse escrito1+(2*3), e n~ao (1+2)*3, i.e., foi criada uma precedencia que elimi-na as ambiguidades. Essa precedencia pode ser alterada atrav�esdo emprego de parenteses. Em Lisp, as express~oes seriam ne-cessariamente distintas, (+ 1 (* 2 3)) ou (* (+ 1 2) 3), n~aopodendo haver qualquer ambiguidade.� Os operadores que n�os de�nimos usam-se exactamente da mesmamaneira que os operadores da linguagem: operador primeiro eoperandos a seguir. Em Pascal, os operadores s~ao in�xos, i.e.,est~ao entre os operandos, e as fun�c~oes e procedimentos por n�osde�nidos s~ao pre�xos, i.e., primeiro a fun�c~ao ou procedimento edepois os operandos. Isto impede a extens~ao da linguagem de umaforma coerente.Para exempli�car este �ultimo aspecto, consideremos a opera�c~aode exponencia�c~ao em Pascal. Para ser coerente com o resto dalinguagem, deveria existir um operador, por exemplo ** que per-mitisse escrever 3**4 para indicar a quarta potencia de 3. Comoesse operador n~ao existe na linguagem (standard), somos obriga-dos a criar uma fun�c~ao que o substitua mas, neste caso, a sintaxemuda radicalmente. Esta fun�c~ao, como se usa de forma pre�xan~ao se pode tornar coerente com as restantes opera�c~oes do Pascal,como a soma e a multiplica�c~ao. Em Lisp, pelo contr�ario, tan-to podemos escrever (* 3 3 3 3) como de�nir uma fun�c~ao quepermita escrever (** 3 4).A desvantagem da nota�c~ao pre�xa est�a na escrita de combina�c~oescomplexas. A express~ao Pascal 1+2*3-4/5*6, equivale �a express~ao Lisp(- (+ 1 (* 2 3)) (* (/ 4 5) 6)) que embora seja mais simples deler para uma m�aquina, pode ser mais dif��cil de ler para um ser humanodevido �a acumula�c~ao de parenteses. No entanto, esta express~ao podeser escrita de forma mais clara usando indenta�c~ao.A regra para indenta�c~ao de combina�c~oes Lisp �e extremamente sim-ples. Numa linha coloca-se o operador e o primeiro operando. Os res-tantes operandos vem alinhados por debaixo do primeiro.6

Page 8: Intro Lisp

(- (+ 1(* 2 3))(* (/ 4 5)6))Quando a regra de indenta�c~ao n~ao �e su�ciente, usam-se pequenasvaria�c~oes, como seja colocar o operador numa linha e os operandos pordebaixo, por exemplo:(umaoperacaocomumnomemuitogrande1 2 3 4)A indenta�c~ao �e fundamental em Lisp pois �e muito f�acil escreverc�odigo complexo. A grande maioria dos editores preparados para Lisp(Emacs, por exemplo) formatam automaticamente os programas �a me-dida que os escrevemos e mostram o emparelhamento de parenteses.Desta forma, ap�os algum tempo de pr�atica, torna-se muito f�acil escre-ver e ler os programas, por mais complexa que possa parecer a suaestrutura.Exerc��cio 3.2.1 Converta as seguintes express~oes da nota�c~ao in�xa daaritm�etica para a nota�c~ao pre�xa do Lisp:1 + 2 - 31 - 2 * 31 * 2 - 31 * 2 * 3(1 - 2) * 3(1 - 2) + 31 - (2 + 3)2 * 2 + 3 * 3 * 3Solu�c~ao do Exerc��cio 3.2.1(- (+ 1 2) 3) ou (+ 1 (- 2 3)) ou (+ 1 2 (- 3))(- 1 (* 2 3))(- (* 1 2) 3)(* (* 1 2) 3) ou (* 1 (* 2 3)) ou (* 1 2 3)(* (- 1 2) 3)(+ (- 1 2) 3)(- 1 (+ 2 3))(+ (* 2 2) (* 3 3 3)Exerc��cio 3.2.2 Converta as seguintes express~oes da nota�c~ao pre�xado Lisp para a nota�c~ao in�xa da aritm�etica:(* (/ 1 2) 3)(* 1 (- 2 3))(/ (+ 1 2) 3)(/ (/ 1 2) 3)(/ 1 (/ 2 3))(- (- 1 2) 3)(- 1 2 3) 7

Page 9: Intro Lisp

Solu�c~ao do Exerc��cio 3.2.21 / 2 * 31 * (2 - 3)(1 + 2) / 31 / 2 / 31 / (2 / 3)(1 - 2) - 31 - 2 - 33.3 Avalia�c~ao de Combina�c~oesComo j�a vimos, o Lisp considera que o primeiro elemento de uma com-bina�c~ao �e um operador e os restantes s~ao os operandos.O avaliador determina o valor de uma combina�c~ao como o resul-tado de aplicar o procedimento especi�cado pelo operador ao valor dosoperandos. O valor de cada operando �e designado de argumento do pro-cedimento. Assim, o valor da combina�c~ao (+ 1 (* 2 3)) �e o resultadode somar o valor de 1 com o valor de (* 2 3). Como j�a se viu, 1 vale1 e (* 2 3) �e uma combina�c~ao cujo valor �e o resultado de multiplicaro valor de 2 pelo valor de 3, o que d�a 6, e que somado a 1 d�a 7.> (+ 1 2)3> (+ 1 (* 2 3))7Exerc��cio 3.3.1 Calcule o valor das seguintes express~oes Lisp:(* (/ 1 2) 3)(* 1 (- 2 3))(/ (+ 1 2) 3)(/ (/ 1 2) 3)(/ 1 (/ 2 3))(- (- 1 2) 3)(- 1 2 3)(- 1)Solu�c~ao do Exerc��cio 3.3.13/2-111/63/2-4-4-13.4 De�ni�c~ao de Fun�c~oesTal como em matem�atica, pode-se de�nir numa linguagem de progra-ma�c~ao a opera�c~ao de elevar um n�umero ao quadrado. Assim, se preten-dermos determinar o produto de um n�umero por ele pr�oprio, escrevemosa combina�c~ao (* x x) sendo x o n�umero, i.e.8

Page 10: Intro Lisp

> (* 5 5)25> (* 6 6)36 Os n�umeros 5 e 6 e a opera�c~ao * s~ao elementos primitivos. Asexpress~oes (* 5 5) e (* 6 6) s~ao combina�c~oes. �A combina�c~ao gen�erica(* x x) queremos associar um nome, por exemplo, quadrado. Issoequivale a acrescentar uma nova fun�c~ao �a linguagem.A t��tulo de exemplo, vamos de�nir a fun�c~ao quadrado:> (defun quadrado (x)(* x x))quadradoPara se de�nirem novas fun�c~oes em Lisp, �e necess�ario criar uma com-bina�c~ao de quatro elementos. O primeiro elemento desta combina�c~ao�e a palavra defun, que informa o avaliador que estamos a de�nir umafun�c~ao. O nome defun �e uma abreviatura de \de�ne function". O se-gundo elemento �e o nome da fun�c~ao que queremos de�nir, o terceiroelemento �e uma combina�c~ao com os parametros da fun�c~ao e o quartoelemento �e a combina�c~ao que determina o valor da fun�c~ao para aquelesparametros.Quando se d�a uma express~ao desta forma ao avaliador, ele acrescentaa fun�c~ao ao conjunto de fun�c~oes da linguagem, associando-a ao nomeque lhe demos e devolve como valor da de�ni�c~ao o nome da fun�c~aode�nida.A de�ni�c~ao da fun�c~ao quadrado diz que para se determinar o qua-drado de um n�umero x, devemos multiplicar esse n�umero por ele pr�oprio(* x x). Esta de�ni�c~ao associa a palavra quadrado a um procedimen-to. Este procedimento possui parametros e um corpo de express~oes. Deforma gen�erica temos:(defun nome (parametro 1 ... parametro n)corpo)Os parametros de um procedimento s~ao designados parametros for-mais e s~ao os nomes usados no corpo de express~oes para nos referirmosaos argumentos correspondentes. Quando escrevemos no avaliador deLisp (quadrado 5), 5 �e o argumento. Durante o c�alculo da fun�c~ao esteargumento est�a associado ao parametro formal x. Os argumentos deuma fun�c~ao s~ao tamb�em designados parametros actuais.> (quadrado 5)25> (quadrado 6)36 Note-se que a regra de avalia�c~ao de combina�c~oes �e tamb�em v�alidapara as fun�c~oes por n�os de�nidas. Assim, a avalia�c~ao da express~ao9

Page 11: Intro Lisp

(quadrado (+ 1 2)) passa pela avalia�c~ao do operando (+ 1 2). Esteoperando tem como valor 3, valor esse que �e usado pela fun�c~ao no lugardo parametro x. O corpo da fun�c~ao �e ent~ao avaliado, i.e., o valor �nalser�a o da combina�c~ao (* 3 3).O seguinte exemplo mostra um caso um pouco mais complexo. Neleest~ao apresentadas as etapas de avalia�c~ao dos operandos e de avalia�c~aodo corpo da fun�c~ao.(quadrado (quadrado (+ 1 2)))(quadrado (quadrado 3))(quadrado (* 3 3))(quadrado 9)(* 9 9)81 A de�ni�c~ao de fun�c~oes permite-nos associar um procedimento a umnome. Isto implica que o Lisp tem de possuir uma mem�oria onde possaguardar o procedimento e a sua associa�c~ao ao nome dado. Esta mem�oriado Lisp designa-se ambiente.Note-se que este ambiente apenas existe enquanto estamos a traba-lhar com a linguagem. Quando terminamos, perde-se todo o ambiente.Isto implica que, se n~ao queremos perder o trabalho que estivemos a es-crever, devemos escrever as fun�c~oes num �cheiro e ir passando-as para oLisp. A grande maioria das implementa�c~oes do Lisp permite fazer istode forma autom�atica.3.5 S��mbolosA de�ni�c~ao de fun�c~oes em Lisp passa pela utiliza�c~ao dos seus nomes.Nomes para as fun�c~oes e nomes para os parametros das fun�c~oes.Uma vez que o Lisp, antes de avaliar qualquer express~ao, tem dea ler e converter internamente para um objecto, os nomes que criamostem de ter um objecto associado. Esse objecto �e designado por s��mbolo.Um s��mbolo �e, pois, a representa�c~ao interna de um nome.Em Lisp, quase n~ao existem limita�c~oes para a escrita de nomes. Umnome como quadrado �e t~ao bom como + ou como 1+2*3 pois o quesepara um nome dos outros elementos de uma combina�c~ao s~ao apenasparenteses e espa�cos em branco. Por exemplo, �e perfeitamente correctode�nir e usar a seguinte fun�c~ao:> (defun x+y*z (x y z)(+ (* y z) x))x+y*z> (x+y*z 1 2 3)7 Lisp atribui um signi�cado muito especial aos s��mbolos. Reparemosque, quando de�nimos uma fun�c~ao, os parametros formais da fun�c~ao10

Page 12: Intro Lisp

s~ao s��mbolos. O nome da fun�c~ao tamb�em �e um s��mbolo. Quando escre-vemos uma combina�c~ao, o avaliador de Lisp usa a de�ni�c~ao de fun�c~aoque foi associada ao s��mbolo que constitui o primeiro elemento da com-bina�c~ao. Quando, no corpo de uma fun�c~ao, usamos um s��mbolo paranos referimos a um parametro formal, esse s��mbolo tem como valor orespectivo parametro actual que lhe foi associado naquela combina�c~ao.Por esta descri�c~ao se ve que os s��mbolos constituem um dos elementosfundamentais da linguagem Lisp.4 Express~oes CondicionaisExistem muitas opera�c~oes cujo resultado depende da realiza�c~ao de umdeterminado teste. Por exemplo, a fun�c~ao matem�atica abs|que cal-cula o valor absoluto de um n�umero|equivale ao pr�oprio n�umero, seeste �e positivo, ou equivale ao seu sim�etrico se for negativo. Estasexpress~oes, cujo valor depende de um ou mais testes a realizar previa-mente, permitindo escolher vias diferentes para a obten�c~ao do resultado,s~ao designadas express~oes condicionais.No caso mais simples de uma express~ao condicional, existem apenasduas alternativas a seguir. Isto implica que o teste que �e necess�ariorealizar para determinar a via de c�alculo a seguir deve produzir um dedois valores, em que cada valor designa uma das vias.Seguindo o mesmo racioc��cio, uma express~ao condicional com maisde duas alternativas dever�a implicar um teste com igual n�umero deposs��veis resultados. Uma express~ao da forma \caso o valor do testeseja 1, 2 ou 3, o valor da express~ao �e 10, 20 ou 30, respectivamente"�e um exemplo desta categoria de express~oes que, embora seja conside-rada pouco intuitiva, existe nalgumas linguagens (Basic, por exemplo).Contudo, estas express~oes podem ser facilmente reduzidas �a primeira.Basta decompor a express~ao condicional m�ultipla numa composi�c~ao deexpress~oes condicionais simples, em que o teste original �e tamb�em de-composto numa s�erie de testes simples. Assim, poderiamos transformaro exemplo anterior em: \se o valor do teste �e 1, o resultado �e 10, casocontr�ario, se o valor �e 2, o resultado �e 20, caso contr�ario �e 30".4.1 PredicadosDesta forma, reduzimos todos os testes a express~oes cujo valor pode serapenas um de dois, e a express~ao condicional assume a forma de \se. . . ent~ao . . . caso contr�ario . . . ". Nesta situa�c~ao, a fun�c~ao usada comoteste �e denominada predicado e o valor do teste �e interpretado comosendo verdadeiro ou falso. Nesta �optica, o facto de se considerar o valorcomo verdadeiro ou falso n~ao implica que o valor seja de um tipo dedados especial, como o booleano ou l�ogico de algumas linguagens (comoo Pascal), mas apenas que a express~ao condicional considera alguns dosvalores como representando o verdadeiro e os restantes como o falso.11

Page 13: Intro Lisp

Em Lisp, as express~oes condicionais consideram como falso um �unicovalor. Esse valor �e representado por nil. Qualquer outro valor diferentede nil �e considerado como verdadeiro. Assim, do ponto de vista deuma express~ao condicional, qualquer n�umero �e um valor verdadeiro.Contudo, n~ao faz muito sentido para o utilizador humano considerar umn�umero como verdadeiro ou falso, pelo que se introduziu uma constantena linguagem para representar verdade. Essa constante representa-sepor t.Note-se que, se t �e diferente de nil, e se nil �e o �unico valor querepresenta a falsidade, ent~ao t representa verdade. Desta forma, existemmuitos predicados em Lisp cujo valor �e t quando a express~ao por elesdesignada �e considerada verdadeira.> (> 4 3)t> (< 4 3)nilExistem muitos predicados em Lisp. Os predicados num�ericos maisusados s~ao o zerop, =, >, <, >=, <=. O zerop testa se um n�umero �e zeroou diferente de zero.> (zerop 1)nil> (zerop 0)t O facto de zerop terminar com a letra \p" deve-se a uma conven�c~aoadoptada em Common Lisp segundo a qual os predicados devem serdistinguidos das restantes fun�c~oes atrav�es da concatena�c~ao da letra \p"(de predicate) ao seu nome.Apesar da adop�c~ao dos s��mbolos t e nil, conv�em alertar que nemtodos os predicados devolvem t ou nil exclusivamente. Alguns h�a que,quando querem indicar verdade, devolvem valores diferentes de t (e denil, obviamente).4.2 Operadores L�ogicosPara se poder combinar express~oes l�ogicas entre si existem os operadoresand, or e not. O and e o or recebem qualquer n�umero de argumentos.O not s�o recebe um. O valor das combina�c~oes que empregam estesoperadores l�ogicos �e determinado do seguinte modo:� O and avalia os seus argumentos da esquerda para a direita at�eque um deles seja falso, devolvendo este valor. Se nenhum for falsoo and devolve o valor do �ultimo argumento.� O or avalia os seus argumentos da esquerda para a direita at�eque um deles seja verdade, devolvendo este valor. Se nenhum forverdade o or devolve o valor do �ultimo argumento.12

Page 14: Intro Lisp

� O not avalia para verdade se o seu argumento for falso e para falsoem caso contr�ario.Note-se que embora o signi�cado de falso seja claro pois correspondenecessariamente ao valor nil, o signi�cado de verdade j�a n~ao �e t~ao claropois, desde que seja diferente de nil, �e considerado verdade.Exerc��cio 4.2.1 Qual o valor das seguintes express~oes?(and (or (> 2 3) (not (= 2 3))) (< 2 3))(not (or (= 1 2) (= 2 3)))(or (< 1 2) (= 1 2) (> 1 2))(and 1 2 3)(or 1 2 3)(and nil 2 3)(or nil nil 3)Solu�c~ao do Exerc��cio 4.2.1TTT31nil34.3 Selec�c~ao simplesO if �e a express~ao condicional mais simples do Lisp. O if determina avia a seguir em fun�c~ao do valor de uma express~ao l�ogica. A sintaxe doif �e:(if condi�c~aoconsequentealternativa)Para o if, a condi�c~ao �e o primeiro argumento, o consequente nocaso de a condi�c~ao ser verdade �e o segundo argumento e a alternativano caso de ser falso �e o terceiro argumento.> (if (> 4 3)56)5 Uma express~ao if �e avaliada determinando o valor da condi�c~ao. Seela for verdade, �e avaliado o consequente. Se ela for falsa �e avaliada aalternativa.Exerc��cio 4.3.1 De�na uma fun�c~ao soma-grandes que recebe tres n�u-meros como argumento e determina a soma dos dois maiores.13

Page 15: Intro Lisp

Solu�c~ao do Exerc��cio 4.3.1(defun soma-grandes (x y z)(if (>= x y)(if (>= y z)(+ x y)(+ x z))(if (< x z)(+ y z)(+ x y))))Exerc��cio 4.3.2 Escreva uma fun�c~ao que calcule o factorial de um n�u-mero.Solu�c~ao do Exerc��cio 4.3.2(defun fact (n)(if (zerop n)1(* n (fact (- n 1)))))A fun�c~ao fact �e um exemplo de uma fun�c~ao recursiva, i.e., que se refere a ela pr�opria.4.4 Selec�c~ao M�ultiplaPara al�em do if, existem outras express~oes condicionais em Lisp. Ocond �e uma vers~ao mais potente que o if. �E uma esp�ecie de switch-casedo C. A sua sintaxe �e:(cond (condi�c~ao 1 express~ao 1)(condi�c~ao 2 express~ao 2)...(condi�c~ao n express~ao n))Designa-se o par (condi�c~ao i express~ao i) por cl�ausula. O condtesta cada uma das condi�c~oes em sequencia, e quando uma delas avaliapara verdade, �e devolvido o valor da express~ao correspondente, termi-nando a avalia�c~ao. Um exemplo ser�a:> (cond ((> 4 3) 5)(t 6))5 O cond permite uma an�alise 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 fun�c~ao equivalente usando if seria mais complicada.14

Page 16: Intro Lisp

(defun teste (x y z w)(if (> x y)z(if (< (+ x w) (* y z))x(if (= w z)(+ x y)777))))4.5 Formas EspeciaisDada a similitude entre o if e o cond �e l��cito perguntar se precisaremosdos dois. Ser�a que n~ao �e poss��vel de�nir um em termos do outro ?A seguinte de�ni�c~ao parece ser uma resposta:> (defun meu-if (condicao consequente alternativa)(cond (condicao consequente)(t alternativa)))meu-if> (meu-if (> 4 3) 5 6)5> (meu-if (> 3 4) 5 6)6 Quando testamos o meu-if, tudo parece bem. No entanto, se escre-vermos um exemplo ligeiramente diferente, algo corre mal.> (defun divide (x y)(meu-if (zerop y)0(/ x y)))divide> (divide 2 0)Error:.....O que �e que est�a a acontecer? Tentemos seguir as regras do avaliadorpara explicar a situa�c~ao.A avalia�c~ao de combina�c~oes implica a aplica�c~ao da fun�c~ao|que �e oprimeiro elemento da combina�c~ao|aos valores dos restantes elementos.No exemplo (divide 2 0), a aplica�c~ao da fun�c~ao divide ao valor de 2 e0 �e o valor da combina�c~ao (meu-if (zerop 0) 0 (/ 2 0)), que �e aaplica�c~ao da fun�c~ao meu-if ao valor de (zerop 0) que �e t, 0 que vale0 e (/ 2 0) que �e um erro pois n~ao se pode dividir 2 por 0.Desta forma, a fun�c~ao meu-if avalia o seu �ultimo argumento cedodemais, n~ao esperando pelo valor de (zerop 0) para saber se podeavaliar (/ 2 0).Mas h�a situa�c~oes piores. Consideremos a seguinte de�ni�c~ao e apli-ca�c~ao da fun�c~ao factorial. 15

Page 17: Intro Lisp

> (defun meu-fact (n)(meu-if (zerop n)1(* n (meu-fact (- n 1)))))meu-fact> (meu-fact 4)Error:.....Nesta situa�c~ao, a avalia�c~ao de combina�c~oes implica a aplica�c~ao dafun�c~ao que �e o primeiro elemento da combina�c~ao aos valores dos res-tantes elementos. No exemplo (meu-fact 4), a aplica�c~ao da fun�c~aomeu-fact ao valor de 4 �e o valor da combina�c~ao (meu-if (zerop 4) 1(* 4 (meu-fact (- 4 1)))), que �e a aplica�c~ao da fun�c~ao meu-if aovalor de (zerop 4) que �e nil , 1 que vale 1 e (meu-fact 3) que �e aaplica�c~ao da fun�c~ao meu-fact ao valor 3, que �e o valor da combina�c~ao. . .Desta forma, a fun�c~ao meu-if n~ao chega sequer a completar a ava-lia�c~ao dos seus argumentos, n~ao podendo determinar qual dos valores,consequente ou alternativa, retornar, repetindo inde�nidamente a apli-ca�c~ao da fun�c~ao meu-fact a argumentos sucessivamente decrescentes.Suponhamos agora a seguinte interac�c~ao com o Lisp:> (if (> 4 3)100(inexistente))100Segundo o modelo de avalia�c~ao que t��nhamos apresentado, uma com-bina�c~ao �e avaliada aplicando o procedimento que o primeiro elemento dacombina�c~ao especi�ca ao valor dos restantes elementos. Nesta �optica,antes de se escolher a op�c~ao a seguir, o avaliador deveria avaliar todaselas, i.e., 100 cujo valor �e 100 e a aplica�c~ao da fun�c~ao inexistente quedevia produzir um erro pois a fun�c~ao ainda n~ao foi de�nida. Porqueser�a que quando testamos isto n~ao �e produzido nenhum erro?�E evidente que, de algum modo, o if n~ao seguiu as regras do modelode avalia�c~ao de combina�c~oes, caso contr�ario, teria mesmo produzido umerro. Isto sugere que if n~ao �e uma fun�c~ao normal mas sim algo quepossui a sua pr�opria regra de avalia�c~ao.Uma forma especial �e uma express~ao da linguagem que possui a suapr�opria sintaxe e a sua pr�opria regra de avalia�c~ao. �E de salientar queuma forma especial n~ao �e uma fun�c~ao. Ela faz parte da estrutura doavaliador e n~ao do seu ambiente.O defun, o if e o cond s~ao algumas das formas especiais. Mas o ande o or tamb�em s~ao, pois s�o avaliam os operandos que forem necess�ariospara determinar o resultado. O and p�ara de avaliar quando um delesproduzir falso, o or quando um deles produzir verdade.Como uma forma especial possui a sua pr�opria regra de avalia�c~ao,nem tudo o que se faz com uma fun�c~ao se pode fazer com uma formaespecial, e nem tudo o que se faz com uma forma especial se pode fazercom uma fun�c~ao. 16

Page 18: Intro Lisp

Ao contr�ario das outras linguagens que possuem muitas formas es-peciais, Lisp tem muito poucas. Desta forma, a linguagem possui umaregra de avalia�c~ao muito simples e muito bem de�nida, e em que aspequenas excep�c~oes s~ao implementadas pelas formas especiais.No entanto, e ao contr�ario do que acontece na maioria das outraslinguagens, Lisp permite ao utilizador de�nir novas formas cujo compor-tamento �e semelhante ao das formas especiais. Isso �e feito atrav�es demacros, que s~ao formas que s~ao transformadas noutras formas (especiaisou n~ao) durante a interpreta�c~ao ou a compila�c~ao.5 Fun�c~oes5.1 Fun�c~oes RecursivasUma fun�c~ao recursiva �e uma fun�c~ao que se refere a si pr�opria. A ideiaconsiste em utilizar a pr�opria fun�c~ao que estamos a de�nir na sua de�-ni�c~ao.Em todas as fun�c~oes recursivas existe:� Um passo b�asico (ou mais) cujo resultado �e imediatamente conhe-cido.� Um passo recursivo em que se tenta resolver um sub-problema doproblema inicial.Se analisarmos a fun�c~ao factorial, o caso b�asico �e o teste de igualdadea zero (zerop n), o resultado imediato �e 1, e o passo recursivo �e (* n(fact (- n 1))).Geralmente, uma fun�c~ao recursiva s�o funciona se tiver uma express~aocondicional, mas n~ao �e obrigat�orio que assim seja. A execu�c~ao de umafun�c~ao recursiva consiste em ir resolvendo subproblemas sucessivamentemais simples at�e se atingir o caso mais simples de todos, cujo resultado �eimediato. Desta forma, o padr~ao mais comum para escrever uma fun�c~aorecursiva �e:� Come�car por testar os casos mais simples.� Fazer chamada (ou chamadas) recursivas com subproblemas cadavez mais pr�oximos dos casos mais simples.Dado este padr~ao, os erros mais comuns associados �as fun�c~oes recursivass~ao, naturalmente:� N~ao detectar os casos simples� A recurs~ao n~ao diminuir a complexidade do problema.17

Page 19: Intro Lisp

No caso de erro em fun�c~ao recursivas, o mais usual �e a recurs~ao nun-ca parar. O n�umero de chamadas recursivas cresce inde�nidamente at�eesgotar a mem�oria (stack), e o programa gera um erro. Em certas lin-guagens (Scheme) e implementa�c~oes do Common Lisp, isto n~ao �e assim,e pode nunca ser gerado um erro. A recurs~ao in�nita �e o equivalentedas fun�c~oes recursivas aos ciclos in�nitos dos m�etodos iterativos do tipowhile-do e repeat-until.Repare-se que uma fun�c~ao recursiva que funciona perfeitamente pa-ra os casos para que foi prevista pode estar completamente errada paraoutros casos. A fun�c~ao fact �e um exemplo. Quando o argumento �e nega-tivo, o problema torna-se cada vez mais complexo, cada vez mais longedo caso simples. (fact -1) ! (fact -2) ! (fact -3) ! � � �Exerc��cio 5.1.1 Considere uma vers~ao extremamente primitiva da lin-guagem Lisp, em que as �unicas fun�c~oes num�ericas existentes s~ao zerope duas fun�c~oes que incrementam e decrementam o seu argumento emuma unidade, respectivamente, 1+ e 1-. Isto implica que as opera�c~oes>, <, = e similares n~ao podem ser utilizadas. Nesta linguagem, quepassaremos a designar por nanoLisp, abreviadamente �Lisp, de�na opredicado menor, que recebe dois n�umero inteiros positivos e determinase o primeiro argumento �e numericamente inferior ao segundo.Solu�c~ao do Exerc��cio 5.1.1(defun menor (x y)(cond ((zerop y) nil)((zerop x) t)(t (menor (1- x) (1- y)))))Exerc��cio 5.1.2 De�na a opera�c~ao igual? que testa igualdade num�e-rica de inteiros positivos na linguagem �Lisp.Solu�c~ao do Exerc��cio 5.1.2(defun igual? (x y)(cond ((zerop x) (zerop y))((zerop y) nil)(t (igual? (1- x) (1- y)))))Exerc��cio 5.1.3 At�e ao momento, a linguagem �Lisp apenas trabalhacom n�umeros inteiros positivos. Admitindo que as opera�c~oes 1+, 1-e zerop tamb�em funcionam com n�umeros negativos, de�na a fun�c~aonegativo que recebe um n�umero inteiro positivo e retorna o seu sim�e-trico. Assim, pretendemos obter: (negativo 3) ! -3.Solu�c~ao do Exerc��cio 5.1.3(defun negativo (x)(if (zerop x)x(1- (negativo (1- x)))))Exerc��cio 5.1.4 Agora que a linguagem �Lisp pode tamb�em trabalharcom n�umeros inteiros negativos, de�na o predicado positivo?, que re-cebe um n�umero e indica se ele �e positivo ou n~ao.18

Page 20: Intro Lisp

Solu�c~ao do Exerc��cio 5.1.4(defun positivo? (x)(positivo-aux x x))(defun positivo-aux (x+ x-)(cond ((zerop x-) t)((zerop x+) nil)(t (positivo-aux (1+ x+) (1- x-)))))Exerc��cio 5.1.5 De�na o teste de igualdade de dois n�umeros na lin-guagem �Lisp contemplando a possibilidade de trabalhar tamb�em comn�umeros inteiros negativos.Solu�c~ao do Exerc��cio 5.1.5(defun igual? (x y)(igual-aux x x y y))(defun igual-aux (x+ x- y+ y-)(cond ((zerop x+) (zerop y+))((zerop x-) (zerop y-))((or (zerop y+) (zerop y-)) nil)(t (igual-aux (1+ x+) (1- x-) (1+ y+) (1- y-)))))Exerc��cio 5.1.6 De�na a fun�c~ao sim�etrico de um n�umero qualquerna linguagem �Lisp.Solu�c~ao do Exerc��cio 5.1.6(defun simetrico (x)(simetrico-aux x x 0 0))(defun simetrico-aux (x+ x- -x+ -x-)(cond ((zerop x+) -x+)((zerop x-) -x-)(t (simetrico-aux (1+ x+) (1- x-) (1+ -x+) (1- -x-)))))Exerc��cio 5.1.7 �E poss��vel de�nir a soma de dois n�umeros inteiros po-sitivos em �Lisp, i.e., apenas recorrendo �as fun�c~oes 1+ e 1- que somame subtraem uma unidade, respectivamente. De�na a opera�c~ao soma.Solu�c~ao do Exerc��cio 5.1.7(defun soma1 (a b)(if (zerop a)b(1+ (soma1 (1- a) b))))Exerc��cio 5.1.8 Generalize a fun�c~ao de soma de modo a poder recebern�umeros inteiros positivos e negativos.Solu�c~ao do Exerc��cio 5.1.8(defun soma-geral (a b)(soma-iter a a b b))(defun soma-iter (a+ a- b+ b-)(cond ((zerop a+) b-)((zerop a-) b+)(t (soma-iter (1+ a+) (1- a-)(1+ b+) (1- b-)))))19

Page 21: Intro Lisp

Exerc��cio 5.1.9 Do mesmo modo que a soma pode ser de�nida exclu-sivamente em termos de sucessor 1+ e predecessor 1-, a multiplica�c~aopode ser de�nida exclusivamente em termos da soma. De�na a fun�c~aomult que recebe dois n�umero e os multiplica usando a fun�c~ao soma.Solu�c~ao do Exerc��cio 5.1.9(defun mult (a b)(if (zerop a 0)0(soma (mult (1- a) b) b)))5.2 Depura�c~ao de Fun�c~oesEm Lisp, �e poss��vel analizar as chamadas �as fun�c~oes atrav�es da formaespecial trace. Ela recebe o nome das fun�c~oes que se pretendem anali-zar e altera essas fun�c~oes de forma a que elas escrevam no terminal aschamadas com os respectivos argumentos em cada chamada, e os valoresretornados. Esta informa�c~ao �e extremamente �util para a depura�c~ao dasfun�c~oes.Para se parar a depura�c~ao de uma fun�c~ao, usa-se a forma especialuntrace, que recebe o nome da fun�c~ao ou fun�c~oes de que se pretendetirar o trace.Se se usar a forma especial trace sem argumentos ela limita-se aindicar quais as fun�c~oes que est~ao em trace. Se se usar a forma especialuntrace sem argumentos, s~ao retiradas de trace todas as fun�c~oes queestavam em trace.Exerc��cio 5.2.1 Experimentar o trace do fact.Solu�c~ao do Exerc��cio 5.2.1> (trace fact)(fact)> (fact 5)0: (fact 5)1: (fact 4)2: (fact 3)3: (fact 2)4: (fact 1)5: (fact 0)5: returned 14: returned 13: returned 22: returned 61: returned 240: returned 1201205.3 Fun�c~oes de Ordem SuperiorVimos que as fun�c~oes permitem-nos dar um nome a um conjunto deopera�c~oes e trat�a-lo como um todo. Muitas vezes, por�em, h�a um padr~aoque se repete, variando apenas uma ou outra opera�c~ao. Por exemplo,consideremos uma fun�c~ao que soma os quadrados de todos os inteirosentre a e b, Pbi=a i2. 20

Page 22: Intro Lisp

(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 fun�c~ao que soma as raizes quadradasde todos os inteiros entre a e b, Pbi=api.(defun soma-raizes (a b)(if (> a b)0(+ (sqrt a) (soma-raizes (1+ a) b))))> (soma-raizes 1 4)6.146264369941973Em ambas as fun�c~oes existe uma soma de express~oes matem�aticasentre dois limites, i.e., existe um somat�orio Pbi=a f(i). O somat�orio �euma abstrac�c~ao matem�atica para uma soma de n�umeros. Dentro dosomat�orio �e poss��vel colocar qualquer opera�c~ao matem�atica relativa ao��ndice do somat�orio. Esse ��ndice varia desde o limite inferior at�e aolimite superior.Para que se possa de�nir o processo do somat�orio na nossa linguagemde programa�c~ao ela deve ser capaz de fazer abstra�c~ao sobre as pr�opriasopera�c~oes a realizar, e deve poder us�a-las como se de parametros doprocesso se tratasse. O padr~ao a executar seria qualquer coisa do estilo:(defun soma-??? (a b)(if (> a b)0(+ (aplica-??? a) (soma-??? (1+ a) b))))O s��mbolo ??? representa a opera�c~ao a realizar dentro do somat�orio,e que pretendemos transformar num parametro.Em Lisp, para se aplicar uma fun�c~ao que �e o valor de um argumento,usa-se a fun�c~ao funcall, que recebe essa fun�c~ao e os seus parametrosactuais. Para se indicar que pretendemos a fun�c~ao associada a umdeterminado s��mbolo, usa-se a forma especial function.> (function 1+)#<compiled-function 1+>> (funcall (function 1+) 9)10> (defun teste (f x y) (funcall f x y))teste> (teste (function +) 1 2)3 21

Page 23: Intro Lisp

Deste modo j�a podemos escrever a implementa�c~ao do nosso so-mat�orio:(defun somatorio (func a b)(if (> a b)0(+ (funcall func a) (somatorio func (1+ a) b))))Podemos testar a fun�c~ao para o exemplo anterior.> (somatorio (function quadrado) 1 4)30 Como se ve, a fun�c~ao somatorio representa a abstrac�c~ao associa-da ao somat�orio matem�atico. Para isso, ela recebe uma fun�c~ao comoargumento e aplica-a aos sucessivos inteiros inclu��dos no somat�orio.As fun�c~oes que recebem e manipulam outras fun�c~oes s~ao designadasfun�c~oes de ordem superior.Exerc��cio 5.3.1 Repare-se que, tal como a fun�c~ao somat�orio, podemosescrever a abstrac�c~ao correspondente ao produt�orio (tamb�em designadopiat�orio)Qbi=a f(i). Esta abstrac�c~ao corresponde ao produto dos valoresde uma determinada express~ao para todos os inteiros de um intervalo.Escreva uma fun�c~ao Lisp que a implemente.Solu�c~ao do Exerc��cio 5.3.1(defun produtorio (func a b)(if (> a b)1(* (funcall func a) (produtorio func (1+ a) b))))Exerc��cio 5.3.2 Escreva a fun�c~ao factorial usando o produt�orio.Solu�c~ao do Exerc��cio 5.3.2 Uma vez que n~ao queremos aplicar nenhuma fun�c~ao ao��ndicedo produt�orio, temos de de�nir a fun�c~ao identidade.(defun identidade (x) x)(defun fact (n)(produtorio (function identidade) 1 n))Exerc��cio 5.3.3 Quer o somat�orio, quer o produt�orio podem ser vis-tos como casos especiais de uma outra abstrac�c~ao ainda mais gen�erica,designada acumulat�orio. Nesta abstrac�c~ao, quer a opera�c~ao de combi-na�c~ao dos elementos, quer a fun�c~ao a aplicar a cada um, quer o valorinicial, quer o limite inferior, quer a passagem para o elemento seguinte(designado o sucessor), quer o limite superior s~ao parametros. De�naesta fun�c~ao. De�na o somat�orio e o produt�orio em termos de acumu-lat�orio. 22

Page 24: Intro Lisp

Solu�c~ao do Exerc��cio 5.3.3(defun acumulatorio (combina func inicial a suc b)(if (> a b)inicial(funcall combina(funcall func a)(acumulatorio combinafuncinicial(funcall suc a)sucb))))(defun somatorio (func a b)(acumulatorio (function +) func 0 a (function 1+) b))(defun produtorio (func a b)(acumulatorio (function *) func 1 a (function 1+) b))5.4 Especializa�c~aoPor vezes, embora exista uma abstrac�c~ao de ordem superior, �e maissimples pensar numa de ordem inferior, quando esta �ultima �e um casoparticular da primeira.Embora o acumulat�orio seja uma abstrac�c~ao de muito alto n��vel,o grande n�umero de parametros que ela possui torna dif��cil a quem leperceber o que se est�a a fazer. Assim, quando se repete muito um dadopadr~ao de utiliza�c~ao, pode valer a pena criar casos particulares cuja lei-tura seja imediata. Estes casos particulares correspondem a �xar algunsdos parametros da abstrac�c~ao superior. Vimos que as abstrac�c~oes ma-tem�aticas do somat�orio e do produt�orio se podiam escrever em termosda abstrac�c~ao de ordem superior acumulat�orio, i.e., s~ao especializa�c~oesdesta �ultima em que a opera�c~ao de combina�c~ao, o valor inicial e a ope-ra�c~ao de sucessor est~ao �xas.Um outro exemplo desse caso particular �e a fun�c~ao produto. Oproduto �e muito semelhante ao produt�orio, mas com a diferen�ca quea passagem de um elemento ao seguinte �e dada por uma fun�c~ao, e n~aoincrementando o��ndice do produt�orio. Quanto ao resto �e absolutamenteigual. Assim, podemos de�nir o produto em termos do acumulat�orio�xando a opera�c~ao de combina�c~ao no * e o valor inicial no 1.(defun produto (func a suc b)(acumulatorio (function *) func 1 a suc b))Exerc��cio 5.4.1 O produto �e uma abstra�c~ao de ordem superior ou in-ferior �a do produt�orio? Qual delas �e que �e um caso particular da outra?Solu�c~ao do Exerc��cio 5.4.1 Uma vez que o produto apenas difere do produt�orio por n~aoimpor a opera�c~ao de sucessor, �e uma abstrac�c~ao de ordem superior, podendo ser especializadapara o produt�orio:(defun produtorio (func a b)(produto func a (function 1+) b))Exerc��cio 5.4.2 Tal como a fun�c~ao produto, existe uma abstrac�c~aocorrespondente designada soma. De�na-a.23

Page 25: Intro Lisp

Solu�c~ao do Exerc��cio 5.4.2(defun soma (func a suc b)(acumulatorio (function +) func 0 a suc b))Exerc��cio 5.4.3 Sabe-se que a soma 11�3 + 15�7 + 19�11 + � � � converge(muito lentamente) para �8 . De�na a fun�c~ao que calcula a aproxima�c~aoa � at�e ao n-�esimo termo da soma. Determine � at�e ao termo 2000.Solu�c~ao do Exerc��cio 5.4.3(defun pi/8-seguinte (x)(+ x 4))(defun pi/8-func (x)(/ 1.0 (* x (+ x 2))))(defun pi (n)(* 8 (soma (function pi/8-func)1(function pi/8-seguinte)n)))> (pi 2000)3.1405926538397935.5 LambdasComo se viu pelo exemplo anterior, para que pudessemos implemen-tar �=8 em fun�c~ao de acumulat�orio tivemos de escrever as fun�c~oespi/8-seguinte e pi/8-func, cuja utilidade �e muito limitada. Elas ape-nas servem para este exemplo. Por esse motivo, n~ao tem muito sentidoestar a de�ni-las no ambiente do avaliador. O que pretendiamos era t~aosomente que fosse poss��vel especi�car as express~oes que aquelas fun�c~oescalculam, independentemente do nome que se lhes pudesse atribuir.Para isso, a linguagem Lisp fornece as lambdas. Uma lambda �euma fun�c~ao com todas as caracter��sticas das restantes mas que n~ao est�aassociada a nenhum s��mbolo. Pode ser vista como uma fun�c~ao semnome. A sintaxe da lambda �e igual �a da defun, mas em que se omite onome.> ((lambda (z) (+ z 3)) 2)5 Como se ve, uma lambda pode ser usada onde se usaria uma fun�c~ao.Isto permite simpli�car o exerc��cio da aproxima�c~ao a � sem ter de de�nirquaisquer outras fun�c~oes:(defun pi (n)(* 8 (soma (function (lambda (x) (/ 1.0 (* x (+ x 2)))))1(function (lambda (x) (+ x 4)))n))) 24

Page 26: Intro Lisp

Exerc��cio 5.5.1 Imagine uma fun�c~ao f ao longo de um intervalo [a; b].Essa fun�c~ao dever�a apresentar um m�aximo nesse intervalo, i.e., um valorentre a e b para o qual a fun�c~ao toma o seu valor m�aximo. Usando oacumulat�orio, escreva a fun�c~ao maximo-func que recebe uma fun�c~aoe um intervalo e encontra o m�aximo. Para determinar o maior entredois n�umero pode usar a fun�c~ao Lisp max. Teste maximo-func para oexemplo y = x� x2 no intervalo [0; 2] com uma tolerancia de 0:01.Solu�c~ao do Exerc��cio 5.5.1(defun maximo-func (func a b)(acumulatorio (function max)func(funcall func a)a(function (lambda (x) (+ x 0.01)))b))Matematicamente, o maximo de x � x2 ocorre quando a derivada da fun�c~ao se anula,i.e., quando 1� 2x = 0, x = 1=2. O valor m�aximo ser�a ent~ao de y = 1=2� (1=2)2 = 1=4.Testando, temos:> (maximo-func (function (lambda (x) (- x (quadrado x))))0 2)0.25As lambdas s~ao a essencia do Lisp. A qualquer fun�c~ao correspondeuma lambda. Na realidade, a forma especial defun n~ao faz mais do quecriar uma lambda com os parametros e o corpo da fun�c~ao e associ�a-la ao nome da fun�c~ao que se est�a a de�nir. Quando a forma especialfunction recebe um nome (um s��mbolo) ela devolve a lambda associadaa esse nome.A designa�c~ao de lambda (�) deriva duma �area da matem�atica quese dedica ao estudo dos conceitos de fun�c~ao e de aplica�c~ao de fun�c~ao,e que se designa por c�alculo-�. O c�alculo-� �e uma ferramenta muitoutilizada para estudar a semantica das linguagens de programa�c~ao.As lambdas possuem ainda muitas outras utilidades para al�em damera cria�c~ao de fun�c~oes sem nome.5.6 Vari�aveis LocaisImagine-se que pretendemos escrever uma fun�c~ao que calcula a seguinteexpress~ao: f(x; y) = (1+x2y)2x+(1+x2y)y. Em Lisp, temos a seguintetradu�c~ao:(defun f (x y)(+ (* (quadrado (+ 1 (* (quadrado x) y))) x)(* (+ 1 (* (quadrado x) y)) y)))Como se ve, a express~ao (+ 1 (* (quadrado x) y)) aparece repe-tida duas vezes. Isto, para al�em de di�cultar a leitura da fun�c~ao torna-amenos e�ciente pois aquela express~ao vai ter de ser calculada duas vezes.Quase todas as linguagens de programa�c~ao fornecem os meios pa-ra se criarem vari�aveis locais, tempor�arias, para guardarem resultadosparciais que v~ao ser utilizados noutros s��tios. Em Lisp, isso pode serobtido de�nindo fun�c~oes interm�edias:25

Page 27: Intro Lisp

(defun f (x y)(f* x y (+ 1 (* (quadrado x) y))))(defun f* (x y temp)(+ (* (quadrado temp) x)(* temp y)))Mas como j�a vimos, n~ao h�a necessidade de se de�nir uma fun�c~ao f*no ambiente pois podemos usar as lambdas directamente.(defun f (x y)((lambda (temp)(+ (* (quadrado temp) x)(* temp y)))(+ 1 (* (quadrado x) y))))Repare-se que dentro do corpo da lambda h�a referencias quer aosparametros da lambda (temp) quer aos parametros da fun�c~ao f em quea lambda est�a inserida.Uma vez que n~ao �e muito conveniente separar os valores das va-ri�aveis, Lisp providencia uma forma especial designada let que �e con-vertida para uma lambda. A sua sintaxe �e:(let ((var 1 exp 1)(var 2 exp 2)...(var n exp n))corpo )Quando se avalia um let, cada s��mbolo var i �e associado ao valorda express~ao correspondente exp i (em paralelo) e em seguida o corpo�e avaliado como se as referencias a var i estivessem substituidas pelosvalores correspondentes de exp i. Esta forma especial �e absolutamenteequivalente a escrever:((lambda (var 1 var 2 ... var n)corpo )exp 1exp 2 ... exp n)Embora equivalentes, a utiliza�c~ao da forma let �e mais f�acil de ler.Este g�enero de formas especiais que se limitam a ser uma tradu�c~aomais agrad�avel para outras formas especiais s~ao designadas por a�c�ucarsint�atico. O let �e a�cucar sint�atico para uma lambda.Exerc��cio 5.6.1 Usando o let, reescreva a fun�c~ao f anterior.Solu�c~ao do Exerc��cio 5.6.1(defun f (x y)(let ((temp (+ 1 (* (quadrado x) y))))(+ (* (quadrado temp) x)(* temp y)))) 26

Page 28: Intro Lisp

Exerc��cio 5.6.2 Qual o valor das seguintes express~oes:Solu�c~ao do Exerc��cio 5.6.2> (let ((x 10))(+ (let ((x 20))(+ x 5))(+ x 2)))37> (let ((x 10))(+ (let ((x 11) (y (+ x 4)))(+ y x))(+ x 2)))375.7 Fun�c~oes LocaisTal como se podem criar vari�aveis locais com a forma especial let,tamb�em �e poss��vel criar fun�c~oes locais com a forma especial flet. Asua sintaxe �e extremamente parecida com a do let, s�o que o valor decada vari�avel �e a de�ni�c~ao de uma fun�c~ao.A t��tulo de exemplo, estude-se a seguinte de�ni�c~ao:(defun teste (x)(flet ((f-local1 (y) (+ x y))(f-local2 (z) (* x z))(f-local3 (x) (+ x 2)))(+ (f-local1 x) (f-local2 x) (f-local3 x))))> (teste 2)12> (f-local1 2)Error: Undefined function F-LOCAL1As fun�c~oes f-local1, f-local2 e f-local3 s~ao locais �a fun�c~aoteste, sendo estabelecidas a cada aplica�c~ao desta fun�c~ao. Tal comoas vari�aveis do let, as fun�c~oes locais de um flet n~ao se podem referirumas �as outras, pois s~ao avaliadas em paralelo. Isto implica, tamb�em,que n~ao se podem referir a si pr�opias, impedindo a cria�c~ao de fun�c~oeslocais recursivas.Atendendo a que a maioria das vezes as fun�c~oes que de�nimos s~aorecursivas, independentemente de serem locais ou n~ao, interessa possuirum meio de o podermos fazer. A forma especial labels providencia estapossibilidade. A sua sintaxe �e igual �a do flet, mas a sua semantica �eligeiramente diferente. Para o flet, o ambito do nome das fun�c~oesde�nidas �e apenas o corpo do flet. Para o labels, esse ambito �eextendido �a pr�opria forma especial. Isto permite que se possam de�nirfun�c~oes locais recursivas ou mutuamente recursivas.6 Ambito e Dura�c~aoQuando uma determinada express~ao que se d�a ao avaliador faz referenciaa uma vari�avel, ele precisa de saber qual �e o valor dessa vari�avel. At�e27

Page 29: Intro Lisp

agora, vimos que as lambdas eram o �unico meio de estabelecer vari�aveis.Os parametros de uma lambda de�nem um contexto em que as vari�aveistomam um determinado valor. O contexto abarca todo o corpo dalambda.6.1 Ambito de uma ReferenciaDesigna-se por ambito de uma referencia, a zona textual em que elapode ser correctamente referida. Assim, o ambito de um parametro deuma lambda �e a zona textual correspondente ao corpo da fun�c~ao. Istoimplica que qualquer parametro da lambda pode ser referido dentrodesse corpo, mas n~ao fora dele.> ((lambda (z) (+ z z)) 3)6> (+ z z)Error: Unbound variable ZUma vez que o ambito de um parametro �e o corpo da lambda cor-respondente, �e poss��vel 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 parauma vari�avel. Quando se encontra uma referencia a uma vari�avel, o seuvalor �e dado pela liga�c~ao correspondente ao contexto mais pequeno. Sen~ao existe qualquer liga�c~ao em nenhum dos contextos, a vari�avel diz-sen~ao ligada. A avalia�c~ao de vari�aveis n~ao ligadas produz um erro.Quando uma mesma vari�avel aparece ligada repetidamente em con-textos sucessivos, a liga�c~ao mais \interior" obscurece todas as \exterio-res". Isto n~ao quer dizer que as liga�c~oes exteriores sejam destru��das.Elas s~ao apenas localmente substitu��das durante a avalia�c~ao do corpomais interior. Assim, temos o seguinte exemplo:> (let ((x 10))(+ (let ((x 20))x)x))30 Diz-se que uma referencia �e de ambito l�exico quando ela s�o podeser correctamente referida dentro da regi~ao textual da express~ao que acriou. 28

Page 30: Intro Lisp

Diz-se que uma referencia �e de ambito vago (ou inde�nido) quan-do ela pode ser correctamente referida a partir de qualquer regi~ao doprograma.Exerc��cio 6.1.1 Que tipo de ambito possui uma vari�avel de um let?Que tipo de ambito possui o nome de uma fun�c~ao?Solu�c~ao do Exerc��cio 6.1.1 Qualquer vari�avel de um let possui ambito l�exico. Comoas fun�c~oes podem ser livremente referidas de qualquer ponto do programa, o seu nome �e deambito vago.6.2 Dura�c~ao de uma ReferenciaDesigna-se por dura�c~ao de uma referencia o intervalo de tempo duranteo qual ela pode ser correctamente referida.Diz-se que uma referencia �e de dura�c~ao dinamica quando s�o podeser correctamente referida no intervalo de tempo que decorre durante aavalia�c~ao da express~ao que a criou.Diz-se que uma referencia �e de dura�c~ao vaga (ou inde�nida) quandopode ser correctamente referida em qualquer instante ap�os a avalia�c~aoda express~ao que a criou.Em Pascal, os parametros de uma fun�c~ao ou procedimento temambito l�exico e dura�c~ao dinamica. A liga�c~ao dos parametros formaisaos parametros actuais existe apenas durante a execu�c~ao da fun�c~ao ouprocedimento.Em Scheme ou Common Lisp, os parametros das lambdas tem am-bito l�exico e dura�c~ao vaga. Isto implica que �e poss��vel aceder a umavari�avel mesmo depois de a fun�c~ao que a criou ter terminado, desde queessa vari�avel seja acedida dentro da regi~ao textual dessa fun�c~ao.A t��tulo de exemplo, se tentarmos escrever a fun�c~ao que determina om�aximo de uma fun�c~ao num�erica mas de forma a que ela possa receberuma tolerancia como parametro, podemos ser conduzidos a qualquercoisa do g�enero:(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 fun�c~ao que estabelece o incrementorefere-se �a vari�avel livre tol. Uma das capacidades fundamentais daslambdas �e a sua referencia a vari�aveis livres. Uma vari�avel diz-se li-vre numa lambda quando n~ao �e um dos parametros da lambda onde �ereferida.Quando se aplica uma lambda aos seus argumentos, os parametrostomam como valor os argumentos correspondentes, enquanto que as29

Page 31: Intro Lisp

vari�aveis livres tomam como valor o valor da primeira vari�avel igual nocontexto em que a lambda �e de�nida. �E por esse motivo que quando alambda que realiza o incremento �e aplicada a um n�umero, ela sabe qualo valor correcto de tol. Ele �e dado pelo contexto l�exico (i.e. textual)em que a lambda foi de�nida.Exerc��cio 6.2.1 Analise os seguintes casos:((lambda (x) (+ x y)) 10)(let ((y 5)) ((lambda (x) (+ x y)) 10))Solu�c~ao do Exerc��cio 6.2.1 ((lambda (x) (+ x y)) 10) gera um erro pois no momentoda de�ni�c~ao da lambda n~ao existe qualquer valor para y, enquanto que no caso (let ((y 5))((lambda (x) (+ x y)) 10)) o avaliador devolve 15 pois y vale 5 no contexto envolvente dalambda.Exerc��cio 6.2.2 Explique a seguinte fun�c~ao:(defun incrementa (delta)(function (lambda (x) (+ x delta))))Solu�c~ao do Exerc��cio 6.2.2 A fun�c~ao incrementa devolve uma fun�c~ao que possui umavari�avel livre delta. Uma vez que os parametros das fun�c~oes possuem ambito l�exico e ade�ni�c~ao da lambda a devolver �e feita dentro do corpo da fun�c~ao incrementa, aquela lambdapode referir-se livremente �a vari�avel delta. Por outro lado, uma vez que os parametrosdas fun�c~oes possuem dura�c~ao vaga, mesmo depois de a fun�c~ao incrementa ter terminado, alambda devolvida pode referir-se livremente �a vari�avel delta.Exerc��cio 6.2.3 Use a fun�c~ao incrementa para reescrever a fun�c~aoque determina o m�aximo de uma fun�c~ao num�erica|maximo-func.Solu�c~ao do Exerc��cio 6.2.3(defun maximo-func (func a b tol)(acumulatorio (function max)func(funcall func a)a(incrementa tol)b))7 DadosEm todos os exemplos anteriores temos apresentado fun�c~oes essencial-mente num�ericas. Os n�umeros s~ao um exemplo dos dados que os proce-dimentos podem usar e produzir. Vamos agora apresentar outros tiposde dados que se podem utilizar.7.1 �AtomosJ�a vimos que os n�umeros s~ao um dos elementos primitivos do Lisp. Oss��mbolos (nomes de fun�c~oes e vari�aveis) s~ao outro dos exemplos. Esteselementos dizem-se at�omicos, pois n~ao podem ser decompostos.Para se testar se um elemento �e at�omico pode-se usar a fun�c~ao atom:30

Page 32: Intro Lisp

> (atom 1)TExerc��cio 7.1.1 Quando tentamos testar se o s��mbolo xpto �e at�omico,e escrevemos a express~ao (atom xpto) recebemos um erro. Explique oque se passa.Solu�c~ao do Exerc��cio 7.1.1 Segundo as regras do modelo de avalia�c~ao, xpto �e umareferencia a uma vari�avel. (atom xpto) pretende determinar se o valor de xpto �e um �atomo.Como a vari�avel xpto n~ao tem valor, o Lisp gera um erro.Para que o Lisp possa considerar um s��mbolo por si s�o, i.e., semo considerar uma vari�avel, temos de usar a forma especial quote, quedevolve o seu argumento sem o avaliar.> (quote xpto)XPTO> (atom (quote xpto))T A raz~ao de ser do quote est�a associada �a distin�c~ao que existe entreas frases \Diz-me o teu nome" e \Diz-me `o teu nome' ". No primeirocaso a frase tem de ser completamente interpretada para que o ouvintepossa dizer qual �e o seu pr�oprio nome. No segundo caso, as plicas est~aol�a para indicar ao ouvinte que ele n~ao deve tomar o que est�a entreplicas �a letra, e deve limitar-se a dizer \o teu nome". As plicas servem,pois, para distinguir o que deve ser tomado como �e, e o que deve serinterpretado.Existem v�arias fun�c~oes para se testar a igualdade de elementos pri-mitivos. Como j�a se viu, a igualdade de n�umeros �e dada pela fun�c~ao =.Esta fun�c~ao compara n�umeros de todos os tipos.> (= 1 1)t> (= 1 1.0)t Em Lisp, existe unicidade de s��mbolos, i.e., dados dois s��mbolos como mesmo nome, eles representam necessariamente o mesmo objecto, ouseja, o mesmo espa�co da mem�oria do computador. Isto permite que acompara�c~ao entre dois s��mbolos possa ser feita testando se eles represen-tam o mesmo espa�co, i.e., se apontam para a mesma zona da mem�oria.A fun�c~ao eq realiza essa opera�c~ao.> (eq (quote a) (quote a))t> (eq (quote a) (quote b))nilNote-se que a fun�c~ao eq pode n~ao funcionar correctamente quandoaplicada a n�umeros. 31

Page 33: Intro Lisp

> (eq 1 1)t> (eq 111111111111111111111111111111111111111111111111111111111111111111111111)nilA raz~ao do comportamento incoerente da fun�c~ao eq em n�umerosdeve-se ao facto de os n�umeros pequenos poderem ser representadoscomo dados imediatos, enquanto os n�umeros grandes ocupam espa�co namem�oria, em zonas diferentes.Para se testar s��mbolos e n�umeros do mesmo tipo existe uma outrafun�c~ao designada eql.> (eql (quote a) (quote a))t> (eql 111111111111111111111111111111111111111111111111111111111111)t> (eql 1 1.0)nil7.2 Combina�c~oes de DadosPara se combinar dados, �e preciso que a linguagem possua uma \cola"que permita agrupar esses dados. Em Lisp, essa \cola" �e implementadapela fun�c~ao cons.A fun�c~ao cons cria um novo objecto que consiste na aglomera�c~aode dois outros objectos, argumentos do cons. O cons �e para o Lisp omesmo que as tabelas (arrays) e estruturas (records, structs) s~ao paraas outras linguagens como Pascal ou C.> (cons 1 2)(1 . 2)> (cons (cons 1 2) 3)((1 . 2) . 3)Dada uma combina�c~ao de objectos (um \cons") podemos obter oprimeiro elemento da combina�c~ao usando a fun�c~ao car e o segundousando a fun�c~ao cdr.> (car (cons 1 2))1> (cdr (cons 1 2))2 Note-se que aplicar o car ou o cdr a um cons n~ao destroi esse cons.O cons de dois objectos �e designado um par com ponto (dotted pair).Como �e natural, um cons n~ao �e um objecto at�omico:> (atom (cons 1000 2000))nil 32

Page 34: Intro Lisp

Note-se que para combinar dois quaisquer objectos �e necess�ario ar-ranjar espa�co na mem�oria para indicar qual o primeiro objecto e qual osegundo. �E a fun�c~ao cons que arranja esse espa�co. De cada vez que ela�e chamada, mesmo que seja para juntar os mesmos objectos, ela arranjaum novo espa�co de mem�oria. Isto implica que a fun�c~ao eq �e semprefalsa para o cons.> (eq (cons 1 2) (cons 1 2))nilExerc��cio 7.2.1 De�na a fun�c~ao igual? que recebe dois objectos etesta se s~ao os mesmos ou se s~ao combina�c~oes de objectos iguais, i.e.> (igual? 1 1)t> (igual? (cons (cons 1 2) (cons 2 3))(cons (cons 1 2) (cons 2 3)))tSolu�c~ao do Exerc��cio 7.2.1(defun igual? (obj1 obj2)(cond ((atom obj1) (and (atom obj2) (eql obj1 obj2)))((atom obj2) nil)((igual? (car obj1) (car obj2))(igual? (cdr obj1) (cdr obj2)))))Esta fun�c~ao j�a existe em Lisp e designa-se equal.7.3 Abstrac�c~ao de DadosA abstra�c~ao de dados �e uma forma de aumentar a modularidade. Sedecidirmos implementar n�umeros racionais, teremos de pensar em com-binar dois n�umeros|o numerador e o denominador, e de os tratar co-mo um todo. Se n~ao fosse poss��vel considerar aquela combina�c~ao den�umeros como uma abstra�c~ao (um racional), toda a sua utiliza�c~ao seriaextremamente dif��cil. Por exemplo, para se somar dois n�umeros racio-nais, seria necess�ario usar uma opera�c~ao para o c�alculo do n�umerador, eoutra opera�c~ao para o c�alculo do denominador, em vez de se pensar nu-ma opera�c~ao gen�erica, soma-racional, que receberia dois argumentos|dois racionais|e calcularia um terceiro n�umero|um racional.Para nos abstrairmos da complexidade de um n�umero racional, deve-mos de�nir fun�c~oes que os manipulam internamente. Podemos come�carpor de�nir uma fun�c~ao que constroi um n�umero racional a partir donumerador e do denominador.(defun racional (numerador denominador)(cons numerador denominador))Para sabermos qual �e o numerador ou o denominador de um dadon�umero racional podemos de�nir: 33

Page 35: Intro Lisp

(defun numerador (racional)(car racional))(defun denominador (racional)(cdr racional))Assim, j�a j�a podemos escrever a fun�c~ao que calcula a soma de doisracionais, usando a f�ormula n1d1 + n2d2 = n1d2+n2d1d1d2 .(defun +racional (r1 r2)(racional (+ (* (numerador r1) (denominador r2))(* (numerador r2) (denominador r1)))(* (denominador r1) (denominador r2))))Para simpli�car a escrita de racionais, podemos de�nir uma fun�c~aoque escreve um racional de acordo com uma conven�c~ao qualquer.(defun escreve-racional (racional)(format t "~a/~a" (numerador racional)(denominador racional)))Agora, j�a podemos calcular a seguinte express~ao:> (escreve-racional (+racional (racional 1 2)(racional 1 3)))5/6Exerc��cio 7.3.1 De�na as restantes fun�c~oes do tipo abstracto de in-forma�c~ao racional: -racional, *racional, e /racional.Solu�c~ao do Exerc��cio 7.3.1(defun -racional (r1 r2)(racional (- (* (numerador r1) (denominador r2))(* (numerador r2) (denominador r1)))(* (denominador r1) (denominador r2))))(defun *racional (r1 r2)(racional (* (numerador r1) (numerador r2))(* (denominador r1) (denominador r2))))(defun /racional (r1 r2)(racional (* (numerador r1) (denominador r2))(* (denominador r1) (numerador r2))))Como se ve, tratamos um n�umero racional como um s�o objecto, eseparamos a parte do programa que usa racionais da parte que os im-plementa como pares de inteiros. Esta t�ecnica designa-se por abstra�c~aode dados.A abstra�c~ao �e a melhor maneira de lidar com a complexidade. Aabstra�c~ao de dados permite-nos isolar a utiliza�c~ao dos dados do modocomo eles est~ao implementados, atrav�es da utiliza�c~ao de barreiras deabstra�c~ao. Essas barreiras consistem em limitar a utiliza�c~ao dos dados aum pequeno conjunto de fun�c~oes (racional, numerador e denominador)que escondem a maneira como eles est~ao implementados. Ao utilizador34

Page 36: Intro Lisp

de um dado tipo de dados, apenas se diz quais as fun�c~oes que ele podeusar para os manipular, e n~ao qual o funcionamento das fun�c~oes queimplementam aquele tipo de dados.Seguindo esta metodologia, se precisarmos de testar a igualdadede racionais, devemos escrever uma fun�c~ao que o fa�ca usando apenasas fun�c~oes de manipula�c~ao de racionais, i.e., racional, numerador edenominador:(defun =racional (r1 r2)(and (= (numerador r1) (numerador r2))(= (denominador r1) (denominador r2))))Exerc��cio 7.3.2 A fun�c~ao que compara dois racionais n~ao funcionacorrectamente para todos os casos. Assim,> (=racional (racional 4 6) (racional 4 6))t> (=racional (racional 4 6) (racional 2 3))nilQual �e o problema? Como �e que se pode resolver?Solu�c~ao do Exerc��cio 7.3.2 O problema est�a no facto de os racionais n~ao estarem ambosreduzidos �a sua forma mais simples. Para reduzir um racional, basta dividir o numerador eo denominador pelo maior divisor comum entre os dois.Exerc��cio 7.3.3 Escreva uma fun�c~ao que calcule o maior divisor co-mum entre dois n�umeros. Para isso, use o algoritmo de Euclides quediz que se r �e o resto da divis~ao de a por b, ent~ao o maior divisorcomum entre a e b �e tamb�em o maior divisor comum entre b e r:mdc(a,b)=mdc(b,r). Como �e natural, quando o resto �e zero, o maiordivisor comum �e o pr�oprio b.Solu�c~ao do Exerc��cio 7.3.3(defun mdc (a b)(if (= b 0)a(mdc b (mod a b))))Exerc��cio 7.3.4 Empregue o m�etodo de Euclides para reescrever a fun-�c~ao racional de modo a s�o construir n�umeros na forma reduzida.Solu�c~ao do Exerc��cio 7.3.4(defun racional (n d)(let ((mdc (mdc n d)))(cons (/ n mdc) (/ d mdc))))Repare-se como se alterou a implementa�c~ao dos n�umeros racionais sem afectar as ope-ra�c~oes que usavam n�umeros racionais, como +racional ou *racional.35

Page 37: Intro Lisp

7.4 Tipos Abstractos de Informa�c~aoA teoria dos tipos abstractos de informa�c~ao diz que o conceito funda-mental para a abstrac�c~ao de dados �e a de�ni�c~ao de uma interface entrea implementa�c~ao dos dados e a sua utiliza�c~ao. Essa interface �e consti-tuida por fun�c~oes que se podem classi�car em categorias: construtores,selectores, reconhecedores e testes. Estas fun�c~oes s~ao de�nidas em ter-mos dos objectos mais primitivos que implementam o tipo de dados quese quer de�nir.Os construtores s~ao as fun�c~oes que criam um objecto composto apartir dos seus elementos mais simples. Por exemplo, a fun�c~ao racional�e um construtor para o tipo racional.Os selectores s~ao as fun�c~oes que recebem um objecto composto edevolvem as suas partes. As fun�c~oes numerador e denominador s~aoexemplos de selectores.Os reconhecedores s~ao as fun�c~oes que reconhecem certos objectosespeciais do tipo de dados que se est�a a de�nir. A fun�c~ao zerop �e umreconhecedor para o tipo n�umero do Lisp.Finalmente, os testes s~ao fun�c~oes que comparam objectos do tipoque se est�a a de�nir. A fun�c~ao =racional �e um exemplo de uma fun�c~aodesta categoria. Como se pode veri�car pela fun�c~ao =racional, porvezes, os testes s~ao implementados usando os pr�oprios selectores.Para que abstrac�c~ao de dados seja correctamente realizada, �e fun-damental de�nir o conjunto de construtores, selectores, reconhecedorese testes. Todos os programas que pretenderem utilizar aquele tipo dedados s~ao obrigados a usar apenas aquelas fun�c~oes. Isso permite que sepossa alterar a implementa�c~ao do tipo de dados sem afectar os progra-mas que o utilizam.A implementa�c~ao de estruturas de dados complexas s�o �e correcta-mente realizada quando se segue esta metodologia com extremo rigor.Exerc��cio 7.4.1 De�na o teste >racional.Solu�c~ao do Exerc��cio 7.4.1 Reduzindo as frac�c~oes ao mesmo denominador, basta-noscomparar os numeradores.(defun >racional (r1 r2)(> (* (numerador r1) (denominador r2))(* (numerador r2) (denominador r1))))Quando um tipo abstracto de informa�c~ao tem de interagir com umutilizador, quer para lhe pedir uma descri�c~ao de um elemento do ti-po, quer para lhe apresentar uma descri�c~ao de um elemento do ti-po, usa os denominados transformadores de entrada/sa��da. A fun�c~aoescreve-racional �e um exemplo de um transformador de sa��da para otipo racional. Ela limita-se a a apresentar uma representa�c~ao compre-ens��vel de um n�umero racional. O transformador de entrada realiza aopera�c~ao inversa, i.e., constroi um elemento do tipo abstracto a partirde uma representa�c~ao fornecida. 36

Page 38: Intro Lisp

8 ListasAs listas s~ao um dos componentes fundamentais da linguagem Lisp. Onome da linguagem �e, ali�as, uma abrevia�c~ao de \list processing". Comoiremos ver, as listas constituem uma estrutura de dados extremamente ex��vel.8.1 Opera�c~oes sobre ListasEm Lisp, quando o segundo elemento de um cons �e outro cons, o Lispescreve o resultado sob a forma de uma lista:> (cons 1 (cons 2 (cons 3 (cons 4 5))))(1 2 3 4 . 5)Se o �ultimo elemento �e a constante nil, o Lisp considera que elarepresenta a lista vazia, pelo que escreve:> (cons 1 (cons 2 (cons 3 (cons 4 nil))))(1 2 3 4)Esta nota�c~ao designa-se de lista e �e esta que o Lisp usa para simpli�-car a leitura e a escrita. Uma lista �e ent~ao uma sequencia de elementos.Nesta �optica, a fun�c~ao car devolve o primeiro elemento de uma lista,enquanto a fun�c~ao cdr devolve o resto da lista. A fun�c~ao cons pode servista como recebendo um elemento e uma lista e devolve como resultadouma nova lista correspondente �a jun�c~ao daquele elemento no princ��piodaquela lista. Segundo esta abordagem, a fun�c~ao cons �e um construtordo tipo abstracto de informa�c~ao lista, enquanto as fun�c~oes car e cdrs~ao selectores.Uma lista vazia �e uma sequencia sem qualquer elemento e pode serescrita como nil ou ainda mais simplesmente (). A lista vazia �e oelemento mais primitivo do tipo lista. nil �e o constructor do elementoprimitivo. Pode-se testar se uma lista �e vazia com a fun�c~ao null. Afun�c~ao null �e, portanto, um reconhecedor do tipo lista.> (null nil)t> (null (cons 1 (cons 2 nil)))nilExerc��cio 8.1.1 Escreva uma fun�c~ao que calcula uma lista de todos osn�umeros desde a at�e b.Solu�c~ao do Exerc��cio 8.1.1(defun enumera (a b)(if (> a b)nil(cons a (enumera (1+ a) b))))> (enumera 1 10)(1 2 3 4 5 6 7 8 9 10) 37

Page 39: Intro Lisp

Embora as listas n~ao sejam mais do que uma estrutura�c~ao particularde c�elulas cons, podendo por isso ser acedidas com as fun�c~oes car e cdr,�e considerado melhor estilo de programa�c~ao usar as fun�c~oes equivalentesfirst e rest. first devolve o primeiro elemento da lista enquanto restdevolve o resto da lista, i.e., sem o primeiro elemento. Do mesmo modo,o predicado null deve ser substitu��do pela seu equivalente endp.> (first (enumera 1 10))1> (rest (enumera 1 10))(2 3 4 5 6 7 8 9 10)Exerc��cio 8.1.2 Escreva uma fun�c~ao que �ltra uma lista, devolven-do uma lista com os elementos que veri�cam um determinado crit�erio.Utilize-a para encontrar os n�umeros pares entre 1 e 20.Solu�c~ao do Exerc��cio 8.1.2(defun filtra (pred lista)(cond ((null lista) nil)((funcall pred (first lista))(cons (first lista) (filtra pred lista)))(t (filtra pred (rest lista)))))> (filtra (function par?) (enumera 1 20))(2 4 6 8 10 12 14 16 18 20)Esta fun�c~ao j�a existe em Lisp e denomina-se remove-if-not.Quando se pretendem construir listas pode-se usar tamb�em a fun�c~aolist. Esta fun�c~ao recebe qualquer n�umero de argumentos e constroiuma 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 ve �e poss��vel construir listas dentro de listas. Lisp permitetamb�em a constru�c~ao de listas directamente no avaliador. Idealmente,bastaria escrever (1 2 3 ...), s�o que isso seria avaliado segundo as regrasde avalia�c~ao das combina�c~oes. O n�umero 1 seria considerado um ope-rador e os restantes elementos da lista os operandos. Para evitar queuma lista possa ser avaliada podemos usar a forma especial quote, quedevolve o seu argumento sem o avaliar.> (quote (1 . (2 . (3 . nil))))(1 2 3)> (quote (1 2 3 4 5 6 7 8 9 10))(1 2 3 4 5 6 7 8 9 10)> (filtro (function par?) (quote (1 2 3 4 5 6 7 8 9 10)))(2 4 6 8 10) 38

Page 40: Intro Lisp

Uma vez que as formas especiais quote e function s~ao bastanteutilizadas, Lisp fornece um meio de se simpli�car a sua utiliza�c~ao. Sedermos ao avaliador uma express~ao precedida por uma plica (quote emIngles), �e como se tivessemos empregue a forma especial quote. Asubstitui�c~ao �e feita durante a leitura da express~ao. Do mesmo modo, seprecedermos uma fun�c~ao ou uma lambda por #' (cardinal-plica) �e comose tiv�essemos empregue a forma especial function. 'exp �e equivalentea (quote exp ), enquanto que #'exp �e equivalente a (function exp).> '(1 2 3 4 5)(1 2 3 4 5)> (filtra #'par '(1 2 3 4 5 6))(2 4 6)Exerc��cio 8.1.3 O que �e que o avaliador de Lisp devolve para a se-guinte express~ao: (first ''(1 2 3))?Solu�c~ao do Exerc��cio 8.1.3(first ''(1 2 3)) !(first (quote (quote (1 2 3)))) !(first (quote (1 2 3))) !quoteComo �e natural, as opera�c~oes car e cdr podem ser encadeadas:> (car '(1 2 3))1> (cdr '(1 2 3))(2 3)> (car (cdr '(1 2 3))2> (car (cdr (cdr '(1 2 3))))3 Dado que aquele g�enero de express~oes �e muito utilizado em Lisp,foram compostas as v�arias combina�c~oes, e criaram-se fun�c~oes do tipo(caddr exp ), que correspondem a (car (cdr (cdr exp ))). O nomeda fun�c~ao indica quais as opera�c~oes a realizar. Um \a" representa umcar e um \d" representa um cdr.> (cadr '(1 2 3))2> (cdddr '(1 2 3))nil8.2 Fun�c~oes �UteisExerc��cio 8.2.1 Escreva uma fun�c~ao n-esimo que devolva o n-�esimoelemento de uma lista. Note que o primeiro elemento da lista corres-ponde a n igual a zero. 39

Page 41: Intro Lisp

Solu�c~ao do Exerc��cio 8.2.1(defun n-esimo (n lista)(if (= n 0)(first lista)(n-esimo (1- n) (rest lista))))Esta fun�c~ao j�a existe em Lisp e denomina-se nth.Exerc��cio 8.2.2 Escreva uma fun�c~ao muda-n-esimo que recebe um n�u-mero n, uma lista e um elemento, e substitui o n-�esimo elemento da listapor aquele elemento. Note que o primeiro elemento da lista correspondea n igual a zero.Solu�c~ao do Exerc��cio 8.2.2(defun muda-n-esimo (n lista elem)(if (= n 0)(cons elem (rest lista))(cons (first lista) (muda-n-esimo (1- n) (rest lista) elem))))Exerc��cio 8.2.3 Escreva uma fun�c~ao que calcula o comprimento deuma lista, i.e., determina quantos elementos ela tem.Solu�c~ao do Exerc��cio 8.2.3(defun comprimento (lista)(if (null lista)0(1+ (comprimento (rest lista)))))Esta fun�c~ao j�a existe em Lisp e denomina-se length.Exerc��cio 8.2.4 Escreva uma fun�c~ao que recebe um elemento e umalista que cont�em esse elemento e devolve a posi�c~ao desse elemento nalista.Solu�c~ao do Exerc��cio 8.2.4(defun posicao (elem lista)(if (eql elem (first lista))0(1+ (posicao elem (rest lista)))))Exerc��cio 8.2.5 Escreva uma fun�c~ao que calcula o n�umero de �atomosque uma lista (possivelmente com sublistas) tem.Solu�c~ao do Exerc��cio 8.2.5(defun n-atomos (lista)(cond ((null lista) 0)((atom lista) 1)(t (+ (n-atomos (first lista))(n-atomos (rest lista))))))Exerc��cio 8.2.6 Escreva uma fun�c~ao junta que recebe duas listas co-mo argumento e devolve uma lista que �e o resultado de as juntar uma�a frente da outra. 40

Page 42: Intro Lisp

Solu�c~ao do Exerc��cio 8.2.6(defun junta (lista1 lista2)(if (null lista1)lista2(cons (first lista1) (junta (rest lista1) lista2))))Esta fun�c~ao j�a existe em Lisp e denomina-se append.Exerc��cio 8.2.7 De�na uma fun�c~ao inverte que recebe uma lista edevolve outra lista que possui os mesmos elementos da primeira s�o quepor ordem inversa.Solu�c~ao do Exerc��cio 8.2.7(defun inverte (lista)(labels ((inverte-aux (lista lista-aux)(if (null lista)lista-aux(inverte-aux (rest lista)(cons (first lista) lista-aux)))))(inverte-aux lista nil)))Esta fun�c~ao j�a existe em Lisp e denomina-se reverse.Exerc��cio 8.2.8 Escreva uma fun�c~ao designada inverte-tudo que re-cebe uma lista (possivelmente com sublistas) e devolve outra lista quepossui os mesmo elementos da primeira s�o que por ordem inversa, e emque todas as sublistas est~ao tamb�em por ordem inversa, i.e.:> (inverte-tudo '(1 2 (3 4 (5 6)) 7))(7 ((6 5) 4 3) 2 1)Solu�c~ao do Exerc��cio 8.2.8(defun inverte-tudo (lista)(labels ((inverte-tudo-aux (lista lista-aux)(cond ((null lista) lista-aux)((atom lista) lista)(t(inverte-tudo-aux(rest lista)(cons (inverte-tudo (first lista))lista-aux))))))(inverte-tudo-aux lista nil)))Exerc��cio 8.2.9 Escreva uma fun�c~ao mapear que recebe uma fun�c~aoe uma lista como argumentos e devolve outra lista com o resultado deaplicar a fun�c~ao a cada um dos elementos da lista.Solu�c~ao do Exerc��cio 8.2.9(defun mapear (func lista)(if (null lista)nil(cons (funcall func (first lista))(mapear func (rest lista)))))Esta fun�c~ao j�a existe em Lisp e denomina-se mapcar.Exerc��cio 8.2.10 Escreva uma fun�c~ao denominada alisa que recebeuma lista (possivelmente com sublistas) como argumento e devolve outralista com todos os �atomos da primeira e pela mesma ordem, i.e.41

Page 43: Intro Lisp

> (alisa '(1 2 (3 4 (5 6)) 7))(1 2 3 4 5 6 7)Solu�c~ao do Exerc��cio 8.2.10(defun alisa (lista)(cond ((null lista) nil)((atom lista) (list lista))(t (append (alisa (first lista))(alisa (rest lista))))))Exerc��cio 8.2.11 Escreva uma fun�c~ao membro? que recebe um objectoe uma lista e veri�ca se aquele objecto existe na lista.Solu�c~ao do Exerc��cio 8.2.11(defun membro? (obj lista)(cond ((null lista) nil)((eql obj (first lista)) t)(t (membro? obj (rest lista)))))Esta fun�c~ao j�a existe em Lisp e denomina-se member. Quando ela encontra um elementoigual na lista devolve o resto dessa lista.> (member 3 '(1 2 3 4 5 6))(3 4 5 6)Exerc��cio 8.2.12 Escreva uma fun�c~ao elimina que recebe um elemen-to e uma lista como argumentos e devolve outra lista onde esse elementon~ao aparece.Solu�c~ao do Exerc��cio 8.2.12(defun elimina (elem lista)(cond ((null lista) nil)((eql elem (first lista))(elimina elem (rest lista)))(t (cons (first lista)(elimina elem (rest lista))))))Esta fun�c~ao j�a existe em Lisp e denomina-se remove.Exerc��cio 8.2.13 Escreva uma fun�c~ao substitui que recebe dois ele-mentos e uma lista como argumentos e devolve outra lista com todas asocorrencias do segundo elemento substitu��das pelo primeiro.Solu�c~ao do Exerc��cio 8.2.13(defun substitui (novo velho lista)(cond ((null lista) nil)((eql velho (first lista))(cons novo (substitui novo velho (rest lista))))(t (cons (first lista)(substitui novo velho (rest lista))))))Esta fun�c~ao j�a existe em Lisp e denomina-se subst.Exerc��cio 8.2.14 Escreva uma fun�c~ao remove-duplicados que recebeuma lista como argumento e devolve outra lista com todos os elementosda primeira mas sem duplicados, i.e.:> (remove-duplicados '(1 2 3 3 2 4 5 4 1))(3 2 5 4 1) 42

Page 44: Intro Lisp

Solu�c~ao do Exerc��cio 8.2.14(defun remove-duplicados (lista)(cond ((null lista) nil)((member (first lista) (rest lista))(remove-duplicados (rest lista)))(t (cons (first lista)(remove-duplicados (rest lista))))))Esta fun�c~ao n~ao mant�em a anterior ordena�c~ao da lista. Se pretendermos preservar aordem original, temos de testar se um elemento j�a existe na lista que estamos a construir en~ao na que estamos a analisar.(defun remove-duplicados2 (lista)(labels ((remove-aux (lista lista-aux)(cond ((null lista) nil)((member (first lista) lista-aux)(remove-aux (rest lista) lista-aux))(t (cons (first lista)(remove-aux(rest lista)(cons (first lista)lista-aux)))))))(remove-aux lista nil)))> (remove-duplicados2 '(1 2 3 3 2 4 5 4 1))(1 2 3 4 5)Esta fun�c~ao j�a existe em Lisp e denomina-se remove-duplicates.Exerc��cio 8.2.15 Escreva as fun�c~oes inversas do cons, car e cdr, de-signadas snoc, rac e rdc. O snoc recebe um elemento e uma lista ejunta o elemento ao �m da lista. O rac devolve o �ultimo elemento dalista. O rdc devolve todos os elementos da lista menos o primeiro.Solu�c~ao do Exerc��cio 8.2.15(defun snoc (elem lista)(if (null lista)(list elem)(cons (first lista) (snoc elem (rest lista)))))A fun�c~ao snoc j�a existe em Lisp atrav�es da combina�c~ao das fun�c~oes append e list.(defun rac (lista)(if (null (rest lista))(first lista)(rac (rest lista))))A fun�c~ao rac j�a existe em Lisp atrav�es da combina�c~ao das fun�c~oes first e last.(defun rdc (lista)(if (null (rest lista))nil(cons (first lista) (rdc (rest lista)))))A fun�c~ao rdc j�a existe em Lisp e denomina-se butlast.Exerc��cio 8.2.16 As fun�c~oes todos?, algum?, nenhum? e nem-todos?s~ao predicados que recebem uma fun�c~ao e uma lista e veri�cam, respec-tivamente, se a fun�c~ao �e verdade para todos os elementos da lista, se �everdade para pelo menos um, se n~ao �e verdade para todos e se n~ao �everdade para pelo menos um. De�na estas fun�c~oes.43

Page 45: Intro Lisp

Solu�c~ao do Exerc��cio 8.2.16(defun todos? (func lista)(cond ((null lista) t)((funcall func (first lista))(todos? func (rest lista)))(t nil)))Esta fun�c~ao j�a existe em Lisp e denomina-se every.(defun algum? (func lista)(cond ((null lista) nil)((funcall func (first lista)) t)(t (algum? func (rest lista)))))Esta fun�c~ao j�a existe em Lisp e denomina-se some.(defun nenhum? (func lista)(cond ((null lista) t)((not (funcall func (first lista)))(nenhum? func (rest lista)))(t nil)))Esta fun�c~ao j�a existe em Lisp e denomina-se notany.(defun nem-todos? (func lista)(cond ((null lista) nil)((not (funcall func (first lista))) t)(t (nem-todos? func (rest lista)))))Esta fun�c~ao j�a existe em Lisp e denomina-se notevery.8.3 Listas de ArgumentosSendo as listas uma das estruturas b�asicas do Lisp, a linguagem permiteaplicar fun�c~oes directamente a listas de argumentos atrav�es da fun�c~aoapply. Esta �e em tudo identica �a fun�c~ao funcall, mas em vez de teros argumentos da fun�c~ao a aplicar como argumentos da fun�c~ao funcall,tem-nos numa lista, i.e.:(funcall func arg 1 arg 2 ... arg n) $(apply func (list arg 1 arg 2 ... arg n))Na linguagem Common Lisp, a fun�c~ao apply �e uma fus~ao entre afun�c~ao funcall e a fun�c~ao apply primitiva, pois �e da forma:(apply func arg 1 arg 2 ... rest-args)Nesta aplica�c~ao o �ultimo argumento rest-args �e uma lista comos restantes argumentos. Desta forma, pode-se escrever qualquer dasseguintes equivalencias:(funcall func arg 1 arg 2 ... arg n) $(apply func (list arg 1 arg 2 ... arg n)) $(apply func arg 1 (list arg 2 ... arg n)) $(apply func arg 1 arg 2 ... (list arg n)) $(apply func arg 1 arg 2 ... arg n nil))44

Page 46: Intro Lisp

8.4 Tipos AglomeradosUm tipo aglomerado �e um tipo abstracto de informa�c~ao que �e compostoexclusivamente pela aglomera�c~ao de outros tipos abstractos. O conjun-to dos racionais �e um exemplo pois, como vimos, um racional n~ao �e maisdo que uma aglomera�c~ao de dois inteiros. As opera�c~oes fundamentaisde um tipo aglomerado s~ao os seus construtores e selectores, emborapossam existir outras. Como vimos, para um racional, as opera�c~oesmais utilizadas eram o construtor racional e os selectores numerador edenominador, mas tamb�em foram de�nidos alguns testes e os transfor-madores de entrada/sa��da.Os tipos aglomerados s~ao extremamente utilizados. Por este motivo�e costume as linguagens de alto n��vel fornecerem ferramentas pr�opriaspara os tratar. Pascal, por exemplo, permite de�ni-los com a formaespecial record, enquanto que a linguagem C usa, para o mesmo efeito,o struct.Para exempli�carmos a utiliza�c~ao de tipos aglomerados podemosconsiderar a de�ni�c~ao de um autom�ovel. Um autom�ovel �e caracterizadopor uma marca, um modelo, um dado n�umero de portas, uma cilin-drada, uma potencia, etc. Para simpli�car, podemos considerar s�o astres primeiras. O construtor de um objecto do tipo autom�ovel n~ao temmais que agrupar as informa�c~oes relativas a cada uma daquelas carac-ter��sticas. Para isso, podemos usar a fun�c~ao list. Assim, criamos oconstrutor do tipo da seguinte forma:(defun novo-automovel (marca modelo portas)(list marca modelo portas))Os selectores do tipo autom�ovel limitam-se a determinar de que �eque um dado objecto daquele tipo �e composto:(defun automovel-marca (automovel)(nth 0 automovel))(defun automovel-modelo (automovel)(nth 1 automovel))(defun automovel-portas (automovel)(nth 2 automovel))Estando na posse destas fun�c~oes, podemos criar um autom�ovel es-pec���co, por exemplo:> (novo-automovel 'honda 'civic 2)(honda civic 2)Dado aquele objecto do tipo autom�ovel, podemos estar interessa-dos em alterar-lhe o n�umero de portas, passando-as de 2 para 4, porexemplo. Contudo, para manipularmos um tipo abstracto devemosrestringirmo-nos �as opera�c~oes desse tipo. Precisamos, portanto, de criar45

Page 47: Intro Lisp

novas opera�c~oes que nos permitem modi�car um objecto. Para o tipoautom�ovel poderiamos de�nir:(defun muda-automovel-marca (automovel nova-marca)(muda-n-esimo 0 automovel nova-marca))(defun muda-automovel-modelo (automovel novo-modelo)(muda-n-esimo 1 automovel novo-modelo))(defun muda-automovel-portas (automovel novo-portas)(muda-n-esimo 2 automovel novo-portas))A fun�c~ao muda-n-esimo recebia um n�umero n, uma lista e um novoelemento, e substituia o n-�esimo elemento da lista pelo novo elemen-to. Esta fun�c~ao n~ao alterava a lista original, produzindo uma novalista. Desta forma, qualquer destas fun�c~oes do tipo autom�ovel deixao autom�ovel a modi�car absolutamente inalterado, produzindo um no-vo autom�ovel. Por este motivo, estas opera�c~oes devem ser vistas comoconstrutores do tipo autom�ovel, pois elas criam um novo autom�ovel apartir de um outro j�a existente. Elas n~ao permitem alterar um au-tom�ovel j�a criado.9 Programa�c~ao ImperativaTodas as fun�c~oes que apresent�amos anteriormente realizam opera�c~oesmuito variadas e algumas s~ao at�e relativamente complexas, mas nenhu-ma afecta os seus argumentos. Elas limitam-se a produzir novos objectosa partir de outros j�a existentes, sem alterar estes �ultimos seja de queforma for. At�e as pr�oprias vari�aveis que introduzimos nas fun�c~oes eque se destinavam a guardar valores tempor�arios n~ao eram mais do queparametros de uma lambda, e a sua inicializa�c~ao correspondia a invocara lambda com os valores iniciais como argumentos, sendo por isso inicia-lizadas uma �unica vez e nunca modi�cadas. Por este motivo, nem sequerfoi apresentado nenhum operador de atribui�c~ao, t~ao caracter��stico emlinguagens como C e Pascal.Este estilo de programa�c~ao, sem atribui�c~ao, sem altera�c~ao dos argu-mentos de fun�c~oes, e em que estas se limitam a produzir novos valores,�e designado programa�c~ao funcional. Neste paradigma de programa�c~ao,qualquer fun�c~ao da linguagem �e considerada uma fun�c~ao matem�aticapura que, para os mesmos argumentos produz sempre os mesmos valo-res. Nunca nada �e destru��do. Uma fun�c~ao que junta duas listas produzuma nova lista sem alterar as listas originais. Uma fun�c~ao que muda on�umero de portas de um autom�ovel produz um novo autom�ovel.A programa�c~ao funcional tem muitas vantagens sobre outros estilosde programa�c~ao, em especial no que diz respeito a produzir programasmuito rapidamente e minimizando os erros. Contudo, tem tamb�em assuas limita�c~oes, e a sua incapacidade em modi�car seja o que for �e46

Page 48: Intro Lisp

a maior. A partir do momento em que introduzimos a modi�ca�c~aode objectos, estamos a introduzir o conceito de destrui�c~ao. A formaanterior do objecto que foi modi�cado deixou de existir, passando aexistir apenas a nova forma. A modi�ca�c~ao implica a introdu�c~ao doconceito de tempo. Os objectos passam a ter uma hist�oria, e isto conduza um novo estilo de programa�c~ao.9.1 Atribui�c~aoPara se alterar o valor de uma vari�avel Lisp possui um operador deatribui�c~ao. A forma especial setq recebe uma vari�avel e um valor eatribui o valor �a vari�avel.> (let ((x 2))(setq x (+ x 3))(setq x (* x x))(setq x (- x 5))x)20 Cada vez que se realiza uma atribui�c~ao, perde-se o valor anterior quea vari�avel possu��a. Muito embora a forma especial setq, como todas asfun�c~oes e forma especiais, retorne um valor, esse valor n~ao possui qual-quer interesse. O operador de atribui�c~ao serve apenas para modi�caro estado de um programa, neste exemplo, alterando o valor de x. Poreste motivo, a atribui�c~ao assemelha-se mais a um comando do que auma fun�c~ao. A atribui�c~ao �e uma ordem que tem de ser cumprida e deque n~ao interessa o resultado. A ordem �e a opera�c~ao fundamental daprograma�c~ao imperativa, em que um programa �e composto por suces-sivos comandos. Neste estilo de programa�c~ao, os comandos funcionampor efeitos secund�arios. O valor de cada comando n~ao tem interesse emuitas vezes nem sequer tem signi�cado falar dele.9.2 Sequencia�c~aoA forma especial progn est�a especialmente vocacionada para este g�enerode utiliza�c~ao. Ela recebe um conjunto de express~oes que avalia sequen-cialmente retornando o valor da �ultima. Desta forma �e poss��vel incluirv�arias ac�c~oes no consequente ou alternativa de um if, por exemplo.(if (> 3 2)(progn(print 'estou-no-consequente)(+ 2 3))(progn(print 'estou na alternativa)(* 4 5)))Algumas das formas especiais do Lisp incluem um progn impl��cito.Vimos atr�as um exemplo de um let que realizava quatro opera�c~oes em47

Page 49: Intro Lisp

sequencia. Isto implica que o let e, por arrastamento, as lambdas etudo o que for de�nido com defun, possuem tamb�em a capacidade derealizar opera�c~oes em sequencia. O cond est�a tamb�em nesta categoria.A sua sintaxe �e, na realidade, a seguinte:(cond (condi�c~ao 1 express~ao 11...express~ao 1l)(condi�c~ao 2 express~ao 21...express~ao 2m)...(condi�c~ao n express~ao n1...express~aonk))O cond testa cada uma das condi�c~oes em sequencia, e quando umadelas avalia para verdade, s~ao avaliadas todas as express~oes da cl�ausulacorrepondente sendo devolvido o valor da �ultima dessas express~oes.Usando o cond, o exemplo anterior �car�a mais elegante:(cond ((> 3 2)(print 'estou-no-consequente)(+ 2 3))(t(print 'estou na alternativa)(* 4 5)))A forma especial prog1 �e semelhante ao progn. Ela recebe umconjunto de express~oes que avalia sequencialmente retornando o valor daprimeira. A express~ao equivalente ao exemplo anterior mas utilizandoo prog1 ser�a:(if (> 3 2)(prog1(+ 2 3)(print 'estou-no-consequente))(prog1(* 4 5)(print 'estou na alternativa)))Note-se que a ordem de avalia�c~ao das express~oes de um prog1 �e igual�a de um progn. Apenas o valor retornado �e diferente: �e o primeiro nocaso do prog1 e o �ultimo no caso do progn.A sequencia�c~ao �e tamb�em suportada por qualquer lambda. Em con-sequencia, as formas especiais que implicam a cria�c~ao de lambdas, comoo let e o pr�oprio defun permitem tamb�em especi�car mais do que umaexpress~ao, sendo estas avaliadas em sequencia e devolvido o valor da�ultima.9.3 Altera�c~ao de DadosA atribui�c~ao n~ao est�a restricta a vari�aveis. �E tamb�em poss��vel alteraro conte�udo da maioria dos tipos de dados Lisp. Uma c�elula cons, porexemplo, pode ser alterada com as fun�c~oes rplaca e rplacd, signi�can-do, respectivamente \replace-car" e \replace-cdr".48

Page 50: Intro Lisp

> (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 fun�c~oes s~ao uma forma de atribui�c~ao e, como tal,destroem o conte�udo anterior da estrutura a que se aplicam. Por estemotivo, este g�enero de fun�c~oes diz-se destrutivo. Na sequencia l�ogicada conven�c~ao usual em Lisp para a de�ni�c~ao de reconhecedores, queterminam sempre com um ponto de interroga�c~ao (ou com a letra \p"de predicado), deve-se colocar um ponto de exclama�c~ao no �m do nomedas fun�c~oes destrutivas para salientar o seu car�acter imperativo.Exerc��cio 9.3.1 Escreva uma fun�c~ao junta! (note-se o ponto de ex-clama�c~ao) que recebe duas listas como argumento e devolve uma listaque �e o resultado de as juntar destrutivamente uma �a frente da outra,i.e., faz a cauda da primeira lista apontar para a segunda.Solu�c~ao do Exerc��cio 9.3.1 Para resolver o problema, basta atingir a �ultima c�elula consda primeira lista (usando uma fun�c~ao local fim) e ligar destrutivamente o seu cdr (que �enil) �a segunda lista.(defun junta! (lista1 lista2)(labels ((fim (lista)(if (null (rest lista))lista(fim (rest lista)))))(if (null lista1)lista2(progn(rplacd (fim lista1) lista2)lista1))))Esta fun�c~ao j�a existe em Lisp e denomina-se nconc.Exerc��cio 9.3.2 Analise o seguinte exemplo funcional e imperativo:> (let ((x '(1 2 3)))(junta x x))(1 2 3 1 2 3)> (let ((x '(1 2 3)))(junta! x x))(1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 ...Solu�c~ao do Exerc��cio 9.3.2 O que se est�a a passar �e que a altera�c~ao destrutiva da caudada lista x de modo a apontar para o princ��pio da lista x implicou a cria�c~ao de uma listacircular, em que os elementos se sucedem uns aos outros inde�nidamente.49

Page 51: Intro Lisp

Exerc��cio 9.3.3 Escreva uma fun�c~ao muda-n-esimo! (note-se o pontode exclama�c~ao) que recebe um n�umero n, uma lista e um elemento, esubstitui o n-�esimo elemento da lista por aquele elemento. Note que oprimeiro elemento da lista corresponde a n igual a zero.Solu�c~ao do Exerc��cio 9.3.3(defun muda-n-esimo! (n lista elem)(if (= n 0)(rplaca lista elem)(muda-n-esimo! (1- n) (rest lista) elem))lista)Repare-se como a �ultima coisa a ser avaliada e devolvida pela fun�c~ao �e o parametrolista original. Se tal n~ao fosse feito, perderiamos acesso aos elementos anteriores �aquele quese estava a modi�car. Na de�ni�c~ao segundo a programa�c~ao funcional, esses valores eramrecuperados por sucessivas inser�c~oes na nova lista que tinham sido deixadas em suspenso.Exerc��cio 9.3.4 Reescreva as opera�c~oes do tipo abstracto de informa-�c~ao autom�ovel que alteravam as caracter��sticas de um elemento do tipode forma a torn�a-las destrutivas.Solu�c~ao do Exerc��cio 9.3.4(defun muda-automovel-marca! (automovel nova-marca)(muda-n-esimo! 0 automovel nova-marca))(defun muda-automovel-modelo! (automovel novo-modelo)(muda-n-esimo! 1 automovel novo-modelo))(defun muda-automovel-portas! (automovel novo-portas)(muda-n-esimo! 2 automovel novo-portas))Quando as opera�c~oes de um tipo abstracto alteram um elemento dotipo, essas opera�c~oes s~ao classi�cadas como modi�cadores do tipo abs-tracto. Os modi�cadores, como caso especial da atribui�c~ao, s~ao muitoempregues em programa�c~ao imperativa. Note-se que os modi�cadorespossuem todos os problemas da atribui�c~ao simples, nomeadamente a al-tera�c~ao ser destrutiva. Isto levanta problemas quando se testa igualdadeem presen�ca de modi�ca�c~ao.> (let ((x (novo-automovel 'honda 'civic 2)))(let ((y (muda-automovel-portas x 4)))(eql x y)))NIL> (let ((x (novo-automovel 'honda 'civic 2)))(let ((y (muda-automovel-portas! x 4)))(eql x y)))T Repare-se que no primeiro exemplo (funcional), o autom�ovel modi�-cado �e, logicamente, diferente do autom�ovel original. x e y representamautom�oveis diferentes. No segundo exemplo (imperativo), a modi�ca�c~aodo autom�ovel que x representa �e realizada sobre esse pr�oprio autom�ovel,50

Page 52: Intro Lisp

de modo que x e y acabam por representar um mesmo autom�ovel (mo-di�cado). Muito embora esta situa�c~ao possa ter vantagens, ela permitetamb�em a introdu�c~ao de erros muito subtis e extremamente dif��ceis detirar. Para dar apenas um pequeno exemplo, repare-se que o autom�ovelque x representa passou a ter quatro portas embora uma leitura super-�cial do c�odigo sugira que ele foi criado com apenas duas.9.4 Repeti�c~aoPara al�em dos operadores de atribui�c~ao (setq, rplaca, rplacd, etc.) ede sequencia�c~ao (progn, prog1, etc.) a linguagem Common Lisp possuimuitas outras formas especiais destinadas a permitir o estilo de pro-grama�c~ao imperativa. De destacar s~ao as estruturas de controle derepeti�c~ao, tais como o loop, o do, o dotimes e ainda outras adequadaspara iterar ao longo de listas.O loop �e a mais gen�erica de todas as formas de repeti�c~ao. Ela recebeum conjunto de express~oes que avalia sequencialmente, repetindo essaavalia�c~ao em ciclo at�e que seja avaliada a forma especial return. Esta�ultima recebe uma express~ao opcional e termina o ciclo em que est�ainserida, fazendo-o devolver o valor daquela express~ao.A seguinte express~ao exempli�ca um ciclo que escreve todos os n�u-meros desde 0 at�e 100, retornando o s��mbolo fim no �nal do ciclo.(let ((n 0))(loop(print n)(setq n (1+ n))(when (> n 100)(return 'fim))))A forma especial do �e um pouco mais so�sticada que o loop. Elapermite estabelecer vari�aveis, inicializ�a-las e increment�a-las automatica-mente, testar condi�c~oes de paragem com indica�c~ao do valor a retornare repetir a execu�c~ao de c�odigo. Se reescrevermos o exemplo anteriorusando 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 emqualquer altura com um return, retornando o valor opcional fornecidocom o return.Apesar do estilo mais utilizado na maioria das linguagens de pro-grama�c~ao ser o imperativo, ele �e muito pouco natural em Lisp.A falta de naturalidade resulta, por um lado, de os programas emLisp se decomporem geralmente em pequenas fun�c~oes que calculam va-lores, invalidando uma abordagem baseada em ciclos de altera�c~ao devari�aveis, t��pica da programa�c~ao imperativa.51

Page 53: Intro Lisp

Por outro lado, a grande maioria de tipos de dados existentes em Lisps~ao inerentemente recursivos, o que di�culta o seu tratamento segundoo estilo imperativo.Apesar de muito pouco pr�actico para usar em Lisp, a programa�c~aoimperativa tem algumas vantagens, das quais a possibilidade de atri-bui�c~ao �e a maior (e tamb�em a mais perigosa).10 Modelo de AmbientesAt�e agora vimos que as vari�aveis eram apenas designa�c~oes para valores.Quando se avaliava uma express~ao, as vari�aveis desapareciam, sendosubstitu��das pelos seus valores. A partir do momento em que podemosalterar o valor de uma vari�avel, o seu comportamento torna-se menosclaro.Para se explicar correctamente este comportamento �e necess�ario pas-sar para um modelo de avalia�c~ao mais elaborado designado modelo deavalia�c~ao em ambientes.Neste modelo, uma vari�avel j�a n~ao �e uma designa�c~ao de um valormas sim uma designa�c~ao de um objecto que cont�em um valor. Esseobjecto pode ser visto como uma caixa onde se guardam coisas. Emcada instante, a vari�avel designa sempre a mesma caixa, mas esta podeguardar coisas diferentes. Segundo o modelo de ambientes, o valor deuma vari�avel �e o conte�udo da caixa que ela designa. A forma especialsetq �e a opera�c~ao que permite meter valores dentro da caixa.As vari�aveis s~ao guardadas em estruturas denominadas enquadra-mentos. Por exemplo, cada vez que usamos a forma let �e criado umnovo enquadramento para conter as vari�aveis estabelecidas pelo let.Todas as express~oes pertencentes ao corpo do let ser~ao avaliadas emrela�c~ao a este enquadramento. Imaginemos agora a seguinte situa�c~ao:(let ((x 1))(let ((y 2)(z 3))(+ x y z)))Neste exemplo, o corpo do primeiro let �e um novo let. Existemportanto dois enquadramentos. Estes enquadramentos est~ao organiza-dos de modo a que o corpo do segundo let consiga fazer referencia �astres vari�aveis x, y e z.Para isso, os enquadramentos s~ao estruturados sequencialmente, des-de aquele que for textualmente mais interior at�e ao mais exterior. Essasequencia de enquadramentos �e designada por ambiente.Cada enquadramento �e uma tabela de liga�c~oes, que associa as va-ri�aveis aos seus valores correspondentes. Uma vari�avel nunca pode estarrepetida num enquadramento, embora possa aparecer em v�arios enqua-dramentos de um ambiente. Cada enquadramento aponta para o am-biente envolvente, excepto o ambiente global, que �e composto por um52

Page 54: Intro Lisp

�unico enquadramento sem ambiente envolvente. �E no ambiente globalque est~ao guardadas todas as fun�c~oes que usamos normalmente.10.1 Ambito L�exicoA regra de avalia�c~ao de vari�aveis em ambientes diz que o valor de umavari�avel em rela�c~ao a um ambiente �e dado pela liga�c~ao dessa vari�avelno primeiro enquadramento em que ela surja ao longo da sequencia deenquadramentos que constituem esse ambiente. Se nenhum enquadra-mento possui uma liga�c~ao para essa vari�avel ela diz-se n~ao ligada. �E umerro avaliar vari�aveis n~ao ligadas.Uma vez que os enquadramentos de um ambiente est~ao associadoslexicamente �as formas que os criaram, �e poss��vel determinar o ambito deuma vari�avel qualquer simplesmente observando o texto do programa.Usando o modelo de avalia�c~ao em ambientes �e muito f�acil percebero comportamento da forma especial let (que n~ao �e mais do que umasimpli�ca�c~ao de uma lambda) e da forma especial setq. Cada letaumenta o ambiente em que �e avaliado com um novo enquadramento,estabelecendo liga�c~oes para as suas vari�aveis. Quando se pretende sabero valor de uma vari�avel percorre-se o ambiente, come�cando pelo primeiroenquadramento at�e se encontrar a liga�c~ao correspondente. Se ela n~aoaparecer, vai-se passando de enquadramento em enquadramento at�e seatingir o ambiente global, e se a�� tamb�em n~ao existir nenhuma liga�c~aopara aquela vari�avel �e gerado um erro de vari�avel n~ao ligada. O setqaltera o valor da vari�avel que aparece estabelecida no enquadramentomais pr�oximo do ponto onde o setq �e avaliado. Se se atingir o ambienteglobal, e se a�� tamb�em n~ao existir nenhuma liga�c~ao para aquela vari�avel,�e criada essa liga�c~ao no ambiente global.> (let ((x 10))(+ x y))Error: Unbound variable: Y> (setq y 20)20> (let ((x 10))(+ x y))30 Como se ve pelo exemplo. A partir do momento em que se esta-beleceu uma liga�c~ao no ambiente global para a vari�avel y, j�a �e poss��velavaliar aquele let apesar de ele fazer referencia a uma vari�avel livre. Noentanto, a utiliza�c~ao da forma especial setq para criar vari�aveis globais�e considerada pouca correcta e o compilador emitir�a um aviso se en-contrar uma destas formas de utiliza�c~ao. A utiliza�c~ao do setq deve serrestricta a modi�ca�c~oes do valor de vari�aveis previamente estabelecidas.As regras de avalia�c~ao do modelo de ambientes s~ao, em tudo, equi-valentes �as do modelo cl�assico, excepto no que diz respeito �a aplica�c~aode fun�c~oes. 53

Page 55: Intro Lisp

No modelo de ambientes todas as fun�c~oes possuem um ambienteassociado, que corresponde �aquele que existia quando a fun�c~ao foi de-�nida. Quando se aplica uma fun�c~ao aos seus argumentos, cria-se umnovo ambiente, cujo primeiro enquadramento cont�em as liga�c~oes dosparametros formais da fun�c~ao aos seus argumentos e cujo ambiente en-volvente �e aquele em que a fun�c~ao foi de�nida. �E em rela�c~ao a este novoambiente que se avalia o corpo da fun�c~ao.Note-se que a forma especial defun de�ne fun�c~oes (i.e., cria umaliga�c~ao entre o nome da fun�c~ao e a lambda correspondente ao seu cor-po) sempre no ambiente global, enquanto que setq altera a liga�c~ao deuma vari�avel no primeiro enquadramento do ambiente em que a formaespecial �e avaliada. S�o se a vari�avel n~ao for encontrada na sequencia deenquadramentos �e que o setq cria uma no ambiente global.Exerc��cio 10.1.1 Interprete o comportamento da seguinte fun�c~ao:(let ((valor 0))(defun incrementa ()(setq valor (1+ valor))valor))> (incrementa)1> (incrementa)2> (incrementa)3Solu�c~ao do Exerc��cio 10.1.1 Quando se aplica a fun�c~ao incrementa, o ambiente em queela foi de�nida �e ampliado com um enquadramento vazio pois a fun�c~ao n~ao tem parametros.Neste ambiente, a vari�avel valor possuia o valor zero. A avalia�c~ao do corpo da fun�c~ao alterao valor daquela vari�avel para 1, e devolve o seu novo valor. Cada nova avalia�c~ao altera aliga�c~ao que tinha sido estabelecida na de�ni�c~ao da fun�c~ao.�E de salientar o car�acter evolutivo da fun�c~ao incrementa. Na meto-dologia da programa�c~ao funcional, o resultado de uma fun�c~ao s�o depen-de dos seus argumentos, e uma fun�c~ao sem argumentos equivale a umaconstante. Sempre que a fun�c~ao �e aplicada ela tem de devolver sempreo mesmo resultado. A partir do momento que se admite a atribui�c~ao,abre-se a porta a in�umeras possibilidades de viola�c~ao do modelo fun-cional. As fun�c~oes passam a ser caracterizadas por um estado local eo resultado da aplica�c~ao de uma fun�c~ao passa a depender n~ao s�o dosargumentos mas tamb�em do estado local.Exerc��cio 10.1.2 Uma excelente aplica�c~ao de fun�c~oes com estado local�e na cria�c~ao de geradores de sequencias de n�umeros, i.e., fun�c~oes semargumentos que, a cada invoca�c~ao, devolvem o elemento que sucede lo-gicamente a todos os que foram gerados anteriormente. Seguindo esteparadigma, de�na o gerador da sequencia de Fibonacci, que �e represen-tada pela sucess~ao crescente 1, 2, 3, 5, 8, ..., em que cada n�umero �e asoma dos dois �ultimos que o precedem.54

Page 56: Intro Lisp

Solu�c~ao do Exerc��cio 10.1.2 Para implementar a sucess~ao de Fibonacci, �e convenienteusar a vers~ao iterativa da fun�c~ao de Fibonacci, para se poder determinar mais rapidamente opr�oximo elemento da sequencia em fun�c~ao dos dois �ultimos que o precederam. Assim sendo,a fun�c~ao necessita de duas vari�aveis no seu estado local, para guardar o �ultimo e o pen�ultimovalores produzidos.(let ((ultimo 1) (penultimo 0))(defun gera-fib ()(let ((novo (+ ultimo penultimo)))(setq penultimo ultimoultimo novo)novo)))> (gera-fib)1> (gera-fib)2> (gera-fib)3Exerc��cio 10.1.3 Infelizmente, a fun�c~ao gera-fib n~ao sabe recome-�car. Para isso, �e necess�ario de�ni-la (compil�a-la) outra vez. Este pro-cesso, como �e l�ogico, �e muito pouco pr�actico, em especial porque obrigao utilizador a ter acesso ao c�odigo do gerador. Complemente a de�ni�c~aoda fun�c~ao gera-fib com uma outra fun�c~ao denominada repoe-fib querep~oe o gerador em condi�c~oes de recome�car de novo desde o princ��pio.Solu�c~ao do Exerc��cio 10.1.3 Para implementar reposi�c~ao da sucess~ao de Fibonacci, �enecess�ario que haja acesso �as vari�aveis de estado do processo. Isto implica que a fun�c~aorepoe-�b tem de estar de�nida no mesmo ambito l�exico da fun�c~ao gera-fib.(let ((ultimo 1) (penultimo 0))(defun repoe-fib ()(setq ultimo 1penultimo 0))(defun gera-fib ()(let ((novo (+ ultimo penultimo)))(setq penultimo ultimoultimo novo)novo)))> (gera-fib)1> (gera-fib)2> (repoe-fib)0> (gera-fib)110.2 Ambito DinamicoA cria�c~ao de vari�aveis globais em Common Lisp deve ser feita usandoas formas especiais defconstant para criar constantes, defvar paracriar vari�aveis inicializ�aveis uma �unica vez e defparameter para criarvari�aveis inicializ�aveis v�arias vezes. Esta diferen�ca �e relevante sobretudodurante a fase de desenvolvimento e depura�c~ao de programas.55

Page 57: Intro Lisp

> (defconstant acelaracao-gravidade 9.8)ACELARACAO-GRAVIDADE> (defvar *y*)*Y*> (defparameter *z* 10)*Z*Note-se a conven�c~ao adoptada para as vari�aveis globais de usar no-mes compreendidos entre um par de asteriscos. Quando se de�nemconstantes essa conven�c~ao n~ao se aplica.O facto de podermos ter vari�aveis globais introduz uma altera�c~aonas regras de avalia�c~ao. T��nhamos visto que as vari�aveis que eramparametros de fun�c~oes (e, como tal, as vari�aveis introduzidas por umlet) tinham ambito l�exico, ou seja, apenas podiam ser referidas dentroda regi~ao textual que as introduziu. No entanto, as vari�aveis globaiscomo aceleracao-gravidade, *y* ou *z* podem ser referidas de qual-quer ponto do programa, fazendo com que o seu ambito passe a ser vago.No entanto, apesar de ser poss��vel referenciar a vari�avel *y*, ser�a pro-duzido um erro quando tentarmos determinar o seu valor, uma vez queele ainda est�a inde�nido. Ser�a preciso ligarmos um valor �aquela vari�avelantes de a podermos avaliar e, para isso, podemos usar um let.> (defvar *y*)*Y*> (defun teste () (+ *y* *y*))TESTE> (teste)Error: Unbound variable: *Y*> (let ((*y* 10)) (teste))20>*y*Error: Unbound variable: *Y*Repare-se que apesar de, momentaneamente, termos atribuido umvalor �a vari�avel *y* por interm�edio de um let, ela perdeu esse valorassim que terminou o let. A dura�c~ao da vari�avel *y* �e, assim, dinamica.Apenas as vari�aveis l�exicas possuem dura�c~ao inde�nida. Vari�aveis como*y* e *z* dizem-se especiais e possuem ambito vago e dura�c~ao dinamica.Esta combina�c~ao d�a origem a um comportamento que se designa deambito dinamico.Um dos aspectos mais cr��ticos na utiliza�c~ao de vari�aveis de ambitodinamico �e o facto de, geralmente, n~ao ser su�ciente ler o c�odigo de umprograma para perceber o que �e que ele vai fazer|�e tamb�em precisoexecut�a-lo. O seguinte exemplo explica este ponto.Imaginemos as seguintes de�ni�c~oes:56

Page 58: Intro Lisp

> (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))1001O primeiro exemplo envolve apenas vari�aveis l�exicas. Da�� que basteobservar o texto da fun�c~ao soma-2 para se perceber que a vari�avel xusada em (+ x y) toma sempre o valor 2.No segundo exemplo, a �unica diferen�ca est�a no facto de a vari�avel*x* ser especial. Nesta situa�c~ao a fun�c~ao soma-2 n~ao usa o valor de*x* que existia no momento da de�ni�c~ao da fun�c~ao, mas sim o valor de*x* que existe no momento da execu�c~ao da fun�c~ao. Desta forma, j�a n~ao�e su�ciente observar o texto da fun�c~ao soma-2 para perceber o que elafaz. Por este motivo, o uso excessivo de vari�aveis dinamicas pode tornarum programa dif��cil de ler e, consequentemente, dif��cil de desenvolver edif��cil de corrigir.11 Parametros Especiais11.1 Parametros OpcionaisComo referimos anteriormente, a forma especial return, que se podeutilizar dentro de um loop, possui um parametro opcional, que �e o valora retornar do ciclo. Isto quer dizer que o argumento que deveremos pas-sar para esse parametro pode ser omitido. Esta caracter��stica �e tamb�emmuito �util para a de�ni�c~ao de fun�c~oes, permitindo que ela possa assumircertos parametros por omiss~ao.Para de�nirmos fun�c~oes que tem parametros opcionais temos de usarum quali�cador especial designado &optional na lista de parametrosformais. Esse quali�cador indica que todos os parametros que se lheseguem s~ao opcionais e que, se os argumentos correspondentes foremomitidos, eles valem nil. Se pretendermos um valor diferente para umparametro, podemos inserir o parametro numa lista com o seu valor. Aseguinte fun�c~ao mostra como se pode de�nir a fun�c~ao incr que incre-menta o seu argumento de uma unidade, ou de uma quantidade que lheseja fornecida. 57

Page 59: Intro Lisp

(defun incr (x &optional (i 1))(+ x i))> (incr 10)11> (incr 10 5)15Exerc��cio 11.1.1 De�na a fun�c~ao eleva, que eleva um n�umero a umadeterminada potencia. Se a potencia n~ao for indicada dever�a ser consi-derada 2. Nota: a express~ao (expt x y) determina a potencia y de x,i.e., xy.Solu�c~ao do Exerc��cio 11.1.1(defun eleva (x &optional (n 2))(expt x n))Os parametros opcionais permitem ainda simpli�car a escrita deprocessos iterativos, ao aumentarem o n�umero de vari�aveis de estadosem obrigar o utilizador a inicializ�a-las. No entanto, �e usual considerar-se esta utiliza�c~ao como mau estilo de programa�c~ao.Exerc��cio 11.1.2 Reescreva a fun�c~ao factorial de forma a gerar umprocesso iterativo mas sem usar fun�c~oes auxiliares.Solu�c~ao do Exerc��cio 11.1.2(defun fact (n &optional (result 1))(if (= n 0)result(fact (1- n) (* n result))))11.2 Parametros de RestoPara al�em do quali�cador &optional existem ainda o &rest e o &key.O &rest s�o pode quali�car o �ultimo parametro de uma fun�c~ao, e indicaque esse parametro vai �car ligado a uma lista com todos os restantesargumentos. A t��tulo de exemplo, temos:> ((lambda (x y &rest z) (list x y z)) 1 2 3 4 5 6)(1 2 (3 4 5 6))O quali�cador &rest permite assim construir fun�c~oes com qualquern�umero de argumentos.Exerc��cio 11.2.1 De�na a fun�c~ao lista que recebe qualquer n�umero deargumentos e constroi uma lista com todos eles.Solu�c~ao do Exerc��cio 11.2.1(defun lista (&rest elems)elems)>(lista 1 2 3 4 5 6 7 8 9 0)(1 2 3 4 5 6 7 8 9 0) 58

Page 60: Intro Lisp

11.3 Parametros de ChaveO quali�cador &key informa o avaliador que os parametros quali�cadoss~ao ligados atrav�es de uma indica�c~ao expl��cita de quem chama a fun�c~ao.Essa indica�c~ao �e feita designando o nome de cada parametro precedidopor dois pontos e indicando qual o valor a que ele deve estar ligado.Os parametros que n~ao forem ligados comportam-se como se fossemopcionais. Este g�enero de parametros dizem-se de chave (keyword).Desta forma, o &key permite trocar a ordem dos argumentos. Oseguinte 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 fun�c~oes pr�e-de�nidas na linguagem para ma-nipular listas possui parametros opcionais e de chave. Repare-se queas chaves s~ao tratadas de forma especial pelo avaliador. Efectivamen-te, se assim n~ao fosse, quando especi�c�avamos os argumentos de umafun�c~ao com parametros de chave, o avaliador iria tentar determinar ovalor das chaves, gerando ent~ao um erro por estas n~ao terem valor. Narealidade, quando um s��mbolo qualquer �e precedido por dois pontos,esse s��mbolo �e considerado como especial, pertencendo ao conjunto doss��mbolos chaves, e avaliando para si pr�oprio.> olaError: Unbound variable: OLA> 'olaOLA> :ola:OLAOs tipos aglomerados constituem uma das grandes utiliza�c~oes dosparametros de chave. Nestes tipos de dados, os constructores limitam-se a realizar um agrupamento de valores para os diversos constituintesdo tipo. Vimos o exemplo de um autom�ovel, que era constituido poruma marca, um modelo, um n�umero de portas, etc. Embora n~ao existaqualquer raz~ao para que a marca de um autom�ovel seja mais importan-te que o seu n�umero de portas, infelizmente a ordena�c~ao impl��cita dosargumentos das fun�c~oes Lisp imp~oem que assim seja. Podemos resolvereste problema usando parametros de chave, de forma a eliminar a orde-na�c~ao dos argumentos e permitir ao utilizador especi�c�a-los pela ordemque entender, por exemplo:(defun novo-automovel (&key marca modelo portas)(list marca modelo portas))> (novo-automovel :portas 2 :marca 'honda :modelo 'civic)(honda civic 2) 59

Page 61: Intro Lisp

Para al�em da possibilidade de altera�c~ao da ordem dos argumentos, oquali�cador &key ajuda �a legibilidade do programa, ao tornar expl��citoo papel que cada argumento tem na fun�c~ao.12 MacrosComo referimos na apresenta�c~ao da linguagem Lisp, existem certas for-mas da linguagem que n~ao obedecem �as regras de avalia�c~ao usuais. Essasformas designam-se formas especiais e o if �e um exemplo. Cada for-ma especial possui a sua pr�opria regra de avalia�c~ao. Vimos que, porisso, era imposs��vel de�nir o if como se fosse uma fun�c~ao, pois todosos operandos (o teste, o consequente e a alternativa) seriam avaliados.Embora a linguagem Lisp possua muitas formas especiais, �e poss��vel\criar" outras formas especiais atrav�es da utiliza�c~ao de macros. Umamacro �e uma forma que a linguagem expande para outra forma, supe-rando assim as di�culdades inerentes �a avalia�c~ao dos argumentos que asfun�c~oes realizam. Na realidade, Lisp possui muito poucas formas espe-ciais reais. A grande maioria das formas especiais s~ao implementadasatrav�es de macros, usando a forma especial defmacro cuja sintaxe �eigual �a da defun.12.1 Avalia�c~ao de MacrosA utiliza�c~ao de uma macro implica duas avalia�c~oes. Na primeira, amacro produz uma express~ao Lisp a partir dos seus argumentos, quese designa a expans~ao da macro. Esta express~ao �e ent~ao avaliada umasegunda vez para produzir o valor �nal. A t��tulo de exemplo, se de�-nirmos o if como uma macro que expande para um cond e avaliarmosa express~ao (if (> 3 4) (+ 1 2) (- 5 2)), a primeira avalia�c~ao de-ver�a produzir a express~ao (cond ((> 3 4) (+ 1 2)) (t (- 5 2))),que ser�a avaliada segunda vez para determinar o seu valor.Note-se que, neste exemplo, se a forma cond fosse, tamb�em ela, umamacro, o processo era aplicado recursivamente at�e que n~ao surgisse maisnenhuma macro. Nessa altura, o Lisp usava a regra usual de avalia�c~aopara determinar o valor �nal da express~ao12.2 Escrita de MacrosA escrita de uma macro �e inerentemente mais complexa que a escritade uma fun�c~ao, sendo decomposta em quatro fases:1. Decidir se a macro �e realmente necess�aria. Esta fase �e de gran-de importancia, pois cada vez que se de�ne uma macro est�a-se aaumentar a linguagem com uma nova forma especial. Quem pre-tende ler um programa que usa a macro �e obrigado a conhecer asua sintaxe e semantica, e se o n�umero de macros �e muito grande,pode ser dif��cil perceber o c�odigo.60

Page 62: Intro Lisp

2. Escrever a sintaxe da macro. Nesta fase pretende-se de�nir qualvai ser a forma de utiliza�c~ao da macro. A sintaxe deve ser o maissimples poss��vel e o mais coerente poss��vel com as restantes formasda linguagem para n~ao complicar a sua leitura.3. Escrever a expans~ao da macro. Nesta fase determina-se a ex-press~ao Lisp que a macro deve produzir quando expandida. Aexpans~ao �e qualquer express~ao Lisp, que pode inclusiv�e fazer re-ferencia a outras macros.4. Escrever a macro usando a forma especial defmacro. �E esta a fasemais delicada do processo, em que se programa um processo detransformar a forma especial que queremos de�nir numa express~aoque use outras formas especiais j�a de�nidas.A t��tulo de exemplo vamos de�nir a forma especial meu-if cujoobjectivo �e simpli�car o uso do cond quando s�o existe um teste, umconsequente e uma alternativa.A sintaxe da forma meu-if �e:(meu-if teste consequente alternativa)A expans~ao da macro ser�a qualquer coisa da forma:(cond (teste consequente)(t alternativa))A de�ni�c~ao da macro �e:(defmacro meu-if (teste consequente alternativa)(list 'cond(list teste consequente)(list t alternativa)))12.3 Depura�c~ao de MacrosUma vez que a aplica�c~ao de uma macro mostra apenas o resultado�nal, depois de a macro ter sido expandida e a sua expans~ao avalia-da, �e necess�ario um meio auxiliar para visualizarmos se a expans~aoest�a a ser feita de modo correcto. Para isso o Lisp fornece as fun�c~oesmacroexpand-1 e macroexpand que realizam a expans~ao da macro uma�unica vez ou todas as vezes poss��veis, respectivamente.> (macroexpand-1 '(meu-if (> 3 4) (+ 2 3) (- 5 3)))(COND ((> 3 4) (+ 2 3)) (T (- 5 3)))Exerc��cio 12.3.1 Implemente a macro quando, que recebe um teste eum conjunto de express~oes. Esta forma especial avalia o teste e, quandoeste �e verdade, avalia sequencialmente as express~oes, devolvendo o valorda �ultima. Se o teste �e falso, a forma retorna nil sem avaliar mais nada.61

Page 63: Intro Lisp

Solu�c~ao do Exerc��cio 12.3.1 A sintaxe da macro �e:(quando testeexpr 1 expr 2 ... expr n)A expans~ao desejada �e:(cond (testeexpr 1 expr 2 ... expr n)(t nil))A macro ser�a:(defmacro quando (teste &rest exprs)(list 'cond(cons teste exprs)'(t nil)))Esta macro j�a existe em Lisp e denomina-se when.12.4 Caracteres de MacroGeralmente, a parte mais dif��cil na escrita de uma macro �e a determi-na�c~ao das express~oes Lisp que quando avaliadas produzem uma novaexpress~ao Lisp que realiza o nosso objectivo. Para simpli�car esta tare-fa �e usual utilizarem-se caracteres especiais que, �a semelhan�ca da plica(quote) e do cardinal-plica (function) s~ao transformados na leiturapara outras express~oes. Cada um dos caracteres especiais possui umafun�c~ao Lisp associada que �e avaliada quando a linguagem, durante aleitura das express~oes, encontra um desses caracteres. Essa fun�c~ao Lisppode ent~ao realizar mais leituras e retornar o que achar mais convenien-te. Estes caracteres especiais s~ao designados caracteres de macro poiseles s~ao transformados (s~ao expandidos) na leitura em outras express~oes,um pouco �a imagem do que acontecia com as macros. A diferen�c~aoest�a no instante em que a expans~ao ocorre. Uma macro �e expandidaem tempo de compila�c~ao (ou, em algumas implementa�c~oes de Lisp, emtempo de execu�c~ao). Um car�acter de macro �e expandido na leitura deexpress~oes.Qualquer car�acter pode ser considerado especial, bastando, para is-so, usar a fun�c~ao set-macro-caracter que recebe um car�acter e umafun�c~ao a aplicar sempre que o car�acter for lido. A fun�c~ao a aplicar devepossuir dois parametros que receber~ao, o primeiro, o local donde o Lispestava a ler (terminal, �cheiro, etc) para que a fun�c~ao possa continuara leitura do mesmo s��tio, e o segundo, o pr�oprio car�acter de macro.Para se indicar um car�acter em Lisp �e necess�ario precede-lo doscaracteres \#\". Por exemplo, o car�acter $ �e indicado por \#\$". Comoj�a �e sabido, se n~ao se inclu��ssem os caracteres \#\", o Lisp considerariao objecto lido como um s��mbolo e n~ao como um car�acter.Para se compreender a utiliza�c~ao dos caracteres de macro, podemosadmitir que n~ao existia o car�acter de plica e que pretend��amos de�ni-lo. A express~ao 'ola representa, como sabemos, (quote ola), logo a62

Page 64: Intro Lisp

fun�c~ao que seria invocada quando se encontrasse a plica necessitaria deler o objecto que estava ap�os a plica (usando a fun�c~ao read) e construiriauma lista com o s��mbolo quote �a cabe�ca e o objecto lido no �m, ou seja:(defun plica (canal-leitura caracter)(list (quote quote) (read canal-leitura)))> (set-macro-character #� (function plica))T> 'olaOLAA linguagem Lisp possui, previamente de�nidos, v�arios caracteresde macro. A plica �e um deles, o ponto e v��rgula (que representa um co-ment�ario) �e outro, etc. Alguns dos caracteres de macro s~ao precedidospor um car�acter especial, considerado o car�acter de despacho, permitin-do aumentar o n�umero de poss��veis caracteres de macro sem reduzir on�umero de caracteres normais utiliz�aveis. O car�acter de despacho maisusado �e o cardinal, mas pode ser qualquer outro. Como exemplos destescaracteres de macro com despacho temos o cardinal-plica para indicarfun�c~oes, o cardinal-barra para indicar caracteres, etc.De todos os caracteres de macro, aqueles que s~ao particularmente�uteis para a escrita de macros s~ao o plica para tr�as (`|backquote), av��rgula (,|comma) e o v��rgula-arroba (,@|comma-at).O backquote indica ao avaliador que n~ao deve avaliar uma express~aoexcepto quando for indicado em contr�ario. Quando usado isoladamente,o backquote funciona exactamente como o quote. No entanto, a suacombina�c~ao com o comma e o comma-at permite simpli�car imenso aescrita de express~oes complexas. O comma s�o pode ser utilizado numaexpress~ao (uma lista, tipicamente) precedida do backquote, e informao avaliador que a express~ao que se segue �e para avaliar e o seu resultadoinserido na lista. O comma-at �e identico mas o resultado da avalia�c~aotem de ser uma lista cujos elementos s~ao inseridos.A t��tulo de exemplo, temos:> `((+ 1 2) ,(+ 1 2) (list 1 2) ,(list 1 2) ,@(list 1 2))((+ 1 2) 3 (LIST 1 2) (1 2) 1 2)12.5 Macros �UteisExerc��cio 12.5.1 Reescreva a macro quando usando o backquote, ocomma e o comma-at.Solu�c~ao do Exerc��cio 12.5.1(defmacro quando (teste &rest exprs)`(cond (,teste ,@exprs)(t nil))) 63

Page 65: Intro Lisp

Exerc��cio 12.5.2 Escreva a macro a-menos-que, que recebe um teste eum conjunto de express~oes. Esta forma especial avalia o teste e, quandoeste �e falso, avalia sequencialmente as express~oes, devolvendo o valorda �ultima. Se o teste �e verdade, a forma retorna nil sem avaliar maisnada.Solu�c~ao do Exerc��cio 12.5.2(defmacro a-menos-que (teste &rest exprs)`(cond (,teste nil)(t ,@exprs)))Esta macro j�a existe em Lisp e denomina-se unless.Exerc��cio 12.5.3 Escreva uma implementa�c~ao da macro meu-cond u-sando a forma especial if.Solu�c~ao do Exerc��cio 12.5.3(defmacro meu-cond (&rest clausulas)(if (null clausulas)nil`(if ,(caar clausulas)(progn ,@(cdar clausulas))(meu-cond ,@(cdr clausulas)))))Exerc��cio 12.5.4 A implementa�c~ao da macro meu-cond anterior im-plica que a expans~ao �e apenas parcial, i.e., enquanto houver cl�ausulas, omeu-cond expande para outro meu-cond. Implemente a macro meu-condde modo a realizar a expans~ao de uma s�o vez.Solu�c~ao do Exerc��cio 12.5.4 Em princ��pio, bastaria for�car a avalia�c~ao do meu-cond �nalmas, como a de�ni�c~ao tem um parametro do tipo &rest, o que iria ser passado n~ao eramas cl�ausulas restantes mas sim uma lista com as cl�ausulas restantes, o que seria incorrecto(o parametro cl�ausulas teria como valor uma lista com a lista das cl�ausulas restantes). Oproblema resume-se ent~ao a eliminar o parametro do tipo &rest, o que poderemos fazerrecorrendo a uma fun�c~ao local.(defmacro meu-cond (&rest clausulas)(labels ((expande (clausulas)(if (null clausulas)nil`(if ,(caar clausulas)(progn ,@(cdar clausulas)),(expande (cdr clausulas))))))(expande clausulas)))Exerc��cio 12.5.5 A macro seja implementa a mesma funcionalidadeque a forma especial let. Como se sabe, o let n~ao �e mais do que umamacro que expande para uma lambda. A ideia ser�a de�nir a macro sejaexactamente da mesma forma, i.e., dever�a existir uma correspondenciaentre a express~ao:(seja ((x 10) (y 20))(+ x y))e a sua expans~ao:((lambda (x y) (+ x y)) 10 20)64

Page 66: Intro Lisp

De�na a macro seja de modo a implementar essa correspondencia.Solu�c~ao do Exerc��cio 12.5.5(defmacro seja (vars-vals &rest exprs)`((lambda ,(mapcar #'car vars-vals),@exprs),@(mapcar #'cadr vars-vals)))> (seja ((x 10) (y 20)) (+ x y))30 Como se disse, esta macro j�a existe em Lisp e designa-se let.Exerc��cio 12.5.6 Escreva a macro enquanto, que recebe um teste eum conjunto de express~oes. A forma enquanto deve avaliar o testee, caso seja verdade, avaliar sequencialmente as express~oes e voltar aoprinc��pio. Caso o teste seja falso, deve terminar com o valor nil.Solu�c~ao do Exerc��cio 12.5.6(defmacro enquanto (teste &rest exprs)`(loop(a-menos-que ,teste (return nil)),@exprs))Exerc��cio 12.5.7 Escreva a macro caso, que recebe uma express~ao eum conjunto de pares �atomo-express~oes. A forma especial caso avaliaa primeira express~ao, e compara o resultado (usando a fun�c~ao eql) comcada um dos �atomos em sequencia. Se um dos �atomos emparelhar, s~aoavaliadas as express~oes a ele associadas e retornado o valor da �ultima.Um exemplo de utiliza�c~ao seria:(defun inverso-fact (x)(caso x(1 (print 'basico) 1)(2 (print 'menos-basico) 2)(6 (print 'ainda-menos-basico) 3)))> (inverso-fact 1)BASICO1Solu�c~ao do Exerc��cio 12.5.7(defmacro caso (exp &rest clausulas)`(let ((temp ,exp))(cond ,@(mapcar #'(lambda (clausula)`((eql temp ,(car clausula)) ,@(cdr clausula)))clausulas))))Esta forma especial j�a existe em Lisp e denomina-se case.65

Page 67: Intro Lisp

12.6 IteradoresComo se pode depreender dos exemplos apresentados, as macros desti-nam-se essencialmente �a cria�c~ao de a�c�ucar sint�atico, i.e., de express~oesque sejam mais simples de utilizar que outras j�a existentes. Esta carac-ter��stica torna as macros ferramentas extremamente �uteis para cria�c~aode tipos abstractos de informa�c~ao capazes de dar ao utilizador iteradoressobre os objectos desse tipo.A t��tulo de exemplo, vamos considerar a de�ni�c~ao de um iteradorpara os elementos de uma lista. Este iterador dever�a ser uma formaespecial que recebe um s��mbolo (uma vari�avel), uma lista e um conjuntode express~oes, e itera aquelas express~oes com o s��mbolo ligado a cadaelemento da lista. Apresenta-se agora um exemplo da sintaxe da formaespecial:(itera-lista (elem (list 1 2 3 4 5))(print elem))A sua de�ni�c~ao �e relativamente simples:(defmacro itera-lista (var-e-lista &rest exprs)`(let ((lista ,(cadr var-e-lista))(,(car var-e-lista) nil))(loop(unless lista (return nil))(setq ,(car var-e-lista) (car lista)lista (cdr lista)),@exprs)))> (itera-lista (x '(1 2 3)) (print x))123NILInfelizmente, nem tudo est�a bem. Reparemos no seguinte exemplo:> (let ((lista '(1 2 3)))(itera-lista (x '(4 5 6))(print (cons x lista))))(4 5 6)(5 6)(6)NILO problema est�a no facto de a macro estabelecer uma vari�avel deno-minada lista, que interfere com a vari�avel exterior do segundo exem-plo, pois tem o mesmo nome, �cando assim obscurecida. A referenciaa lista feita no corpo da macro refere-se assim �a vari�avel interna da66

Page 68: Intro Lisp

macro, e n~ao �a que seria desej�avel. Nesta situa�c~ao diz-se que a macrocapturou vari�aveis.A solu�c~ao para este problema est�a na utiliza�c~ao de vari�aveis quen~ao possam interferir de modo algum com outras j�a existentes. Umrem�edio poss��vel ser�a criar as vari�aveis necess�arias �as macros com nomesestranhos, com pouca probabilidade de serem usados pelo utilizadorda macro, como por exemplo, %%%$$$lista$$$%%%. No entanto estasolu�c~ao n~ao �e perfeita. O melhor a fazer �e usar novos s��mbolos quen~ao se possam confundir com os j�a existentes. A fun�c~ao gensym produzum s��mbolo novo e �unico de cada vez que �e chamada, sendo ideal pararesolver estas di�culdades.Exerc��cio 12.6.1 De�na a macro itera-lista usando a referida fun-�c~ao gensym para proteger as vari�aveis do utilizador de uma capturaindevida.Solu�c~ao do Exerc��cio 12.6.1(defmacro itera-lista (var-e-lista &rest exprs)(let ((var-lista (gensym)))`(let ((,var-lista ,(cadr var-e-lista))(,(car var-e-lista) nil))(loop(unless ,var-lista (return nil))(setq ,(car var-e-lista) (car ,var-lista),var-lista (cdr ,var-lista)),@exprs))))> (let ((lista '(1 2 3)))(itera-lista (x '(4 5 6))(print (cons x lista))))(4 1 2 3)(5 1 2 3)(6 1 2 3)NIL Esta forma especial j�a existe em Lisp e denomina-se dolist.Exerc��cio 12.6.2 De�na de novo a forma especial itera-lista, masrecorrendo desta vez �a abordagem da programa�c~ao funcional, i.e., semutilizar formas especiais para ciclos nem atribui�c~ao de valores a va-ri�aveis.Solu�c~ao do Exerc��cio 12.6.2(defmacro itera-lista (var-e-lista &rest exprs)(let ((itera (gensym)) (lista (gensym)))`(labels ((,itera (,lista)(if (null ,lista)nil(let ((,(car var-e-lista) (car ,lista))),@exprs(,itera (cdr ,lista))))))(,itera ,(cadr var-e-lista)))))Note-se que a de�ni�c~ao da macro implicou a utiliza�c~ao de s��mbolos �unicos (via gensym)quer para o nome de um parametro quer para o nome de uma fun�c~ao local.67

Page 69: Intro Lisp

Exerc��cio 12.6.3 A forma especial caso de�nida anteriormente sofriado mesmo problema do itera-lista, pois a vari�avel usada para guar-dar o valor tempor�ario pode obscurecer vari�aveis identicas declaradasexteriormente. Rede�na a macro de forma a evitar esse perigo.Solu�c~ao do Exerc��cio 12.6.3(defmacro caso (exp &rest clausulas)(let ((temp (gensym)))`(let ((,temp ,exp))(cond ,@(mapcar #'(lambda (clausula)`((eql ,temp ,(car clausula)) ,@(cdr clausula)))clausulas)))))12.7 FichasComo vimos, uma �cha (no sentido da linguagem Pascal) n~ao �e maisdo que um tipo aglomerado. Uma �cha possui um nome e �e compostapor um conjunto de campos, cada um com um nome distinto. Cada ele-mento de um dado tipo de �cha corresponde a um conjunto de valoresapropriados para cada campo desse tipo. A utiliza�c~ao de �chas �e de talmodo simples que todas as linguagens de programa�c~ao possuem capaci-dades pr�oprias para lidar com elas. Um dado tipo de �cha n~ao necessitamais do que um construtor para os elementos desse tipo, um selectore um modi�cador para cada um dos campos da �cha e, possivelmente,um reconhecedor de �chas de um dado tipo.Dadas as capacidades da linguagem Lisp em aglomerar facilmenteobjectos, a implementa�c~ao de �chas �e uma tarefa de tal modo simplesque �e bastante f�acil automatiz�a-la.Vimos na descri�c~ao do tipo aglomerado automovel que ele era de�-nido por um constructor:(defun novo-automovel (&key marca modelo portas) ...)e pelos selectores:(defun automovel-marca (automovel) ...)(defun automovel-modelo (automovel) ...)(defun automovel-portas (automovel) ...)e ainda pelos modi�cadores:(defun muda-automovel-marca! (automovel nova-marca) ...)(defun muda-automovel-modelo! (automovel novo-modelo) ...)(defun muda-automovel-portas! (automovel novo-portas) ...)Vamos tamb�em incluir um reconhecedor de autom�oveis muito �utilpara distinguirmos os diversos tipos de �chas:68

Page 70: Intro Lisp

(defun automovel? (obj) ...)Repare-se que o conjunto de fun�c~oes que apresent�amos constituemum modelo para a de�ni�c~ao de �chas. A �cha automovel possui umconstrutor criado atrav�es da concatena�c~ao da palavra \novo" ao nomeda �cha. Cada selector �e dado pela concatena�c~ao do nome da �cha aonome do campo a que ele diz respeito. Cada modi�cador �e dado pelaconcatena�c~ao da palavra \muda" ao nome do selector correspondente eterminado com a letra \!'. O reconhecedor �e dado pelo nome da �chaterminado com a letra \?'. Qualquer outra �cha seria de�nida de formaidentica, pelo que podemos fazer uma abstrac�c~ao da de�ni�c~ao de �chas,criando uma macro que encapsule a de�ni�c~ao de todas aquelas fun�c~oes.Um dos problemas que temos de resolver �e a implementa�c~ao das�chas. Vimos no exemplo do autom�ovel que o podiamos implementarcomo uma lista com os valores dos v�arios campos. Infelizmente, aquelaimplementa�c~ao n~ao nos permite distinguir o tipo de registo autom�ovel deoutros tipos de registo, pelo que temos de modi�car a implementa�c~aode modo a isso ser poss��vel. Para minimizar as altera�c~oes, podemosconsiderar que cada registo �e implementado por uma lista cujo primeiroelemento �e o nome da �cha e cujos restantes elementos s~ao os valoresdos campos da �cha. Isto permite manter a mesma l�ogica de acessoe modi�ca�c~ao dos campos desde que os ��ndices desses campos na listasejam incrementados de uma unidade.Assim sendo, o construtor �car�a qualquer coisa do g�enero:(defun novo-automovel (&key marca modelo portas)(list 'automovel marca modelo portas))Um dado selector ser�a :(defun automovel-marca (automovel)(nth 1 automovel))Um modi�cador ser�a:(defun muda-automovel-marca! (automovel nova-marca)(muda-n-esimo! 1 automovel nova-marca))O reconhecedor de autom�oveis �car�a:(defun automovel? (obj)(and (listp obj) (eql (first obj) 'automovel)))Podemos agora de�nir uma macro designada ficha, cuja utiliza�c~aoser�a da seguinte forma:(ficha automovelmarca modelo portas)A expans~ao da macro dever�a ser qualquer coisa da forma:69

Page 71: Intro Lisp

(progn(defun novo-automovel (&key marca modelo portas) ...)(defun automovel-marca (automovel) ...)(defun automovel-modelo (automovel) ...)(defun automovel-portas (automovel) ...)(defun muda-automovel-marca! (automovel nova-marca) ...)(defun muda-automovel-modelo! (automovel novo-modelo) ...)(defun muda-automovel-portas! (automovel novo-portas) ...)(defun automovel? (obj) ...)'carro)A de�ni�c~ao da macro �e, assim, relativamente simples:(defmacro ficha (nome &rest campos)`(progn,(construtor-ficha nome campos),@(selectores-ficha nome campos),@(modificadores-ficha nome campos),(reconhecedor-ficha nome)',nome))Para de�nirmos as v�arias opera�c~oes vamos necessitar de concatenars��mbolos. Para isso, �e necess�ario criar um s��mbolo, atrav�es da fun�c~aointern, cujo nome seja a concatena�c~ao, atrav�es da fun�c~ao concatenate,dos nomes dos s��mbolos, obtidos mapeando a fun�c~ao string nessess��mbolos, i.e.:(defun junta-nomes (&rest nomes)(intern (apply #'concatenate'string(mapcar #'string nomes))))Com a ajuda desta fun�c~ao, j�a podemos de�nir as restantes fun�c~oesda macro.

70

Page 72: Intro Lisp

(defun construtor-ficha (nome campos)`(defun ,(junta-nomes 'novo- nome) (&key ,@campos)(list ',nome ,@campos)))(defun selectores-ficha (nome campos)(mapcar#'(lambda (campo)`(defun ,(junta-nomes nome '- campo) (,nome)(nth ,(1+ (position campo campos)) ,nome)))campos))(defun modificadores-ficha (nome campos)(mapcar#'(lambda (campo)`(defun ,(junta-nomes 'muda- nome '- campo '!)(,nome novo)(muda-n-esimo! ,(1+ (position campo campos)),nome novo)))campos))(defun reconhecedor-ficha (nome)`(defun ,(junta-nomes nome '?) (obj)(and (listp obj) (eql (first obj) ',nome))))Podemos agora experimentar a macro e visualizar os resultados:> (macroexpand-1 '(ficha automovel marca modelo portas)))(PROGN(DEFUN NOVO-AUTOMOVEL (&KEY MARCA MODELO PORTAS)(LIST 'AUTOMOVEL MARCA MODELO PORTAS))(DEFUN AUTOMOVEL-MARCA (AUTOMOVEL)(NTH 1 AUTOMOVEL))(DEFUN AUTOMOVEL-MODELO (AUTOMOVEL)(NTH 2 AUTOMOVEL))(DEFUN AUTOMOVEL-PORTAS (AUTOMOVEL)(NTH 3 AUTOMOVEL))(DEFUN MUDA-AUTOMOVEL-MARCA! (AUTOMOVEL NOVO)(MUDA-N-ESIMO! 1 AUTOMOVEL NOVO))(DEFUN MUDA-AUTOMOVEL-MODELO! (AUTOMOVEL NOVO)(MUDA-N-ESIMO! 2 AUTOMOVEL NOVO))(DEFUN MUDA-AUTOMOVEL-PORTAS! (AUTOMOVEL NOVO)(MUDA-N-ESIMO! 3 AUTOMOVEL NOVO))(DEFUN AUTOMOVEL? (OBJ)(AND (LISTP OBJ) (EQL (first OBJ) 'AUTOMOVEL)))'AUTOMOVEL)Exerc��cio 12.7.1 Um dos melhoramentos que seria interessante incluirna de�ni�c~ao de �chas seria a inclus~ao de valores por omiss~ao para qual-71

Page 73: Intro Lisp

quer campo. Poderiamos indicar que um autom�ovel possui geralmentequatro portas da seguinte forma:(ficha automovelmarcamodelo(portas 4))Assim, se se criasse um autom�ovel sem especi�car o n�umero de por-tas, ele seria de 4. Implemente essa altera�c~ao.Solu�c~ao do Exerc��cio 12.7.1 Uma vez que os parametros &key podem ser inicializadose a sintaxe �e igual �a usada na �cha, basta modi�car o construtor e passar apenas os nomesdos campos (esquecendo as inicializa�c~oes por omiss~ao) �as restantes fun�c~oes.(defun nome-campo (campo)(if (listp campo) (first campo) campo))(defmacro ficha (nome &rest campos)(let ((nomes-campos (mapcar #'nome-campo campos)))`(progn,(construtor-ficha nome nomes-campos campos),@(selectores-ficha nome nomes-campos),@(modificadores-ficha nome nomes-campos),(reconhecedor-ficha nome)',nome))(defun construtor-ficha (nome nomes-campos campos)`(defun ,(junta-nomes 'novo- nome) (&key ,@campos)(list ',nome ,@nomes-campos)))> (macroexpand-1 '(ficha automovel marca modelo (portas 4))))(PROGN(DEFUN NOVO-AUTOMOVEL (&KEY MARCA MODELO (PORTAS 4))(LIST 'AUTOMOVEL MARCA MODELO PORTAS))(DEFUN AUTOMOVEL-MARCA (AUTOMOVEL)(NTH 1 AUTOMOVEL))...Esta forma de cria�c~ao de �chas j�a existe em Common Lisp atrav�es da macro defstruct.

72

Page 74: Intro Lisp

�Indice Remissivo(), 381+, 191-, 19<=, 13<, 13=, 13, 32>=, 13>, 13&key, 60, 73&optional, 58&rest, 59and, 13, 17append, 42, 44apply, 45atom, 31backquote, 64butlast, 44car, 33, 38case, 66cdr, 33, 38comma-at, 64comma, 64concatenate, 71cond, 15, 17, 49, 61cons, 33, 38defconstant, 56defmacro, 61, 62defparameter, 56defstruct, 73defun, 10, 17, 26, 49, 55defvar, 56dolist, 68do, 52endp, 39eql, 33, 66eq, 32, 34every, 45first, 39, 44flet, 28funcall, 22, 45function, 22, 26, 40, 63gensym, 68if, 14, 17, 61

intern, 71labels, 28last, 44length, 41let, 27, 48, 49, 53, 54, 66list, 39, 44, 46loop, 52mapcar, 42max, 26member, 43nconc, 50nil, 13, 38notany, 45notevery, 45not, 13nth, 41null, 38or, 13, 17prog1, 49progn, 48quote, 32, 39, 63read, 64remove-duplicates, 44remove-if-not, 39remove, 43rest, 39return, 52reverse, 42rplaca, 49rplacd, 49set-macro-caracter, 63setq, 48, 53{55some, 45string, 71subst, 43trace, 21t, 13unless, 65untrace, 21when, 63zerop, 1373