antónio menezes leitão fevereiro 2007 · para a interpretação da fórmula original não se...

142
Introdução à Programação com AutoLisp António Menezes Leitão Fevereiro 2007

Upload: phungminh

Post on 04-Nov-2018

215 views

Category:

Documents


0 download

TRANSCRIPT

Introdução à Programação com AutoLisp

António Menezes Leitão

Fevereiro 2007

Conteúdo

1 Prefácio 3

2 Programação 32.1 Linguagens de Programação . . . . . . . . . . . . . . . . . . . . . 52.2 Lisp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62.3 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

3 Sintaxe, Semântica e Pragmática 73.1 Sintaxe e Semântica do Lisp . . . . . . . . . . . . . . . . . . . . . 83.2 O Avaliador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

4 Elementos da Linguagem 94.1 Elementos primitivos . . . . . . . . . . . . . . . . . . . . . . . . . 10

4.1.1 Números . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104.2 Avaliação de Combinações . . . . . . . . . . . . . . . . . . . . . . 114.3 Operadores de Strings . . . . . . . . . . . . . . . . . . . . . . . . 124.4 Definição de Funções . . . . . . . . . . . . . . . . . . . . . . . . . 134.5 Símbolos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154.6 Avaliação de Símbolos . . . . . . . . . . . . . . . . . . . . . . . . 164.7 Funções de Múltiplos Parâmetros . . . . . . . . . . . . . . . . . . 184.8 Encadeamento de Funções . . . . . . . . . . . . . . . . . . . . . . 184.9 Funções Pré-Definidas . . . . . . . . . . . . . . . . . . . . . . . . 194.10 Aritmética de Inteiros em Auto Lisp . . . . . . . . . . . . . . . . 214.11 Aritmética de Reais em Auto Lisp . . . . . . . . . . . . . . . . . 234.12 Símbolos e Avaliação . . . . . . . . . . . . . . . . . . . . . . . . . 25

5 Combinação de Dados 265.1 Coordenadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275.2 Pares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275.3 Operações com Coordenadas . . . . . . . . . . . . . . . . . . . . 295.4 Abstracção de Dados . . . . . . . . . . . . . . . . . . . . . . . . . 305.5 Coordenadas Tri-Dimensionais . . . . . . . . . . . . . . . . . . . 325.6 Coordenadas Bi- e Tri-Dimensionais . . . . . . . . . . . . . . . . 365.7 A Notação de Lista . . . . . . . . . . . . . . . . . . . . . . . . . . 385.8 Átomos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405.9 Tipos Abstractos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415.10 Coordenadas em AutoCad . . . . . . . . . . . . . . . . . . . . . . 425.11 Coordenadas Polares . . . . . . . . . . . . . . . . . . . . . . . . . 435.12 A função command . . . . . . . . . . . . . . . . . . . . . . . . . . 465.13 Variantes de Comandos . . . . . . . . . . . . . . . . . . . . . . . 525.14 Ângulos em Comandos . . . . . . . . . . . . . . . . . . . . . . . . 535.15 Efeitos Secundários . . . . . . . . . . . . . . . . . . . . . . . . . . 545.16 A Ordem Dórica . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555.17 Parameterização de Figuras Geométricas . . . . . . . . . . . . . 595.18 Documentação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

1

5.19 Depuração . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 655.19.1 Erros Sintáticos . . . . . . . . . . . . . . . . . . . . . . . . 665.19.2 Erros Semânticos . . . . . . . . . . . . . . . . . . . . . . . 67

6 Modelação Tridimensional 696.1 Sólidos Tridimensionais Pré-Definidos . . . . . . . . . . . . . . . 696.2 Modelação de Colunas Dóricas . . . . . . . . . . . . . . . . . . . 73

7 Expressões Condicionais 767.1 Expressões Lógicas . . . . . . . . . . . . . . . . . . . . . . . . . . 767.2 Valores Lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 767.3 Predicados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 777.4 Predicados Aritméticos . . . . . . . . . . . . . . . . . . . . . . . . 777.5 Operadores Lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . 777.6 Predicados com número variável de argumentos . . . . . . . . . 787.7 Predicados sobre Cadeias de Caracteres . . . . . . . . . . . . . . 797.8 Predicados sobre Símbolos . . . . . . . . . . . . . . . . . . . . . . 797.9 Predicados sobre Listas . . . . . . . . . . . . . . . . . . . . . . . . 797.10 Reconhecedores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 807.11 Reconhecedores Universais . . . . . . . . . . . . . . . . . . . . . 807.12 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82

8 Estruturas de Controle 828.1 Sequenciação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 838.2 Invocação de Funções . . . . . . . . . . . . . . . . . . . . . . . . . 848.3 Variáveis Locais . . . . . . . . . . . . . . . . . . . . . . . . . . . . 868.4 Atribuição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 878.5 Variáveis Globais . . . . . . . . . . . . . . . . . . . . . . . . . . . 898.6 Variáveis Indefinidas . . . . . . . . . . . . . . . . . . . . . . . . . 918.7 Proporções de Vitrúvio . . . . . . . . . . . . . . . . . . . . . . . . 918.8 Selecção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 938.9 Selecção Múltipla—A Forma cond . . . . . . . . . . . . . . . . . 968.10 Selecção nas Proporções de Vitrúvio . . . . . . . . . . . . . . . . 978.11 Operações com Coordenadas . . . . . . . . . . . . . . . . . . . . 1018.12 Coordenadas Cilíndricas . . . . . . . . . . . . . . . . . . . . . . . 1058.13 Coordenadas Esféricas . . . . . . . . . . . . . . . . . . . . . . . . 1078.14 Recursão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1088.15 Depuração de Programas Recursivos . . . . . . . . . . . . . . . . 115

8.15.1 Trace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1168.16 Templos Dóricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1198.17 A Ordem Jónica . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1288.18 Recursão na Natureza . . . . . . . . . . . . . . . . . . . . . . . . 134

2

1 Prefácio

2 Programação

A transmissão de conhecimento é um dos problemas que desde cedo preo-cupou a humanidade. Sendo o homem capaz de acumular conhecimento aolongo de toda a sua vida, é com desânimo que enfrenta a ideia de que, com amorte, todo esse conhecimento se perca.

Para o evitar, a humanidade inventou toda uma série de mecanismos detransmissão de conhecimento. O primeiro, a transmissão oral, consiste natransmissão do conhecimento de uma pessoa para um grupo reduzido deoutras pessoas, de certa forma transferindo o problema da perda de conhe-cimento para a geração seguinte. O segundo, a transmissão escrita, consisteem registar em documentos o conhecimento que se pretende transmitir. Estaforma tem a grande vantagem de, por um lado, poder chegar a muitas maispessoas e, por outro, de reduzir significativamente o risco de se perder o co-nhecimento por problemas de transmissão. De facto, a palavra escrita per-mite preservar por muito tempo e sem qualquer tipo de adulteração o conhe-cimento que o autor pretendeu transmitir.

É graças à palavra escrita que hoje conseguimos compreender e acumularum vastíssimo conjunto de conhecimentos, muitos deles registados há milha-res de anos atrás.

Infelizmente, nem sempre a palavra escrita conseguiu transmitir com rigoraquilo que o autor pretendia. A língua natural tem inúmeras ambiguidadese evolui substancialmente com o tempo, o que leva a que a interpretação dostextos seja sempre uma tarefa subjectiva. Quer quando escrevemos um texto,quer quando o lemos e o interpretamos, existem omissões, imprecisões, in-correcções e ambiguidades que podem tornar a transmissão de conhecimentofalível. Se o conhecimento que se está a transmitir for simples, o receptor dainformação, em geral, consegue ter a imaginação e a capacidade suficientespara conseguir ultrapassar os obstáculos mas, no caso da transmissão de co-nhecimentos mais complexos já isso poderá ser muito mais difícil.

Infelizmente, quando se exige rigor na transmissão de conhecimento, fazerdepender a compreensão desse conhecimento da capacidade de interpretaçãode quem o recebe pode ter consequências desastrosas e, de facto, a históriada humanidade está repleta de acontecimentos catastróficos cuja causa é, tãosomente, uma insuficiente ou errónea transmissão de conhecimento.

Para evitar estes problemas, inventaram-se linguagens mais rigorosas. Amatemática, em particular, tem-se obssessivamente preocupado ao longo dosúltimos milénios com a construção de uma linguagem onde o rigor seja abso-luto. Isto permite que a transmissão do conhecimento matemático seja muitomais rigorosa que nas outras áreas, reduzindo ao mínimo essencial a capaci-dade de imaginação necessária de quem está a absorver esse conhecimento.

Para melhor percebermos do que estamos a falar, consideremos, a títulode exemplo, a transmissão do conhecimento relativo à função factorial. Seassumirmos, como ponto de partida, que a pessoa a quem queremos transmitir

3

esse conhecimento já sabe de antemão o que são os números, as variáveis e asoperações aritméticas, podemos definir matematicamente a função factorialnos seguintes termos:

n! = 1× 2× 3× · · · × (n− 1)× n

Será a definição anterior suficientemente rigorosa? Será possível interpretá-la sem necessitar de imaginar a intenção do autor? Aparentemente, sim mas,na verdade, há um detalhe da definição que exige imaginação: as reticências.Aquelas reticências indicam ao leitor que ele terá de imaginar o que deveria es-tar no lugar delas e, embora a maioria dos leitores irá imaginar correctamenteque o autor pretendia a multiplicação dos sucessores dos números anteriores,leitores haverá cuja imaginação delirante poderá tentar substituir aquelas reti-cências por outra coisa qualquer.

Mesmo que excluamos do nosso público-alvo as pessoas de imaginaçãodelirante, há ainda outros problemas com a definição anterior. Pensemos, porexemplo, no factorial de 2. Qual será o seu valor? Se substituirmos na fórmula,para n = 2 obtemos:

2! = 1× 2× 3× · · · × 1× 2

Neste caso, não só as reticências deixam de fazer sentido como toda a fórmuladeixa de fazer sentido, o que mostra que, na verdade, a imaginação necessáriapara a interpretação da fórmula original não se restringia apenas às reticên-cias mas sim a toda a fórmula: o número de termos a considerar depende donúmero do qual queremos saber o factorial.

Admitindo que o nosso leitor teria tido a imaginação suficiente para des-cobrir esse detalhe, ele conseguiria tranquilamente calcular que 2! = 1×2 = 2.Ainda assim, casos haverá que o deixarão significativamente menos tranquilo.Por exemplo, qual é o factorial de zero? A resposta não parece óbvia. E quantoao factorial de −1? Novamente, não está claro. E quanto ao factorial de 4.5?Mais uma vez, a fórmula nada diz e a nossa imaginação também não consegueadivinhar.

Será possível encontrar uma forma de transmitir o conhecimento da funçãofactorial que minimize as imprecisões, lacunas e ambiguidades? Será possívelreduzir ao mínimo razoável a imaginação necessária para compreender esseconhecimento? Experimentemos a seguinte variante da definição da funçãofactorial:

n! =

{1, se n = 0n · (n− 1)!, se n ∈ N.

Teremos agora atingido o rigor suficiente que dispensa imaginação porparte do leitor? A resposta estará na análise dos casos que causaram proble-mas à definição anterior. Em primeiro lugar, não existem reticências, o que épositivo. Em segundo lugar, para o factorial de 2 temos, pela definição:

2! = 2× 1! = 2× (1× 0!) = 2× (1× 1) = 2× 1 = 2

ou seja, não há qualquer ambiguidade. Finalmente, vemos que não faz sentidodeterminar o factorial de −1 ou de 4.5 pois a definição apresentada apenas seaplica aos membros de N0.

4

Este exemplo mostra que, mesmo na matemática, há diferentes graus derigor nas diferentes formas como se expõe o conhecimento. Algumas dessasformas exigem um pouco mais de imaginação e outras um pouco menos mas,em geral, qualquer delas tem sido suficiente para que a humanidade tenhaconseguido preservar o conhecimento adquirido ao longo da sua história.1

Acontece que, actualmente, a humanidade conta com um parceiro que temdado uma contribuição gigantesca para o seu progresso: o computador. Estamáquina tem a extraordinária capacidade de poder ser instruída de forma asaber executar um conjunto complexo de tarefas. A actividade da programaçãoconsiste, precisamente, da transmissão, a um computador, do conhecimentonecessário para resolver um determinado problema. Esse conhecimento é de-nominado um programa. Por serem programáveis, os computadores têm sidousados para os mais variados fins e, sobretudo nas últimas décadas, têm trans-formado radicalmente a forma como trabalhamos. Infelizmente, a extraordi-nária capacidade de “aprendizagem” dos computadores vem acompanhadaduma igualmente extraordinária incapacidade de imaginação. O computadornão imagina, apenas interpreta rigorosamente o conhecimento que lhe trans-mitimos sob a forma de um programa.

Uma vez que não tem imaginação, o computador depende criticamente daforma como lhe apresentamos o conhecimento que queremos transmitir: esseconhecimento tem de estar descrito numa linguagem tal que não possa havermargem para qualquer ambiguidade, lacuna ou imprecisão. Uma linguagemcom estas características denomina-se, genericamente, de linguagem de progra-mação.

2.1 Linguagens de Programação

À medida que os computadores foram resolvendo os problemas que lhes fo-mos colocando, surgiu o interesse de os usarmos para resolver problemas pro-gressivamente mais complexos. Infelizmente, à medida que os problemas setornam mais complexos, também a sua descrição se torna mais complexa.Como a descrição do problema e dos passos necessários para a sua soluçãosão responsabilidade dos seres humanos, essas actividades passaram a exigira colaboração de várias pessoas e passaram a exigir, por vezes, quantidadessubstanciais de trabalho. Felizmente, o rigor que os computadores exigemtem a vantagem de permitir também aos seres humanos formas rigorosas decomunicação entre si.

Uma linguagem de programação não é apenas um meio de indicar a umcomputador uma série de operações a executar. Uma linguagem de programa-ção é, sobretudo, um meio de descrevermos um processo que, se for seguido,irá produzir os resultados desejados.

Uma linguagem de programação deve ser feita para seres humanos dialo-garem acerca de programas e, só incidentalmente, para computadores os exe-cutarem. Como tal, deve possuir ideias simples, deve ser capaz de combinar

1Infelizmente, o descuido e, por vezes, a pura irracionalidade têm permitido destruir algu-mas preciosas colecções de conhecimentos. As sucessivas destruições sofridas pela Bibliotecade Alexandria são um triste exemplo dos frequentes retrocessos da humanidade.

5

ideias simples para formar ideias mais complexas e deve ser capaz de realizarabstracções de ideias complexas para as tornar simples.

Existe uma enorme diversidade de linguagens de programação, umas maisadaptadas a um determinado tipo de processos, outras mais adaptadas a ou-tros. A escolha de uma linguagem de programação deve estar condicionada,naturalmente, pelo tipo de problemas que queremos resolver, mas não deveser um comprometimento total. Para quem programa é muito mais impor-tante compreender os fundamentos e técnicas da programação do que domi-nar esta ou aquela linguagem. No entanto, para mais rigorosamente se expli-car aqueles fundamentos e técnicas, convém exemplificá-los numa linguagemde programação concreta.

Uma vez que este texto se irá focar na programação aplicada à Arquitec-tura, vamos empregar uma linguagem de programação que esteja particular-mente vocacionada para resolver problems geométricos. Várias linguagensexistem com esta vocação, em geral associadas a ferramentas de desenho as-sistido por computador. O ArchiCAD, por exemplo, disponibiliza uma lingua-gem de programação denominada GDL— acrónimo de Geometric DescriptionLanguage—que permite ao utilizador programar as várias formas geométricaspretendidas. Já no caso do AutoCad, a linguagem de programação empregueé o Auto Lisp, um dialecto de uma famosa linguagem de programação deno-minada Lisp.

Embora a linguagem GDL pareça muito diferente da linguagem Auto Lisp,os conceitos fundamentais são muito semelhantes. É sobre estes conceitos fun-damentais da programação que nos iremos debruçar embora, por motivos pe-dagógicos, seja conveniente particularizá-los numa única linguagem. Nestetexto iremos explicar os fundamentos da programação através da utilizaçãoda linguagem Auto Lisp, não só pela sua facilidade de aprendizagem mastambém pela sua aplicabilidade prática. No entanto, uma vez apreendidos es-ses fundamentos, o leitor deverá ser capaz de os traduzir para qualquer outralinguagem de programação.

2.2 Lisp

Lisp é uma linguagem de programação extremamente simples e flexível. Em-bora tenha sido inicialmente inventada como uma linguagem matemática, cedose procedeu à sua implementação em diversos computadores, o que os tornouem executores dos processos descritos nos programas Lisp.

Desde a sua génese que a linguagem Lisp prima pela enorme facilidadede extensão. Isto permite ao programador ampliar a linguagem, dotando-ade novos meios para resolver problemas. Esta capacidade permitiu ao Lispsobreviver à evolução da informática. Embora outras linguagens se tenhamtornado obsoletas, a linguagem Lisp continua activa e é a segunda mais antigalinguagem ainda em utilização, sendo apenas ultrapassada pela linguagemFortran.

A facilidade de utilização, adaptação e extensão da linguagem fez comque surgissem dezenas de diferentes dialectos: FranzLisp, ZetaLisp, LeLisp,MacLisp, InterLisp, Scheme, T, Nil, XLisp e AutoLISP são apenas alguns dos

6

exemplos mais relevantes. Neste livro iremos debruçarmo-nos sobre o dialectoAutoLISP.

A linguagem AutoLISP derivou da linguagem XLisp e foi incorporada noAutoCad em 1986, tendo sido intensivamente usada desde então. Em 1999, oAutoCad passou a disponibilizar uma versão melhorada do AutoLisp deno-minada Visual LISP que, entre outras diferenças, possui um compilador parauma execução mais eficiente e um depurador de programas para facilitar adetecção e correcção de erros. A influência do AutoCad é tão grande que vá-rios outros vendedores decidiram incluir uma implementação de AutoLISPnos seus próprios produtos, de modo a facilitar a transição de utilizadores.

Um aspecto característico do Lisp é ser uma linguagem interactiva. Cadaporção de um programa pode ser desenvolvida, testada e corrigida indepen-dentemente das restantes. Deste modo, Lisp permite que o desenvolvimento,teste e correcção de programas seja feito de uma forma incremental, o que fa-cilita muito a tarefa do programador.

Embora os exemplos que iremos apresentar sejam válidos apenas em Au-toLISP, falaremos genericamente da linguagem Lisp e, apenas quando neces-sário, mencionaremos o AutoLISP.

2.3 Exercícios

Exercício 2.3.1 A exponenciação bn é uma operação entre dois números b e n designados basee expoente, respectivamente. Quando n é um inteiro positivo, a exponenciação define-se comouma multiplicação repetida:

bn = b× b× · · · × b| {z }n

Para um leitor que nunca tenha utilizado o operador de exponenciação, a definição anteriorlevanta várias questões cujas respostas poderão não lhe ser evidentes. Quantas multiplicaçõessão realmente feitas? Serão n multiplicações? Serão n − 1 multiplicações? O que fazer no casob1? E no caso b0?

Proponha uma definição matemática de exponenciação que não levante estas questões.

Exercício 2.3.2 O que é uma linguagem de programação? Para que serve?

3 Sintaxe, Semântica e Pragmática

Todas as linguagens possuem sintaxe, semântica e pragmática.Em termos muito simples, podemos descrever a sintaxe de uma linguagem

como o conjunto de regras que determinam as frases que se podem construirnessa linguagem. Sem sintaxe, qualquer concatenação arbitrária de palavrasconstituiria uma frase. Por exemplo, dadas as palavras “João,” “o,” “comeu,”e “bolo,” as regras da sintaxe da língua Portuguesa dizem-nos que “o Joãocomeu o bolo” é uma frase e que “o comeu João bolo bolo” não é uma frase.Note-se que, de acordo com a sintaxe do Português, “o bolo comeu o João” étambém uma frase sintaticamente correcta.

A sintaxe regula a construção das frases mas nada diz acerca do seu signi-ficado. É a semântica de uma linguagem que nos permite atribuir significado

7

às frases da linguagem e que nos permite perceber que a frase “o bolo comeuo João” não faz sentido.

Finalmente, a pragmática diz respeito à forma usual como se escrevem asfrases da linguagem. Para uma mesma linguagem, a pragmática varia de con-texto para contexto: a forma como dois amigos íntimos falam entre si é dife-rente da forma usada por duas pessoas que mal se conhecem.

Estes três aspectos das linguagens estão também presentes quando discu-timos linguagens de programação. Contrariamente ao que acontece com aslinguagens naturais que empregamos para comunicarmos uns com os outros,as linguagens de programação caracterizam-se por serem formais, obedecendoa um conjunto de regras muito mais simples e rígido que permite que sejamanalisadas e processadas mecanicamente.

Neste texto iremos descrever a sintaxe e semântica da linguagem AutoLisp. Embora existam formalismos matemáticos que permitem descrever ri-gorosamente aqueles dois aspectos das linguagens, eles exigem uma sofistica-ção matemática que, neste trabalho, é desapropriada. Por este motivo, iremosapenas empregar descrições informais.

Quanto à pragmática, esta será explicada à medida que formos introdu-zindo os elementos da linguagem. A linguagem Auto Lisp promove umapragmática que, nalguns aspectos, difere significativamente do que é habi-tual nos restantes dialectos de Lisp. Uma vez que a pragmática não influenciaa correcção dos nossos programas mas apenas o seu estilo, iremos empregaruma pragmática ligeiramente diferente da usual em Auto Lisp mas que pen-samos ser mais apropriada.

3.1 Sintaxe e Semântica do Lisp

Quando comparada com a grande maioria das outras linguagens de progra-mação, a linguagem Lisp possui uma sintaxe extraordinariamente simples ba-seada no conceito de expressão.2

Uma expressão, em Lisp, pode ser construída empregando elementos pri-mitivos como, por exemplo, os números; ou combinando outras expressõesentre si como, por exemplo, quando somamos dois números. Como iremosver, esta simples definição permite-nos construir expressões de complexidadearbitrária. No entanto, convém relembrarmos que a sintaxe restringe aquiloque podemos escrever: o facto de podermos combinar expressões para pro-duzir outras expressões mais complexas não nos autoriza a escrever qualquercombinação de subexpressões. As combinações obedecem a regras sintáticasque iremos descrever ao longo do texto.

À semelhança do que acontece com a sintaxe, também a semântica da lin-guagem Lisp é, em geral, substancialmente mais simples que a de outras lin-guagens de programação. Como iremos ver, a semântica é determinada pelosoperadores que as nossas expressões irão empregar. Um operador de soma, porexemplo, serve para somar dois números. Uma combinação que junte este

2Originalmente, Lisp dizia-se baseado em S-expression, uma abreviatura para expressõessimbólicas. Esta caracterização ainda é aplicável mas, por agora, não a vamos empregar.

8

operador e, por exemplo, os números 3 e 4 tem, como significado, a soma de3 com 4, i.e., 7. No caso das linguagens de programação, a semântica de umaexpressão é dada pelo computador que a vai avaliar.

3.2 O Avaliador

Em Lisp, qualquer expressão tem um valor. Este conceito é de tal modo impor-tante que todas as implementações da linguagem Lisp apresentam um avalia-dor, i.e., um programa destinado a interagir com o utilizador de modo a avaliarexpressões por este fornecidas.

No caso do AutoCad, o avaliador está contido num ambiente interactivode desenvolvimento, denominado Visual Lisp e pode ser acedido através demenus (menu “Tools”, submenu “AutoLISP”, item “Visual LISP Editor”) ouatravés do comando vlisp. Uma vez acedido o Visual Lisp, o utilizador en-contrará uma janela com o título “Visual LISP Console” e que se destina preci-samente à interacção com o avaliador de Auto Lisp.

Assim, quando o utilizador começa a trabalhar com o Lisp, é-lhe apresen-tado um sinal (denominado “prompt”) e o Lisp fica à espera que o utilizadorlhe forneça uma expressão.

_$

O texto “_$” é a “prompt” do Lisp, à frente da qual vão aparecer as ex-pressões que o utilizador escrever. O Lisp interacciona com o utilizador exe-cutando um ciclo em que lê uma expressão, determina o seu valor e escreve oresultado. Este ciclo de acções designa-se, tradicionalmente, de read-eval-print-loop (e abrevia-se para REPL).

Quando o Lisp lê uma expressão, constroi internamente um objecto quea representa. Esse é o papel da fase de leitura. Na fase de avaliação, o ob-jecto construído é analisado de modo a produzir um valor. Esta análise é feitaempregando as regras da linguagem que determinam, para cada caso, qual ovalor do objecto construído. Finalmente, o valor produzido é apresentado aoutilizador na fase de escrita através de uma representação textual desse valor.

Dada a existência do read-eval-print-loop, em Lisp não é necessário instruir ocomputador a escrever explicitamente os resultados de um cálculo como acon-tece noutras linguages como, por exemplo, em Java. Isto permite que o testee correcção de erros seja bastante facilitada. A vantagem de Lisp ser uma lin-guagem interactiva está na rapidez com que se desenvolvem protótipos deprogramas, escrevendo, testando e corrigindo pequenos fragmentos de cadavez.

Exercício 3.2.1 O que é o REPL?

4 Elementos da Linguagem

Em qualquer linguagem de programação precisamos de lidar com duas es-pécies de objectos: dados e procedimentos. Os dados são as entidades que

9

pretendemos manipular. Os procedimentos são descrições das regras para ma-nipular esses dados.

Se considerarmos a linguagem da matemática, podemos identificar os nú-meros como dados e as operações algébricas como procedimentos. As ope-rações algébricas permitem-nos combinar os números entre si. Por exemplo,2× 2 é uma combinação. Uma outra combinação envolvendo mais dados será2× 2× 2 e, usando ainda mais dados, 2× 2× 2× 2. No entanto, a menos quepretendamos ficar eternamente a resolver problemas de aritmética elementar,convém considerar operações mais elaboradas que representam padrões decálculos. Na sequência de combinações que apresentámos é evidente que opadrão que está a emergir é o da operação de potenciação, i.e, multiplicaçãosucessiva, tendo esta operação sido definida na matemática há já muito tempo.A potenciação é, portanto, uma abstracção de uma sucessão de multiplicações.

Tal como a linguagem da matemática, uma linguagem de programaçãodeve possuir dados e procedimentos primitivos, deve ser capaz de combinarquer os dados quer os procedimentos para produzir dados e procedimentosmais complexos e deve ser capaz de abstrair padrões de cálculo de modo apermitir tratá-los como operações simples, definindo novas operações que re-presentem esses padrões de cálculo.

Mais à frente iremos ver como é possível definir essas abstracções em Lispmas, por agora, vamos debruçar-nos sobre os elementos mais básicos, os cha-mados elementos primitivos da linguagem.

4.1 Elementos primitivos

Elementos primitivos são as entidades mais simples com que a linguagem lida.Os elementos primitivos podem ser divididos em dados primitivos e procedi-mentos primitivos. Um número, por exemplo, é um dado primitivo.

4.1.1 Números

Como dissemos anteriormente, o Lisp executa um ciclo read-eval-print. Istoimplica que tudo o que escrevemos no Lisp tem de ser avaliado, i.e., tem deter um valor, valor esse que o Lisp escreve no écran.

Assim, se dermos um número ao avaliador, ele devolve-nos o valor dessenúmero. Quanto vale um número? O melhor que podemos dizer é que elevale por ele próprio. Por exemplo, o número 1 vale 1.

_$ 11_$ 1234512345_$ 4.54.5

Como se vê no exemplo, em Lisp, os números podem ser inteiros ou reais.Os reais são números com um ponto decimal e, no caso do Auto Lisp, tem dehaver pelo menos um dígito antes do ponto decimal:

10

_$ 0.10.1_$ .1; error: misplaced dot on input

Note-se, no exemplo anterior, que o Auto Lisp produziu um erro. Um erroé uma situação anómala que impede o Auto Lisp de prosseguir o que estava afazer. Neste caso, é apresentada uma mensagem de erro e o Auto Lisp volta aoprincípio do ciclo read-eval-print.

Exercício 4.1.1 Descubra qual é o maior real e o maior inteiro que o seu Lisp aceita.

Exercício 4.1.2 Em matemática, é usal empregar simultaneamente as diferentes notações pre-fixa, infixa e posfixa. Escreva exemplos de expressões matemáticas que utilizem estas diferentesnotações.

Exercício 4.1.3 Converta as seguintes expressões da notação infixa da aritmética para a notaçãoprefixa do Lisp:

1. 1 + 2− 3

2. 1− 2× 3

3. 1× 2− 3

4. 1× 2× 3

5. (1− 2)× 3

6. (1− 2) + 3

7. 1− (2 + 3)

8. 2× 2 + 3× 3× 3

Exercício 4.1.4 Converta as seguintes expressões da notação prefixa do Lisp para a notaçãoinfixa da aritmética:

1. (* (/ 1 2) 3)

2. (* 1 (- 2 3))

3. (/ (+ 1 2) 3)

4. (/ (/ 1 2) 3)

5. (/ 1 (/ 2 3))

6. (- (- 1 2) 3)

7. (- 1 2 3)

Exercício 4.1.5 Indente a seguinte expressão de modo a ter um único operando por linha.

(* (+ (/ 3 2) (- (* (/ 5 2) 3) 1) (- 3 2)) 2)

4.2 Avaliação de Combinações

Como já vimos, o Lisp considera que o primeiro elemento de uma combinaçãoé um operador e os restantes são os operandos.

O avaliador determina o valor de uma combinação como o resultado deaplicar o procedimento especificado pelo operador ao valor dos operandos. Ovalor de cada operando é designado de argumento do procedimento. Assim, ovalor da combinação (+ 1 (* 2 3)) é o resultado de somar o valor de 1 como valor de (* 2 3). Como já se viu, 1 vale 1 e (* 2 3) é uma combinaçãocujo valor é o resultado de multiplicar o valor de 2 pelo valor de 3, o que dá 6.Finalmente, somando 1 a 6 obtemos 7.

11

_$ (* 2 3)6_$ (+ 1 (* 2 3))7

Uma diferença importante entre o Auto Lisp e a aritmética ocorre na ope-ração de divisão. Matematicamente falando, 7/2 é uma fracção, i.e., não éum número inteiro. Infelizmente, o Auto Lisp não sabe lidar com fracções e,consequentemente, emprega uma definição ligeiramente diferente para o ope-rador de divisão: em Auto Lisp, o símbolo / representa a divisão inteira, i.e.,o número que multiplicado pelo divisor e somado ao resto iguala o dividendo.Assim, (/ 7 2) avalia para 3. No entanto, quando está a lidar com númerosreais, o Auto Lisp já faz a divisão segundo as regras da matemática mas, pos-sivelmente, envolvendo perdas de precisão. Assim, (/ 7.0 2) produz 3.5.

Exercício 4.2.1 Calcule o valor das seguintes expressões Lisp:

1. (* (/ 1 2) 3)

2. (* 1 (- 2 3))

3. (/ (+ 1 2) 3)

4. (- (- 1 2) 3)

5. (- 1 2 3)

6. (- 1)

4.3 Operadores de Strings

Para além dos operadores que já vimos para números, existem operadorespara strings. Por exemplo, para concatenar várias strings, existe o operadorstrcat. A concatenação de várias strings produz uma só string com todos oscaracteres dessas várias strings e pela mesma ordem:

_$ (strcat "1" "2")"12"_$ (strcat "um" "dois" "tres" "quatro")"umdoistresquatro"_$ (strcat "eu" " " "sou" " " "uma" " " "string")"eu sou uma string"_$ (strcat "E eu" " sou " "outra")"E eu sou outra"

Para saber o número de caracteres de uma string existe o operador strlen:

_$ (strlen (strcat "eu" " " "sou" " " "uma" " " "string"))17_$ (strlen "")0

Note-se que as aspas são os delimitadores de strings e não contam comocaracteres.

A função substr providencia uma terceira operação útil com strings: elapermite obter parte de uma string. A parte a obter é especificada através da

12

posição do primeiro carácter e do número de caracteres a considerar. Numastring, a posição de um carácter é também denominada de índice do caracter.O Auto Lisp considera que o primeiro carácter de uma string tem o índice 1.A função substr recebe, como argumentos, uma string, um índice e, opcio-nalmente, um número de caracteres e devolve a parte da string que começa noíndice dado e que tem o número de caracteres dado no parâmetro opcional outodos os restantes caracteres no caso de esse número não ter sido dado. Assim,temos:

_$ (substr "abcd" 1)"abcd"_$ (substr "abcd" 2)"bcd"_$ (substr "abcd" 2 2)"bc"

Exercício 4.3.1 Qual é o resultado das seguintes avaliações?

1. (strcat "a" "vista" "da" " baixa" "da" " banheira")

2. (substr (strcat "Bom dia") 1 3)

3. (substr (strcat "Bom dia") 5)

4.4 Definição de Funções

Para além das operações básicas aritméticas, a matemática disponibiliza-nosum vastíssimo conjunto de outras operações que se definem à custa daquelas.Por exemplo, o quadrado de um número é uma operação (também designadapor função) que, dado um número, produz o resultado de multiplicar esse nú-mero por ele próprio. Matematicamente falando, define-se o quadrado de umnúmero pela função x2 = x · x.

Tal como em matemática, pode-se definir numa linguagem de programa-ção a função que obtém o quadrado de um número. Em Lisp, para obtermos oquadrado de um número qualquer, por exemplo, 5, escrevemos a combinação(* 5 5). No caso geral, dado um número qualquer x, sabemos que obtemoso seu quadrado escrevendo (* x x). Falta apenas associar um nome que in-dique que, dado um número x, obtemos o seu quadrado avaliando (* x x).Lisp permite-nos fazer isso através da operação defun (abreviatura de definefunction):

_$ (defun quadrado (x) (* x x))QUADRADO

Note-se que o Auto Lisp, ao avaliar a definição da função quadrado de-volve como valor o nome da função definida. Note-se ainda que o Auto Lispescreve esse nome em maiúsculas. Na realidade, o que se passa é que, por mo-tivos históricos, todos os nomes que escrevemos no Auto Lisp são traduzidospara maiúsculas assim que são lidos. Por este motivo, quadrado, QUADRADO,Quadrado, qUaDrAdo, ou qualquer outra combinação de maiúsculas e minús-culas designa o mesmo nome em Auto Lisp: QUADRADO. Apesar da conversão

13

para maiúsculas que é feita na leitura, é pragmática usual escrevermos os nos-sos programas em letras minúsculas.

Como se pode ver pela definição da função quadrado, para se definiremnovos procedimentos em Lisp, é necessário criar uma combinação de quatroelementos. O primeiro elemento desta combinação é a palavra defun, que in-forma o avaliador que estamos a definir uma função. O segundo elemento é onome da função que queremos definir, o terceiro elemento é uma combinaçãocom os parâmetros da função e o quarto elemento é a expressão que deter-mina o valor da função para aqueles parâmetros. De modo genérico podemosindicar que a definição de funções é feita usando a seguinte forma:

(defun nome (parâmetro1 ... parâmetron)corpo)

Quando se dá uma expressão desta forma ao avaliador, ele acrescenta afunção ao conjunto de funções da linguagem, associando-a ao nome que lhedemos e devolve como valor da definição o nome da função definida. Se jáexistir uma função com o mesmo nome, a definição anterior é simplesmenteesquecida. Os parâmetros de um procedimento são designados parâmetrosformais e são os nomes usados no corpo de expressões para nos referirmosaos argumentos correspondentes. Quando escrevemos no avaliador de Lisp(quadrado 5), 5 é o argumento do procedimento. Durante o cálculo da fun-ção este argumento está associado ao parâmetro formal x. Os argumentos deuma função são também designados por parâmetros actuais.

No caso da função quadrado, a sua definição diz que para se determinar oquadrado de um número x, devemos multiplicar esse número por ele próprio(* x x). Esta definição associa a palavra quadrado a um procedimento, i.e.,a uma descrição do modo de produzir o resultado pretendido. Note-se queeste procedimento possui parâmetros, permitindo o seu uso com diferentesargumentos. Para se utilizar este procedimento, podemos avaliar as seguinteexpressões:

_$ (quadrado 5)25_$ (quadrado 6)36

A regra de avaliação de combinações que descrevemos anteriormente étambém válida para as funções por nós definidas. Assim, a avaliação da ex-pressão (quadrado (+ 1 2)) passa pela avaliação do operando (+ 1 2).Este operando tem como valor 3, valor esse que é usado pela função no lu-gar do parâmetro x. O corpo da função é então avaliado mas substituindotodas as ocorrências de x pelo valor 3, i.e., o valor final será o da combinação(* 3 3).

Para se perceber a avaliação de combinações que o Lisp emprega, é útil de-compor essa avaliação nas suas etapas mais elementares. No seguinte exemplomostramos o processo de avaliação para a expressão

(quadrado (quadrado (+ 1 2)))

14

Em cada passo, uma das sub-expressões foi avaliada.

(quadrado (quadrado (+ 1 2)))↓

(quadrado (quadrado 3))↓

(quadrado (* 3 3))↓

(quadrado 9)↓

(* 9 9)↓81

Como dissemos, a definição de funções permite-nos associar um procedi-mento a um nome. Isto implica que o Lisp tem de possuir uma memória ondepossa guardar a função e a sua associação ao nome dado. Esta memória doLisp designa-se ambiente.

Note-se que este ambiente apenas existe enquanto estamos a trabalhar coma linguagem. Quando terminamos, perde-se todo o ambiente. Para evitar per-dermos as definições de procedimentos que tenhamos feito, convém registá-las num suporte persistente, como seja um ficheiro. Para facilitar a utilização,o Lisp permite que se avaliem as diversas definições a partir de um ficheiro.Por este motivo, o processo usual de trabalho com Lisp consiste em escreveras várias definições em ficheiros embora se continue a usar o avaliador de Lisppara experimentar e testar o correcto funcionamento das nossas definições.

Exercício 4.4.1 Defina a função dobro que, dado um número, calcula o seu dobro.

4.5 Símbolos

A definição de funções em Lisp passa pela utilização de nomes: nomes para asfunções e nomes para os parâmetros das funções.

Uma vez que o Lisp, antes de avaliar qualquer expressão, tem de a ler econverter internamente para um objecto, também cada nome que escrevemosé convertido na leitura para um determinado objecto. Esse objecto é designadopor símbolo. Um símbolo é, pois, a representação interna de um nome e é outrodos elementos primitivos da linguagem.

Em Lisp, quase não existem limitações para a escrita de nomes. Um nomecomo quadrado é tão válido como + ou como 1+2*3 pois o que separa umnome dos outros elementos de uma combinação são apenas parênteses e es-paço em branco.3 Por exemplo, é perfeitamente correcto definir e usar a se-guinte função:

3Dentro do conceito de “espaço em branco” consideram-se os caracteres de espaço, de tabu-lação e de mudança de linha.

15

_$ (defun x+y*z (x y z)(+ x (* y z)))

X+Y*Z_$ (x+y*z 1 2 3)7

No caso do Auto Lisp, os únicos caracteres que não se podem utilizar nosnomes dos símbolos são o abrir e fechar parênteses ( e ), a plica (tambémchamada apóstrofo) ’, as aspas ", o ponto . e o ponto e vírgula ;. Todos osrestantes caracteres se podem usar para nomes de funções mas, na prática, acriação de nome segue algumas regras que convém ter presente:

• Apenas se devem usar as letras do alfabeto, os dígitos, os símbolos arit-méticos e alguns caracteres de pontuação, como o ponto, o ponto de ex-clamação e o ponto de interrogação. Por motivos de portabilidade, con-vém evitar o uso de caracteres acentuados.

• Se o nome da função é composto por várias palavras, deve-se separaras palavras com traços (-). Por exemplo, uma função que calcula a áreade um círculo poderá ter como nome o símbolo area-circulo. Já osnomes areacirculo, area_circulo e area+circulo serão menosapropriados.

• Se o nome corresponde a uma interrogação, deve-se terminar o nomecom um ponto de interrogação (?) ou, na tradição Lisp, com a letra p.4

Por exemplo, uma função que indica se um número é par poderá ter onome par?.

• Se a função faz uma conversão entre dois tipos de valores, o nome poderáser obtido a partir dos nomes dos tipos com uma seta entre eles a indicara direcção da conversão. Por exemplo, uma função que converte eurospara libras poderá ter como nome euros->libras ou, ainda melhor,libras<-euros.

Exercício 4.5.1 Indique um nome apropriado para cada uma das seguintes funções:

1. Função que calcula o volume de uma esfera.

2. Função que indica se um número é primo.

3. Função que converte uma medida em centímetros para polegadas.

4.6 Avaliação de Símbolos

Vimos que todos os outros elementos primitivos que apresentámos até agora,nomeadamente, os números e as cadeias de caracteres avaliavam para elespróprios, i.e., o valor de uma expressão composta apenas por um elementoprimitivo é o próprio elemento primitivo. No caso dos símbolos, isso já não éverdade.

4Esta letra é a abreviatura de predicado, um tipo de função que iremos estudar mais à frente.

16

Lisp atribui um significado muito especial aos símbolos. Reparemos que,quando definimos uma função, o nome da função é um símbolo. Os parâ-metros formais da função também são símbolos. Quando escrevemos umacombinação, o avaliador de Lisp usa a definição de função que foi associadaao símbolo que constitui o primeiro elemento da combinação. Isto quer dizerque o valor do primeiro símbolo de uma combinação é a função que lhe estáassociada. Admitindo que tínhamos definido a função quadrado tal como vi-mos na secção 4.4, podemos verificar este comportamento experimentando asseguintes expressões:

_$ (quadrado 3)9_$ quadrado#<USUBR @1d4c66f4 QUADRADO>

Como se pode ver pelo exemplo anterior, o valor do símbolo quadrado éuma entidade que o Lisp descreve usando uma notação especial. A entidadeem questão é, como vimos, uma função. O mesmo comportamento ocorre paraqualquer outra função pré-definida na linguagem:5

_$ +#<SUBR @0a87c5a8 +>_$ *#<SUBR @0a87c588 *>

Como vimos com o + e o *, alguns dos símbolos estão pré-definidos nalinguagem. Por exemplo, o símbolo PI também existe pré-definido com umaaproximação do valor de π.

_$ pi3.141519

No entanto, quando o avaliador está a avaliar o corpo de uma função, o va-lor de um símbolo especificado nos parâmetros da função é o argumento cor-respondente na invocação da função. Assim, na combinação (quadrado 3),depois de o avaliador saber que o valor de quadrado é a função por nós defi-nida atrás e que o valor de 3 é 3, o avaliador passa a avaliar o corpo da funçãoquadrado mas assumindo que, durante essa avaliação, o nome x, sempre quefor necessário, irá ter como valor precisamente o mesmo 3 que foi associadoao parâmetro x.

Exercício 4.6.1 Defina a função radianos<-graus que recebe uma quantidade angular emgraus e calcula o valor correspondente em radianos. Note que 180 graus correspondem a πradianos.

Exercício 4.6.2 Defina a função graus<-radianos que recebe uma quantidade angular emradianos e calcula o valor correspondente em graus.

Exercício 4.6.3 Defina a função que calcula o perímetro de uma circunferência de raio r.

5Um olhar mais atento encontrará uma pequena diferença: no caso do quadrado, a funçãoassociada é do tipo USUBR enquanto que nos casos do + e * as funções associadas são do tipoSUBR. A diferença entre estes dois tipos está relacionada com o facto de as SUBRs estarem com-piladas, permitindo a sua invocação de forma mais eficiente. O termo SUBR é uma abreviaturade subroutine.

17

4.7 Funções de Múltiplos Parâmetros

Já vimos e definimos várias funções em Lisp. Até agora, todas as que defini-mos tinham um único parâmetro mas, obviamente, é possível definir funçõescom mais do que um parâmetro.

Por exemplo, a área A de um triângulo de base b e altura c define-se mate-maticamente por A(b, c) = b·c

2 . Em Lisp, teremos:

(defun A (b c) (/ (* b c) 2))

Como se pode ver, a definição da função em Lisp é idêntica à definiçãocorrespondente em Matemática, com a diferença de os operadores se usaremde forma prefixa e o símbolo de definição matemática = se escrever defun.No entanto, contrariamente ao que é usual ocorrer em Matemática, os nomesque empregamos em Lisp devem ter um significado claro. Assim, ao invés dese escrever A é preferível escrever area-triangulo. Do mesmo modo, aoinvés de se escrever b e c, seria preferível escrever base e altura. Tendoestes aspectos em conta, podemos apresentar uma definição mais legível:

(defun area-triangulo (base altura)(/ (* base altura) 2))

Quando o número de definições aumenta torna-se particularmente impor-tante para quem as lê que se perceba rapidamente o seu significado e, por isso,é de crucial importância que se faça uma boa escolha de nomes.

Exercício 4.7.1 Defina uma função que calcula o volume de um paralelipípedo. Empreguenomes suficientemente claros.

Exercício 4.7.2 Defina a função media que calcula o valor médio entre dois outros valores. Porexemplo (media 2 3)→2.5.

4.8 Encadeamento de Funções

Todas funções por nós definidas são consideradas pelo avaliador de Lisp empé de igualdade com todas as outras definições. Isto permite que elas possamser usadas para definir ainda outras funções. Por exemplo, após termos defi-nido a função quadrado, podemos definir a função que calcula a área de umcírculo de raio r através da fórmula π · r2.

(defun area-circulo (raio)(* pi (quadrado raio)))

Durante a avaliação de uma expressão destinada a computar a àrea de umcírculo, a função quadrado acabará por ser invocada. Isso é visível na se-guinte sequência de passos de avaliação:

(area-circulo 2)(* pi (quadrado 2))(* 3.141519 (quadrado 2))(* 3.141519 (* 2 2))(* 3.141519 4)12.566076

18

Exercício 4.8.1 Defina a função que calcula o volume de um cilindro com um determinado raioe comprimento. Esse volume corresponde ao produto da área da base pelo comprimento docilindro.

4.9 Funções Pré-Definidas

A possibilidade de se definirem novas funções é fundamental para aumentar-mos a flexibilidade da linguagem e a sua capacidade de se adaptar aos proble-mas que pretendemos resolver. As novas funções, contudo, precisam de serdefinidas à custa de outras que, ou foram também por nós definidas ou, nolimite, já estavam pré-definidas na linguagem.

Isto mesmo se verifica no caso da função area-circulo que definimosacima: ela está definida à custa da função quadrado (que foi também pornós definida) e à custa da operação de multiplicação. No caso da funçãoquadrado, ela foi definida com base na operação de multiplicação. A ope-ração de multiplicação que, em última análise, é a base do funcionamento dafunção area-circulo é, na realidade, uma função pré-definida do Lisp.

Como iremos ver, Lisp providencia um conjunto razoavelmente grande defunções pré-definidas. Em muitos casos, elas são suficientes para o que preten-demos mas, em geral, não nos devemos coibir de definir novas funções sempreque acharmos necessário.

A Tabela 1 apresenta uma selecção de funções matemáticas pré-definidasdo Auto Lisp. Note-se que, devido às limitações sintáticas do Auto Lisp (e quesão comuns a todas as outras linguagens de programação), há vários casos emque uma função em Auto Lisp emprega uma notação diferente daquela queé usual em matemática. Por exemplo, a função raiz quadrada

√x escreve-se

como (sqrt x). O nome sqrt é uma contracção do Inglês square root e con-tracções semelhantes são empregues para várias outras funções. Por exemplo,a função valor absoluto |x| escreve-se (abs x) (de absolute value), a funçãoparte inteira bxc escreve-se (fix x) (de fixed point) e a função potência xy quese escreve (expt x y) (de exponential).

A Tabela 2 apresenta uma selecção de funções sobre strings pré-definidasdo Auto Lisp. À medida que formos apresentando novos tópicos do Auto Lispiremos explicando outras funções pré-definidas que sejam relevantes para oassunto.

Exercício 4.9.1 Embora o seno (sin) e o cosseno (cos) sejam funções pré-definidas em AutoLisp, a tangente (tan) não é. Defina-a a partir da fórmula tanx = sin x

cos x.

Exercício 4.9.2 Do conjunto das funções trigonométricas inversas, o Auto Lisp apenas provi-dencia o arco tangente (atan). Defina também as funções arco-seno (asin) e arco-cosseno(acos), cujas definições matemáticas são:

asinx = atanx√

1− x2

acosx = atan

√1− x2

x

Exercício 4.9.3 A função fix permite truncar um número real, produzindo o número inteiroque se obtém pela eliminação da parte fraccionária desse real. Defina a função round que

19

permite arredondar um número real, i.e., produzir o inteiro que é mais próximo desse númeroreal.

Exercício 4.9.4 Traduza as seguintes expressões Auto Lisp para a notação matemática:

1. (log (sin (+ (expt 2 4) (/ (fix (atan pi)) (sqrt 5)))))

2. (expt (cos (cos (cos 0.5))) 5)

3. (sin (/ (cos (/ (sin (/ pi 3)) 3)) 3))

Exercício 4.9.5 Defina o predicado impar? que, dado um número, testa de ele é ímpar, i.e., seo resto da divisão desse número por dois é um. Para calcular o resto da divisão de um númeropor outro, utilize a função pré-definida rem.

Exercício 4.9.6 Em várias actividades é usual empregarem-se expressões padronizadas. Porexemplo, se o aluno Passos Dias Aguiar pretende obter uma certidão de matrícula da universi-dade que frequenta, terá de escrever uma frase da forma:

Passos Dias Aguiar vem respeitosamente pedir a V. Exa. que se digne passaruma certidão de matrícula.

Por outro lado, se a aluna Maria Gustava dos Anjos pretende um certificado de habilitações,deverá escrever:

Maria Gustava dos Anjos vem respeitosamente pedir a V. Exa. que se dignepassar um certificado de habilitações.

Para simplificar a vida destas pessoas, pretende-se que implemente uma função deno-minada requerimento que, convenientemente parameterizada, gera uma string com a fraseapropriada para qualquer um dos fins anteriormente exemplificados e também para outros domesmo género que possam vir a ser necessários. Teste a sua função para garantir que conseguereproduzir os dois exemplos anteriores passando o mínimo de informação nos argumentos dafunção.

Exercício 4.9.7 Também na actividade da Arquitectura, é usual os projectos necessitarem deinformações textuais escritas numa forma padronizada. Por exemplo, considere as seguintestrês frases contidas nos Cadernos de Encargos de três diferentes projectos:

Toda a caixilharia será metálica, de alumínio anodizado, cor natural, cons-truída com os perfis utilizados pela Serralharia do Corvo (ou semelhante), sujeitosa aprovação da Fiscalização.

Toda a caixilharia será metálica, de alumínio lacado, cor branca, construídacom os perfis utilizados pela Technal (ou semelhante), sujeitos a aprovação daFiscalização.

Toda a caixilharia será metálica, de aço zincado, preparado com primáriosde aderência, primário epoxídico de protecção e acabamento com esmalte SMP,cor 1050-B90G (NCS), construída com os perfis standard existentes no mercado,sujeitos a aprovação da Fiscalização.

Defina uma função que, convenientemente parameterizada, gera a string adequada paraos três projectos anteriores, tendo o cuidado de a generalizar para qualquer outra situação domesmo género.

20

−∞ +∞−2147483648 −2−1 0 +1 +2 +2147483647

−2−1 0 +1

+2

+2147483647−2147483648

Figura 1: A recta infinita dos inteiros empregue em aritmética e o círculo dosinteiros modulares empregues em Auto Lisp.

4.10 Aritmética de Inteiros em Auto Lisp

Vimos que o Auto Lisp disponibiliza, para além dos operadores aritméticosusuais, um conjunto de funções matemáticas. No entanto, há que ter em contaque existem diferenças substanciais entre o significado matemático destas ope-rações e a sua implementação no Auto Lisp.

Um primeiro problema importante tem a ver com a gama de inteiros. EmAuto Lisp, os inteiros são representados com apenas 32 bits de informação.8

Isto implica que apenas se conseguem representar inteiros desde−2147483647até 2147483647. Para tornar a situação ainda mais problemática, embora existaesta limitação, quando ela não é respeitada o Auto Lisp não emite qualqueraviso. Este comportamento é usual na maioria das linguagens de programaçãoe está relacionado com questões de performance.9 Infelizmente, a performancetem como preço o poder originar resultados aparentemente bizarros:

_$ (+ 2147483647 1)-2147483648

Este resultado surge porque no Auto Lisp, na realidade, as operações arit-méticas com inteiros são modulares. Isto implica que a sequência dos inteirosnão corresponde a uma linha infinita nas duas direcção, antes correspondendoa um círculo em que a seguir ao maior número inteiro positivo aparece o me-nor número inteiro negativo. Este comportamento encontra-se explicado naFigura 1.

Um segundo problema que já referimos anteriormente está no facto de oAuto Lisp não saber representar fracções. Isto implica que uma divisão dedois inteiros não corresponde à divisão matemática mas sim a uma operação

8Em AutoCad, a gama de inteiros é ainda mais pequena pois a sua representação apenas usa16 bits, permitindo apenas representar os inteiros de −32768 a 32767. Isto não afecta o AutoLisp excepto quando este tem de passar inteiros para o AutoCad.

9Curiosamente, a maioria dos dialectos de Lisp (mas não o Auto Lisp) não apresenta estecomportamento, preferindo representar números inteiros com dimensão tão grande quanto fornecessário.

21

substancialmente diferente denominada divisão inteira: uma divisão em quea parte fraccional do resultado é eliminada, i.e., apenas se considera o quoci-ente da divisão, eliminando-se o resto. Assim, (/ 7 2) é 3 e não 7/2 ou 3.5.Obviamente, isto inviabiliza algumas equivalências matemáticas óbvias. Porexemplo, embora 1

3 × 3 = 1, em Auto Lisp temos:

_$ (* (/ 1 3) 3)0

Finalmente, um terceiro problema que ocorre com os inteiros é que a leiturade um número inteiro que excede a gama dos inteiros implica a sua conversãoautomatica para o tipo real.10 Neste caso, o avaliador do Auto Lisp nuncachega a “ver” o número inteiro pois ele foi logo convertido para real na leitura.Esse comportamento é visível no seguinte exemplo:

_$ 12345678901234567890_$ 123456789011.23457e+010

Também este aspecto do Auto Lisp pode ser fonte de comportamentos bi-zarros, tal como é demonstrado pelo seguinte exemplo:

_$ (+ 2147483646 1)2147483647_$ (+ 2147483647 1)-2147483648_$ (+ 2147483648 1)2.14748e+009

É importante salientar que a conversão de inteiro para real que é visível naúltima interacção é feita logo na leitura.

Exercício 4.10.1 Calcule o valor das seguintes expressões Auto Lisp:

1. (- 1)

2. (- 1 1)

3. (- 1 1 1)

4. (/ 1 1)

5. (/ 1 2)

6. (/ (* 3 2) 2)

7. (* (/ 3 2) 2)

8. (/ (/ (/ 8 2) 2) 2)

9. (/ 8 (/ 2 (/ 2 2)))

10. (+ (/ 1 2) (/ 1 2))

Exercício 4.10.2 Explique o seguinte comportamento do Auto Lisp:11

10Note-se que estamos apenas a falar da leitura de números. Como já vimos, as operaçõesaritméticas usuais não apresentam este comportamento.

11Este exemplo mostra que, em Auto Lisp, há um inteiro que não pode ser lido mas quepode ser produzido como resultado de operações aritméticas. É importante salientar que estecomportamento verdadeiramente bizarro é exclusivo do Auto Lisp e não se verifica em maisnenhum dialecto de Lisp.

22

_$ (- -2147483647 1)-2147483648_$ (- -2147483648 1)-2.14748e+009_$ (- -2147483647 2)2147483647

Exercício 4.10.3 A área A de um pentágono regular inscrito num círculo de raio r é dada pelafórmula

A =5

8r2

q10 + 2

√5

Defina uma função Auto Lisp que calcule essa área.

Exercício 4.10.4 Defina uma função que calcula o volume de um elipsóide de semi-eixos a, b ec. Esse volume pode ser obtido pela fórmula V = 4

3πabc.

4.11 Aritmética de Reais em Auto Lisp

Em relação aos reais, o seu comportamento é mais próximo do que se consi-dera matematicamente correcto mas, ainda assim, há vários problemas comque é preciso lidar.

A gama dos reais vai desde −4.94066 · 10−324 até 1.79769 · 10+308. Se oAuto Lisp tentar ler um número real que excede esta gama ele é imediatamenteconvertido para um número especial que representa o infinito:

_$ 2e4001.#INF_$ -2e400-1.#INF

Note-se que 1.#INF ou -1.#INF é a forma do Auto Lisp indicar um valorque excede a capacidade de representação de reais do computador. Não éinfinito, como se poderia pensar, mas apenas um valor excessivamente grandepara as capacidades do Auto Lisp. Do mesmo modo, quando alguma operaçãoaritmética produz um número que excede a gama dos reais, é simplesmentegerada a representação do infinito.

_$ 1e3001.0e+300_$ 1e1001.0e+100_$ (* 1e300 1e100)1.#INF

Nestes casos, em que o resultado de uma operação é um número que ex-cede as capacidades da máquina, dizemos que ocorreu um overflow.

As operações com números reais têm a vantagem de a maioria dos compu-tadores actuais conseguirem detectar o overflow de reais e reagirem em confor-midade (gerando um erro ou simplesmente produzindo uma representação doinfinito). Como vimos anteriormente, se se tivessem usado números inteiros,então o overflow não seria sequer detectado, produzindo comportamentos apa-rentemente bizarros, como, por exemplo, o produto de dois números positivosser um número negativo.

23

Há ainda dois outros problemas importantes relacionados com reais: errosde arredondamento e redução de precisão na escrita. A título de exemplo,consideremos a óbvia igualdade matemática (4

3 − 1) · 3− 1 = 0 e comparemosos resultados que se obtêm usando inteiros ou reais:

_$ (- (* (- (/ 4 3) 1) 3) 1)-1_$ (- (* (- (/ 4.0 3.0) 1.0) 3.0) 1.0)-2.22045e-016

Como se pode ver, nem usando inteiros, nem usando reais, se consegueobter o resultado correcto. No caso dos inteiros, o problema é causado peladivisão inteira de 4 por 3 que produz 1. No caso dos reais, o problema é cau-sado por erros de arrendondamento: 4/3 não é representável com um númerofinito de dígitos. Este erro de arrendondamento é então propagado nas res-tantes operações produzindo um valor que, embora não seja zero, está muitopróximo.

Obviamente, como o resultado da avaliação com reais é suficientementepequeno, podemos convertê-lo para o tipo inteiro (aplicando-lhe uma trunca-tura com a função fix) e obtemos o resultado correcto:

_$ (fix (- (* (- (/ 4.0 3.0) 1.0) 3.0) 1.0))0

No entanto, esta “solução” também tem problemas. Consideremos a divi-são 0.6/0.2 = 3:

_$ (/ 0.6 0.2)3.0_$ (fix (/ 0.6 0.2))2

O problema ocorre porque os computadores usam o sistema binário derepresentação e, neste sistema, não é possível representar os números 0.6 e 0.2com um número finito de dígitos binários e, consequentemente, ocorrem errosde arredondamento. Por este motivo, o resultado da divisão, em vez de ser 3,é 2.999999999999999555910790149937, embora o Auto Lisp o apresente como3.0. No entanto, ao eliminarmos a parte fraccionária com a função fix, o quefica é apenas o inteiro 2.

Igualmente perturbante é o facto de, embora 0.4 · 7 = 0.7 · 4 = 2.8, emAuto Lisp, (* 0.4 7) 6= (* 0.7 4): ambas as expressões têm um valor que éescrito pelo Auto Lisp como 2.8 mas há diferentes erros de arrendondamentocometidos nas duas expressões que tornam os resultados diferentes.

Note-se que, mesmo quando não há erros de arrendondamento nas ope-rações, a reduzida precisão com que o Auto Lisp escreve os resultados podeinduzir-nos em erro. Por exemplo, embora internamente o Auto Lisp saibaque (/ 1.000001 10) produz 0.1000001, ele apenas escreve 0.1 como re-sultado.

24

Exercício 4.11.1 Pretende-se criar um lanço de escada com n espelhos capaz de vencer umadeterminada altura a em metros. Admitindo que cada degrau tem uma altura do espelho h euma largura do cobertor d que verificam a proporção

2h+ d = 0.64

defina uma função que, a partir da altura a vencer e do número de espelhos, calcula o compri-mento do lanço de escada.

4.12 Símbolos e Avaliação

Vimos que os símbolos avaliam para aquilo a que estiverem associados no mo-mento da avaliação. Por este motivo, os símbolos são usados para dar nomesàs coisas. O que é mais interessante é que os símbolos são, eles próprios, coisas!

Para percebermos esta característica dos símbolo vamos apresentar umafunção pré-definida do Auto Lisp que nos permite saber o tipo de uma enti-dade qualquer: type. Consideremos a seguinte interacção:

_$ (type 1)INT_$ (type "Bom dia!")STR_$ (type pi)REAL_$ (type quadrado)USUBR_$ (type +)SUBR

Como podemos ver, a função type devolve-nos o tipo do seu argumento:INT para inteiros, STR para strings, REAL para reais, etc.

Mas o que são estes resultados—INT, STR, REAL, etc— que foram devolvi-dos pela função type? Que tipo de objectos são? Para responder à questão, omelhor é usar a mesmíssima função type:

_$ (type pi)REAL_$ (type (type pi))SYM

SYM é a abreviatura de symbol, indicando que o objecto que é devolvidopela função type é um símbolo. Note-se que, contrariamente ao que acon-tece com o símbolo pi que está associado ao número 3.141519 . . . , os símbolosREAL, INT, STR, etc., não estão associados a nada, eles apenas são usadoscomo representação do nome de um determinado tipo de dados.

Se os valores devolvidos pela função type são objectos do tipo símbolo,então deverá ser possível designá-los, tal como designamos os números ou asstrings. Mas qual será o modo de o fazermos? Uma hipótese (errada) seriaescrevê-lo tal como escrevemos números ou strings. Acontece que isto é pos-sível no caso dos números e strings pois eles avaliam para eles próprios. Já nocaso dos símbolos, sabemos que não avaliam para eles próprios, antes avali-ando para as entidades a que estão associados naquele momento. Assim, se

25

quisermos designar o símbolo pi, não bastará escrevê-lo numa expressão poiso que irá resultar após a sua avaliação não será um símbolo mas sim o número3.141519 . . . que é o valor desse símbolo.

Para ultrapassar este problema precisamos de, por momentos, alterar a se-mântica habitual que o Lisp atribui aos símbolos. Essa semântica, recordemo-nos, é a de que o valor de um símbolo é a entidade a que esse símbolo estáassociado nesse momento e esse valor surge sempre que o Lisp avalia o sím-bolo. Para alterarmos essa semântica, precisamos de indicar ao Lisp que nãoqueremos que ele avalie um determinado símbolo, i.e., queremos que ele trateo símbolo como ele é, sem o avaliar. Para isso, o Lisp disponibiliza a formaquote. Esta forma, que recebe um único argumento, tem uma semântica sim-plicíssima: devolve o argumento sem este ter sido avaliado. Reparemos naseguinte interacção:

_$ pi3.14159_$ (quote pi)PI_$ (+ 1 2 3)6_$ (quote (+ 1 2 3))(+ 1 2 3)

Como se vê, qualquer que seja o argumento α da expressão (quote α), ovalor da expressão é o próprio α sem ter sido avaliado.

A razão de ser do quote está associada à distinção que existe entre asfrases “Diz-me o teu nome” e “Diz-me ‘o teu nome’ ”. No primeiro caso afrase tem de ser completamente interpretada para que o ouvinte possa dizerqual é o seu próprio nome. No segundo caso, as plicas estão lá para indicar aoouvinte que ele não deve interpretar o que está entre plicas e deve limitar-se adizer a frase “o teu nome”. As plicas servem, pois, para distinguir o que deveser tomado como é e o que deve ser interpretado.

Para simplificar a escrita de formas que empregam o quote, o Lisp dispo-nibiliza uma abreviatura que tem exactamente o mesmo significado: a plica(’). Quando o Lisp está a fazer a leitura de uma expressão e encontra algo daforma ’α, aquilo que é lido é, na realidade, (quote α). Assim, temos:

_$ ’piPI_$ ’(+ 1 2 3)(+ 1 2 3)

Exercício 4.12.1 Qual é o significado da expressão ’’pi?

5 Combinação de Dados

Vimos, nas secções anteriores, alguns dos tipos de dados pré-definidos emAuto Lisp. Em muitos casos, esses tipos de dados são os necessários e sufi-cientes para nos permitir criar os nossos programas mas, noutros casos, será

26

φ

x

Figura 2: Coordenadas rectangulares e polares.

necessário introduzirmos novos tipos de dados. Nesta secção iremos estudaro modo de o fazermos e iremos exemplificá-lo com um tipo de dados que nosserá particularmente útil: coordenadas.

5.1 Coordenadas

A Arquitectura pressupõe a localização de elementos no espaço. Essa localiza-ção expressa-se em termos do que se designa por coordenadas: as coordenadasde um ponto identificam univocamente esse ponto no espaço.

Se o espaço for bi-dimensional (abreviadamente, 2D), cada ponto é identifi-cado por duas coordenadas. Se o espaço for tri-dimensional (3D), cada ponto éidentificado por três coordenadas. Por agora, vamos considerar apenas o casode coordenadas bi-dimensionais.

Existem vários sistemas de coordenadas bi-dimensionais possíveis mas osmais usais são o sistema de coordenadas rectangulares e o sistemas de coor-denadas polares, representados na Figura 2. No sistema de coordenadas rec-tangulares, esse par de números designa a abcissa x e a ordenada y. No sistemade coordenadas polares, esse par de números designa o raio vector ρ e o ângulopolar φ. Em qualquer caso, as coordenadas bi-dimensionais são descritas porum par de números.

Como vimos nas secções anteriores, o Auto Lisp sabe lidar com o conceitode números. Vamos agora ver que ele também sabe lidar com o conceito depar.

5.2 Pares

Em Lisp, podemos criar pares através da função pré-definida cons. Esta fun-ção, cujo nome é a abreviatura da palavra construct, aceita quaisquer duas en-tidades como argumentos e produz um par com essas duas entidades. Tradi-cionalmente, é usual dizer que o par é um cons.12

Eis um exemplo da criação de um par de números:

_$ (cons 1 2)(1 . 2)

12O cons é para o Lisp o mesmo que as tabelas (arrays) e estruturas (records, structs) são paraas outras linguagens como Pascal ou C. Na prática, o cons é um mecanismo de agregação deentidades.

27

Como podemos ver pela interacção anterior, quando o Lisp pretende es-crever o resultado da criação de um par, ele começa por escrever um abrir pa-rênteses, depois escreve o primeiro elemento do par, depois escreve um pontode separaração, depois escreve o segundo elemento do par e, finalmente, es-creve um fechar parênteses. Esta notação denomina-se “par com ponto” ou,no original, dotted pair.

Quando o Lisp cria um par de elementos, cria uma associação interna en-tre esses elementos que podemos representar graficamente como uma caixadividida em duas metades, cada uma apontando para um dos elementos. Porexemplo, o par anterior pode ser representado graficamente da seguinte forma:

1 2

A representação gráfica anterior denomina-se notação de “caixa e pon-teiro” precisamente porque mostra os pares como caixas com ponteiros paraos elementos dos pares.

Uma vez que a construção de um par é feita usando uma função, a suainvocação segue as mesmas regras de avaliação de todas as outras invocaçõesde funções, ou seja, as expressões que constituem os argumentos são avaliadase são os resultados dessas avaliações que são usados como elementos do par.Assim, temos:

_$ (cons (+ 1 2) (* 3 4))(3 . 12)_$ (cons 1 "dois")(1 . "dois")_$ (cons (area-circulo 10) (area-triangulo 20 30))(314.159 . 300)

A propósito deste último exemplo, note-se a diferença entre o ponto rela-tivo à parte fraccionária do primeiro número e o ponto relativo à separaçãodos elementos do par. Na primeira ocorrência, o ponto faz parte da sintaxe donúmero fraccionário e não pode ter espaços. Na segunda ocorrência, o pontofaz parte da sintaxe dos pares e tem de ter espaços.

A partir do momento em que temos um par de entidades, podemos estarinteressados em saber quais são os elementos do par. Em Lisp, dado um parde entidades (um “cons”) podemos obter o primeiro elemento do par usandoa função car e podemos obter o segundo elemento do par usando a funçãocdr.

_$ (car (cons 1 2))1_$ (cdr (cons 1 2))2

Note-se que aplicar o car ou o cdr a um cons não afecta esse cons, ape-nas diz qual é o primeiro ou segundo elemento do cons. Em termos da re-presentação gráfica de “caixa e ponteiro,” a aplicação das funções car e cdr

28

corresponde, simplesmente, a seguir os apontadores da esquerda e direita, res-pectivamente.

Os nomes car e cdr têm raizes históricas e, embora possa não parecer, sãorelativamente fáceis de decorar. Uma mnemónica que pode ajudar é pensarque o “cAr” obtém o elemento que vem “Antes” e o “cDr” obtém o elementoque vem “Depois.”

Uma vez que a função cons forma pares com os seus argumentos, é igual-mente possível formar pares de pares. Os seguintes exercícios exemplificamesta situação.

Exercício 5.2.1 Qual o significado da expressão (cons (cons 1 2) 3)?

Exercício 5.2.2 Qual o significado da expressão (car (cons (cons 1 2) 3))?

Exercício 5.2.3 Qual o significado da expressão (cdr (cons (cons 1 2) 3))?

5.3 Operações com Coordenadas

A partir do momento em que sabemos construir pares, podemos criar coor-denadas e podemos definir operações sobre essas coordenadas. Para criarmoscoordenadas podemos simplesmente juntar num par os dois números que re-presentam a abcissa e a ordenada. Por exemplo, o ponto do espaço cartesiano(1, 2) pode ser construído através de:

_$ (cons 1 2)(1 . 2)

Uma vez que estamos a fazer um par que contém, primeiro, a abcissa e,depois, a ordenada, podemos obter estes valores usando, respectivamente, asfunções car e cdr.

Para melhor percebermos a utilização destas funções, imaginemos que pre-tendemos definir uma operação que mede a distância d entre os pontos P0 =(x0, y0) e P1 = (x1, y1) que podemos ver na Figura 3. A distância d corres-ponde, logicamente, ao comprimento da recta que une P0 a P1.

A aplicação do teorema de Pitágoras permite-nos determinar a distância dem termos das coordenadas dos pontos P0 a P1:

d =√

(x1 − x0)2 + (y1 − y0)2

A tradução desta fórmula para Auto Lisp consiste da seguinte definição:

(defun distancia-2d (p0 p1)(sqrt (+ (quadrado (- (car p1) (car p0)))

(quadrado (- (cdr p1) (cdr p0))))))

Podemos agora experimentar a função com um caso concreto:

_$ (distancia-2d (cons 2 3) (cons 8 6))6.7082

29

P0

P1

d

x0

y0

x1

y1

Figura 3: Distância entre dois pontos.

5.4 Abstracção de Dados

Embora tenhamos pensado na operação distancia-2d como uma funçãoque recebe as coordenadas de dois pontos, quer quando observamos a defini-ção da função, quer quando observamos a sua utilização subsequente, o quevemos é a invocação das operações cons, car e cdr e, consequentemente,nada nos diz que a função esteja a lidar com coordenadas. Embora o conceitode coordenadas esteja presente nos nossos pensamentos, os nossos programasapenas mostram que estamos a construir e a manipular pares.

Esta diferença entre os conceitos que temos na nossa cabeça e os que empre-gamos na programação torna-se ainda mais evidente quando pensamos nou-tras entidades matemáticas que, tal como as coordenadas, também agrupamelementos mais simples. Um número racional, por exemplo, define-se comoum par de dois números inteiros, o numerador e o denominador. À semelhançado que fizemos com as coordenadas, também poderiamos criar um númeroracional em Auto Lisp à custa da função cons e poderiamos seleccionar o nu-merador e o denominador à custa das funções car e cdr. Outro exemplo seráa implementação de números complexos. Estes também são constituídos porpares de números reais e também poderiam ser implementados à custa dasmesma funções cons, car e cdr.

À medida que formos implementando em Auto Lisp as funções que ma-nipulam estes conceitos, a utilização sistemática das funções cons, car e cdrfará com que seja cada vez mais difícil perceber qual ou quais os tipo de da-dos a que se destina uma determinada função. De facto, a partir apenas dasfunções cons, car e cdr não podemos saber se estamos a lidar com coorde-nadas, racionais, complexos ou qualquer outro tipo de dados que tenhamosimplementado em termos de pares.

Para resolvermos este problema é necessário preservarmos no Auto Lisp oconceito original que pretendemos implementar. Para isso, temos de abstraira utilização que fazemos dos pares, “escondendo-a” no interior de funçõesque representem explicitamente os conceitos originais. Vamos agora exempli-ficar esta abordagem reconsiderando o conceito de coordenadas cartesianas

30

bi-dimensionais e introduzindo funções apropriadas para esconder a utiliza-ção dos pares.

Assim, para construirmos as coordenadas (x, y) a partir dos seus compo-nentes x e y, ao invés de usarmos directamente a função cons vamos definiruma nova função que vamos denominar de xy:13

(defun xy (x y)(cons x y))

A construção de coordenadas bi-dimensionais por intermédio da funçãoxy é apenas o primeiro passo para abstrairmos a utilização dos pares. O se-gundo passo é a criação de funções que acedem à abcissa x e à ordenada y dascoordenadas (x, y). Para isso, vamos definir, respectivamente, as funções cx ecy como abreviaturas de coordenada x e coordenada y:14

(defun cx (c)(car c))

(defun cy (c)(cdr c))

As funções xy, cx e cy constituem uma abstracção das coordenadas bi-dimensionais que nos permitem escrever as funções que manipulam coorde-nadas sem termos de pensar na sua implementação em termos de pares. Essefacto torna-se evidente quando reescrevemos a função distancia-2d em ter-mos destas novas funções:

(defun distancia-2d (p0 p1)(sqrt (+ (quadrado (- (cx p1) (cx p0)))

(quadrado (- (cy p1) (cy p0))))))

Reparemos que, contrariamente ao que acontecia com a primeira versãodesta função, a leitura da função dá agora uma ideia clara do que ela faz. Aoinvés de termos de pensar em termos de car e cdr, vemos que a função lidacom as coordenadas x e y. A utilização da função também mostra claramenteque o que ela está a manipular são coordenadas:

_$ (distancia-2d (xy 2 3) (xy 8 6))6.7082

A introdução das operações de coordenadas xy, cx e cy não só torna maisclaro o significado dos nossos programas como facilita bastante a definição de

13Naturalmente, podemos considerar outro nome igualmente apropriado como, por exem-plo, coordenadas-cartesianas-2d. Contudo, como é de esperar que tenhamos frequente-mente de criar coordenadas, é conveniente que adoptemos um nome suficientemente curto e,por isso, vamos adoptar o nome xy.

14Tal como a função xy poderia ter um nome mais explícito, também as funções que acedemà abcissa e à ordenada se poderiam denominar abcissa e ordenada ou coordenada-x ecoordenada-y ou qualquer outro par de nomes suficientemente claro. No entanto, sendo ascoordenadas um tipo de dados com muito uso, mais uma vez se tornará vantajoso que empre-guemos nomes curtos.

31

P

∆y

∆x

P ′

Figura 4: Deslocamento de um ponto.

novas funções. Agora, ao invés de termos de nos lembrar que as coordena-das são pares cujo primeiro elemento é a abcissa e cujo segundo elemento éa ordenada, basta-nos pensar nas operações básicas de coordenadas e definiras funções que pretendermos em termos delas. Por exemplo, imaginemos quepretendemos definir uma operação que faz “deslocar” um ponto de uma de-terminada distância, expressa em termos de um comprimento ∆x e de umaaltura ∆y, tal como está apresentado na Figura 4. Como será evidente pela Fi-gura, sendo P = (x, y), então teremos P ′ = (x+ ∆x, y + ∆y). Para simplificaro uso desta função, vamos denominá-la de +xy. Naturalmente, ela precisa dereceber, como parâmetros, o ponto de partida P e os incrementos ∆x e ∆y queiremos denominar de dx e dy, respectivamente.

A definição da função fica então:

(defun +xy (p dx dy)(xy (+ (cx p) dx)

(+ (cy p) dy)))

Uma vez que esta função recebe coordenadas como argumento e produzcoordenadas como resultado, ela constitui outra importante adição ao con-junto de operações disponíveis para lidar com coordenadas. Naturalmente,podemos usar a função +xy para definir novas funções como, por exemplo, oscasos particulares de deslocamento horizontal e vertical que se seguem:

(defun +x (p dx)(+xy p dx 0))

(defun +y (p dy)(+xy p 0 dy))

5.5 Coordenadas Tri-Dimensionais

As coordenadas bi-dimensionais localizam pontos num plano. A localizaçãode pontos no espaço requer coordenadas tri-dimensionais. Tal como aconte-cia com as coordenadas bi-dimensionais, também existem vários sistemas de

32

y

z

x

P

x

z

y

Figura 5: Coordenadas Cartesianas

coordenadas tri-dimensionais, nomeadamente, o sistema de coordenadas rec-tangulares, o sistema de coordenadas cilíndricas e o sistema de coordenadasesféricas. Em qualquer deles, a localização de um ponto no espaço é feita àcusta de três parâmetros independentes. As coordenadas tri-dimensionais são,por isso, triplos de números.

Nesta secção vamos debruçar-nos apenas sobre o sistema de coordenadasrectangulares, também conhecidas por coordenadas Cartesianas em honra ao seuinventor: René Descartes. Estas coordenadas estão representadas na Figura 5.

Como vimos na secção anterior, o Auto Lisp disponibiliza a operação conspara permitir formar pares de elementos, o que nos permitiu traduzir as coor-denadas bi-dimensionais da geometria para pares de números em Auto Lisp.Agora, a situação é ligeiramente mais complexa pois o Auto Lisp não disponi-biliza nenhuma operação capaz de fazer triplos de números. Ou quádruplos.Ou quíntuplos. Ou, na verdade, qualquer outro tipo de tuplo para além dosduplos.

Na realidade, esta aparente limitação do Auto Lisp é perfeitamente justifi-cada pois é trivial, a partir de pares, formarmos qualquer tipo de agrupamentoque pretendamos. Um triplo pode ser feito à custa de dois pares: um contendodois elementos e outro contendo este par e o terceiro elemento. Um quádruplopode ser feito à custa de três pares: um par para cada dois elementos e umterceiro par contendo os dois pares anteriores. Um quíntuplo pode ser feitoà custa de quatro pares: três pares para formar um quádruplo e um quartopar para juntar o quádruplo ao quinto elemento. Agrupamentos com maiornúmero de elementos podem ser formados simplesmente seguindo esta estra-tégia.

Uma vez que um triplo de números se pode implementar à custa de doispares, vamos usar os mesmos dois pares para implementar coordenadas tri-dimensionais. Para abstrair a utilização destes pares, vamos definir a funçãoxyz para construir coordenadas tri-dimensionais e vamos definir as funçõescx, cy e cz para aceder às coordenadas x, y e z, respectivamente. Comecemospela função xyz:

33

(defun xyz (x y z)(cons (cons x y) z))

Reparemos que a função produz o “triplo” fazendo um par com os doisprimeiros elementos x e y e, em seguida, fazendo um par com o par anterior ecom o terceiro elemento z. Em notação de “caixa e ponteiro” este agrupamentode elementos tem a seguinte forma:

x y

z

Como é evidente, qualquer outro arranjo de pares que juntasse os três ele-mentos seria igualmente válido.

A partir do momento em que temos a função xyz que constrói as coorde-nadas, estamos em condições de definir as funções que acedem às suas com-ponentes:

(defun cx (c)(car (car c)))

(defun cy (c)(cdr (car c)))

(defun cz (c)(cdr c))

Notemos que as funções que seleccionam os componentes das coordenadastêm de ser consistentes com os pares construídos pela função xyz. Uma vezque a coordenada x é o elemento da esquerda do par que é o elemento daesquerda do par que contém os componentes das coordenadas, precisamos deempregar duas vezes a função car na definição da função cx. Do mesmomodo, a coordenada y é o elemento da direita do par que é o elemento daesquerda, pelo que temos primeiro de aplicar a função car para aceder ao parque contém x e y e temos de lhe aplicar a função cdr para aceder a y.

As duas últimas combinações podem ser feita de forma abreviada usandoas funções pré-definidas caar e cdar:

_$ (caar (cons (cons 1 2) 3))1_$ (cdar (cons (cons 1 2) 3))2

Repare-se que a sequência de letras a e d entre as letras c e r indica a com-posição de funções em questão. Assim, a função cadr significa o car do cdrdo argumento, i.e., (cadr c)=(car (cdr c)); e a função caar significa ocar do car do argumento, i.e., (caar c)=(car (car c)). Por tradição, oAuto Lisp define todas as possíveis combinações de até quatro invocações das

34

funções car e cdr. Assim, para além das funções caar, cadr, cdar e cddrque correspondem a todas as possíveis invocações de duas funções, existemainda as variações de três invocações caaar, caadr, cadar, etc., e as vari-ações de quatro invocações caaaar, caaadr, caadar, caaddr, etc. No en-tanto, deve-se evitar a utilização destas combinações de funções como cadar,cdadar, cdar e, por vezes, até mesmo as car e cdr, pois tornam o códigopouco claro. Para termos um pouco mais de clareza devemos definir funçõescujo nome seja suficientemente explicativo do que a função faz.

A partir do momento em que temos coordenadas tri-dimensionais pode-mos definir operações que lidem com elas. Uma função útil é a que mede adistância entre dois pontos no espaço tri-dimensional. Ela corresponde a umageneralização trivial da função distancia-2d onde é necessário ter em contaa coordenada z dos dois pontos. Num espaço tri-dimensional, a distância d en-tre os pontos P0 = (x0, y0, z0) e P1 = (x1, y1, z1) é dada por

d =√

(x1 − x0)2 + (y1 − y0)2 + (z1 − z0)2

Traduzindo está definição para Auto Lisp, temos:

(defun distancia-3d (p0 p1)(sqrt (+ (quadrado (- (cx p1) (cx p0)))

(quadrado (- (cy p1) (cy p0)))(quadrado (- (cz p1) (cz p0))))))

À semelhança do que fizemos para as coordenadas bi-dimensionais, tam-bém podemos definir operadores para incrementar as coordenadas tri-dimensionaisde um ponto de uma determinada “distância” especificada em termos das pro-jecções ∆x, ∆y e ∆z :

(defun +xyz (p dx dy dz)(xyz (+ (cx p) dx)

(+ (cy p) dy)(+ (cz p) dz)))

Tal como acontecia com a função +xy, também a função +xyz é indepen-dente do arranjo particular de pares que usemos para implementar as coor-denadas tri-dimensionais. Desde que mantenhamos a consistência entre asfunções cx, cy e cz, que seleccionam os componentes das coordenadas tri-dimensionais, e a função xyz que cria coordenadas tri-dimensionais a partirdos seus componentes, podemos livremente empregar outros arranjos de pa-res.

Exercício 5.5.1 Defina a função +z que, dado um ponto em coordenadas tri-dimensionais, cal-cula as coordenadas do ponto que lhe está directamente por cima à distância ∆z .

Exercício 5.5.2 Defina a função ponto-medio que calcula as coordenadas tridimensionais doponto médio entre dois outros pontos P0 e P1 descritos também pelas suas coordenadas tridi-mensionais.

35

5.6 Coordenadas Bi- e Tri-Dimensionais

Embora tenhamos conseguido implementar quer coordenadas bi-dimensionais,quer coordenadas tri-dimensionais, as suas implementações em termos de pa-res são intrinsecamente incompatíveis. Este problema é claramente visível, porexemplo, na implementação da operação que selecciona a coordenada x que,na versão para coordenadas bi-dimensionais, tem a forma

(defun cx (c)(car c))

e, na versão para coordenadas tri-dimensionais, tem a forma

(defun cx (c)(car (car c)))

Obviamente, não é possível termos duas funções diferentes com o mesmonome, pelo que temos de escolher qual das versões pretendemos usar e, porarrastamento, qual o número de dimensões que pretendemos usar.

Acontece que, do ponto de vista matemático, as coordenadas bi-dimensionaissão apenas um caso particular das coordenadas tri-dimensionais e, portanto,seria desejável podermos ter uma única operação cx capaz de obter a co-ordenada x quer de coordenadas bi-dimensionais, quer de coordenadas tri-dimensionais. Logicamente, o mesmo podemos dizer da operação cy.

Para isso, é necessário repensarmos a forma como implementamos as co-ordenadas em termos de pares, de modo a encontrarmos um arranjo de paresque seja utilizável por ambos os tipos de coordenadas. Para simplificar, po-demos começar por considerar que, à semelhança do que fizemos para o casobi-dimensional, usamos um primeiro par para conter a coordenada x, tal comose apresenta em seguida:

x ?

Falta agora incluirmos a coordenada y no caso bi-dimensional e as coor-denadas y e z no caso tri-dimensional. Se o objectivo é termos um arranjo depares que funcione para os dois casos, então é crucial que a coordenada y fi-que armazenada de forma idêntica em ambos. Uma vez que as coordenadastri-dimensionais exigem pelo menos mais um par, podemos arbitrar a seguintesolução:

x

y ?

36

Embora no caso bi-dimensional não seja preciso armazenar mais nada, umpar é sempre composto por dois elementos, pelo que temos de decidir o queguardar no lugar assinalado com “?.” Uma vez que, para coordenadas bi-dimensionais, não há nada para guardar nesse lugar, podemos empregar umelemento qualquer que signifique o nada, o vazio. A linguagem Lisp dispo-nibiliza a constante nil precisamente para esse fim. O nome nil é uma con-tracção da palavra nihil que, em latim, significa o vazio.

Empregando a constante nil, as coordenadas bi-dimensionais (x, y) pas-sam a ter a seguinte implementação:

x

y nil

Para construir a estrutura anterior, a função xy passa a ter a seguinte defi-nição:

(defun xy (x y)(cons x (cons y nil)))

No caso das coordenadas tri-dimensionais, poderíamos substituir o “?”pelo valor da coordenada z mas, tal como considerámos as coordenadas bi-dimensionais como um caso particular das coordenadas tri-dimensionais, tam-bém as coordenadas tri-dimensionais podem ser vistas como um caso particu-lar das coordenadas tetra-dimensionais e assim sucessivamente.15 Isto sugereque devemos “repetir” a mesma estrutura à medida que vamos aumentando onúmero de dimensões. Assim, para as coordenadas tri-dimensionais (x, y, z),podemos conceber a seguinte organização:

x

y

z nil

Obviamente, precisamos de redefinir a função xyz:

(defun xyz (x y z)(cons x (cons y (cons z nil))))

15Embora, do ponto de vista da Arquitectura clássica, não seja necessário trabalhar com maisdo que três coordenadas, as modernas ferramentas de visualização podem necessitar de traba-lhar com coordenadas tetra-dimensionais, por exemplo, para lidarem com o tempo.

37

É fácil constatarmos que as duas estruturas anteriores são perfeitamentecompatíveis no sentido de podermos ter uma só versão das operações cx e cyque funcione quer para coordenadas bi-dimensionais, quer para coordenadastri-dimensionais. De acordo com a notação de “caixa e ponteiro” apresentada,podemos definir:

(defun cx (c)(car c))

(defun cy (c)(car (cdr c)))

(defun cz (c)(car (cdr (cdr c))))

É importante referirmos que esta redefinição das funções não perturba asfunções já definidas +xy, distancia-2d e distancia-3d. De facto, desdeque se mantenha a consistência entre as funções que constróiem as coordena-das e as que seleccionam os seus componentes, nada será afectado.

5.7 A Notação de Lista

O arranjo de pares que adoptámos para implementar coordenadas não é origi-nal, sendo usado desde a invenção da linguagem Lisp para implementar agru-pamentos com um número arbitrário de elementos. A esses agrupamentosdá-se o nome de listas. As listas são um tipo de dados muito utilizado na lin-guagem Lisp e o nome Lisp é, na verdade, um acrónimo de “List Processing.”

De forma a tornar o uso de listas mais simples, o Lisp não só disponibilizaum conjunto de operações especificamente destinadas a lidar com listas comoimprime as listas de forma especial: quando o segundo elemento de um paré outro par ou é nil, o Lisp escreve o resultado sob a forma de uma lista deelementos:

_$ (cons 1 (cons 2 (cons 3 nil)))(1 2 3)

Repare-se, no exemplo anterior, que os pares foram escritos usando umanotação diferente que evita o ponto necessário para representar os “dotted pairs”e evita também escrever a constante nil. Esta notação é intencionalmenteusada pelo Lisp para facilitar a leitura de listas pois, quando se pensa numalista como uma sequência de elementos é preferível ver esses elementos dis-postos em sequência a vê-los como um aglomerado de pares. Contudo, con-vém não esquecermos que, internamente, a lista é de facto implementada usandoum aglomerado de pares.

Dada uma lista de elementos, qual o significado de lhe aplicar as operaçõescar e cdr? Para responder a esta pergunta podemos simplesmente experi-mentar:

_$ (car (cons 1 (cons 2 (cons 3 nil))))1_$ (cdr (cons 1 (cons 2 (cons 3 nil))))(2 3)

38

Note-se que a função car obteve o primeiro elemento da lista enquantoque a função cdr obteve os restantes elementos da lista, i.e., a lista a partir(inclusive) do segundo elemento. Isto sugere que podemos ver uma lista comosendo composta por um primeiro elemento seguido do resto da lista. Segundoeste raciocínio, depois de termos obtido o último elemento da lista, o restoda lista deverá ser uma lista vazia, i.e., uma lista sem quaisquer elementos.Para testarmos esta ideia podemos aplicar sucessivamente a função cdr a umadada lista:

_$ (cons 1 (cons 2 (cons 3 nil)))(1 2 3)_$ (cdr (cons 1 (cons 2 (cons 3 nil))))(2 3)_$ (cdr (cdr (cons 1 (cons 2 (cons 3 nil)))))(3)_$ (cdr (cdr (cdr (cons 1 (cons 2 (cons 3 nil))))))nil

Reparemos que, à medida que fomos aplicando sucessivamente a funçãocdr, a lista foi “encolhendo,” i.e., fomos acedendo a partes sucessivamentemais pequenas da lista. No limite, quando aplicámos o cdr à lista (3) quejá só tinha um elemento, estaríamos à espera de obter a lista vazia (), i.e, alista sem quaisquer elementos mas, no seu lugar, obtivémos nil. Tal deve-se ao facto de as “listas” do Lisp serem apenas um artifício de visualizaçãosobre um arranjo particular de pares e, na realidade, a operação cdr continuaa fazer o que sempre fez: acede ao segundo elemento do par que, no caso deuma lista com um só elemento é apenas a constante nil. Por este motivo, paraalém de representar o vazio, é usual considerar que a constante nil tambémrepresenta a lista vazia.

Tal como as funções car e cdr podem ser vistas como operações sobrelistas, também a função cons o pode ser. Reparemos na seguinte interacção:

_$ (cons 1 nil)(1)_$ (cons 1 (cons 2 nil))(1 2)_$ (cons 1 (cons 2 (cons 3 nil)))(1 2 3)

Como podemos ver, quando o segundo argumento de um cons é uma lista(vazia ou não), o resultado é visto como a lista que resulta de juntar o primeiroargumento do cons no início daquela lista.

Para simplificar a criação de listas, o Lisp disponibiliza uma função querecebe qualquer número de argumentos e que constroi uma sequência de parescom os sucessivos argumentos de forma a constituirem uma lista:

_$ (list 1 2 3 4)(1 2 3 4)_$ (list 1 2)(1 2)_$ (list 1)(1)

39

Como é evidente, uma expressão da forma (list e1 e2 ... en) é abso-lutamente idêntica a (cons e1 (cons e2 (... (cons en nil) ...))).

Obviamente, quando recebe zero argumentos a função list produz umalista vazia:

_$ (list)nil

Exercício 5.7.1 Qual o significado da expressão (list (list))?

Exercício 5.7.2 Quantos elementos tem a lista resultante de (list (list (list)))?

Exercício 5.7.3 Qual o significado da expressão (cons (list 1 2) (list 3 4))?

5.8 Átomos

Vimos que os pares (e as listas) permitem-nos criar aglomerações de elementose que podemos posteriormente aceder aos componentes dessas aglomeraçõesusando as operações car e cdr. De facto, através de combinações de cars ecdrs conseguimos aceder a qualquer elemento que faça parte da aglomeração.

O que não é possível, contudo, é usar car ou cdr para aceder ao interiorde um desses elementos. Por exemplo, não é possível, usando as operaçõescar ou cdr, aceder a um carácter qualquer dentro de uma string. Por estemotivo, as strings dizem-se atómicas, i.e., não decomponíveis. Os números,obviamente, também são atómicos.

Sendo uma lista uma aglomeração de elementos, é natural pensar que umalista não é atómica. Contudo, há uma lista em particular que merece um poucomais de atenção: a lista vazia. Uma lista vazia não é decomponível pois, defacto não contém nenhum elemento que se possa obter usando o car nemnenhuns restantes elementos que se possam obter usando o cdr.16 Se não sepode usar nem o car nem o cdr, isso sugere que a lista vazia é, na realidade,um átomo e, de facto, o Auto Lisp assim o considera.

O facto de a lista vazia ser, simultaneamente, um átomo e uma lista pro-voca um aparente paradoxo na definição de atomicidade pois faz com queexista uma lista—a lista vazia—que aparenta ser simultaneamente atómica enão atómica. No entanto, o parodoxo é apenas aparente pois, na realidade, adefinição de atomicidade apenas exclui pares. Uma vez que a lista vazia não éum par, ela é atómica, embora seja uma lista.17

Exercício 5.8.1 Classifique, quanto à atomicidade, o resultado da avaliação das seguintes ex-pressões:

1. (cons 1 2)

2. (list 1 2)

3. (strcat "Bom" " " "Dia")

16Na realidade, alguns dialectos de Lisp, incluindo o Auto Lisp, consideram que é possívelaplicar as funções car e cdr à lista vazia, obtendo-se, em ambos os casos, a lista vazia.

17Nesta matéria, a documentação do Auto Lisp está simplesmente incorrecta pois afirma que“tudo o que não for uma lista é um átomo,” esquecendo-se de excluir a lista vazia que, obvia-mente, é uma lista mas é também um átomo.

40

4. nil

5. (+ 1 2 3)

6. (list 1)

7. (list)

8. (car (cons 1 (cons 2 nil)))

9. (cdr (cons 1 (cons 2 nil)))

5.9 Tipos Abstractos

A utilização das operações xyz, cx, cy e cz permite-nos definir um novo tipode dados—as coordenadas cartesianas tri-dimensionais—e, mais importante,permite-nos abstrair a implementação desse tipo de dados, i.e., esquecer o ar-ranjo particular de pares que o implementa. Por este motivo, denomina-se estenovo tipo de dados por tipo abstracto. Ele é abstracto porque apenas existe nonosso pensamento. Para o Lisp, como vimos, as coordenadas são apenas ar-ranjos particulares de pares. Esse arranjo particular denomina-se representaçãodos elementos do tipo e, como vimos quando mudámos de um particular ar-ranjo de pares para outro, é possível alterarmos a representação de um tipoabstracto sem afectar os programas que usam o tipo abstracto.

Um tipo abstracto é caracterizado apenas pelas suas operações e estas dividem-se em dois conjuntos fundamentais: os construtores que, a partir de argumen-tos de tipos apropriados, produzem elementos do tipo abstracto, e os selecto-res que, a partir de um elemento do tipo abstracto, produzem os seus consti-tuintes. Existem ainda outras categorias de operações mas, por agora iremosconcentrarmo-nos apenas nestas duas.

No caso das coordenadas cartesianas tri-dimensionais, o conjunto dos cons-trutores apenas contém a função xyz, enquanto que o conjunto dos selectorescontém as funções cx, cy e cz. Para um tipo abstracto, a relação entre osconstrutores e os selectores é crucial pois eles têm de ser consistentes entresi. Matematicamente, essa consistência é assegurada por equações que, no casopresente, se podem escrever da seguinte forma:

(cx (xyz x y z)) = x

(cy (xyz x y z)) = y

(cz (xyz x y z)) = z

Se modificarmos a representação de coordenadas mas mantivermos a con-sistência entre os construtores e os selectores, manter-se-á também o correctofuncionamento do tipo abstracto. Foi por este motivo que nos foi possível re-fazer a implementação das coordenadas bi- e tri-dimensionais sem afectar asfunções já definidas +xy, distancia-2d e distancia-3d.

Exercício 5.9.1 O que é um construtor de um tipo abstracto?

Exercício 5.9.2 O que é um selector de um tipo abstracto?

Exercício 5.9.3 O que é a representação de um tipo abstracto?

41

Exercício 5.9.4 Dado um ponto P0 = (x0, y0) e uma recta definida por dois pontos P1 =(x1, y1) e P2 = (x2, y2), a distância mínima d do ponto P0 à recta obtém-se pela fórmula:

d =|(x2 − x1)(y1 − y0)− (x1 − x0)(y2 − y1)|p

(x2 − x1)2 + (y2 − y1)2

Defina uma função denominada distancia-ponto-recta que, dadas as coordenadas dospontos P0, P1 e P2, devolve a distância mínima de P0 à recta definida por P1 e P2.

5.10 Coordenadas em AutoCad

Como mostrámos nas secções anteriores, a compatibilização das operaçõesque lidam com coordenadas bi- e tri-dimensionais levou-nos a escolher representá-las por intermédio de listas. Para além de esta representação ser mais sim-ples de ler, tem ainda outra enorme vantagem: é totalmente compatível coma forma como as funcionalidades do AutoCad esperam receber coordenadas.De facto, como iremos ver, o próprio AutoCad necessita que as coordenadasdas entidades geométricas que pretendemos criar sejam especificadas comolistas de números: dois números no caso das coordenadas bi-dimensionais etrês números no caso das coordenadas tri-dimensionais.

Seria então de esperar que já existissem pré-definidas em Auto Lisp as ope-rações que lidam com coordenadas mas, na realidade, tal não acontece porque,na altura em que o Auto Lisp foi criado a teoria dos tipos abstractos ainda nãotinha sido efectivamente posta em prática. Nessa altura, não se tinha a noçãoclara das vantagens desta abordagem mas tinha-se uma noção muito clara deum dos seus problemas: piorava a performance dos programas.

De facto, é óbvio que, a partir de uma lista representando as coordenadasde um ponto, é mais rápido obter a coordenada x usando a função car do queusando o selector cx que, indirectamente, invoca a função car.

Como, na altura em que a teoria dos tipos abstractos apareceu, os computa-dores ainda não eram suficientemente rápidos para que os programadores sepudessem dar ao luxo de “desperdiçar” performance, a penalização induzidapela teoria não foi bem aceite durante algum tempo.18 Quando os computado-res se tornaram suficientemente rápidos, a situação mudou e os programado-res passaram a considerar que as vantagens da utilização de tipos abstractosultrapassava largamente a cada vez mais residual desvantagem da perda deperformance. Actualmente, é ponto assente que devemos sempre usar tiposabstractos.

Infelizmente, o Auto Lisp foi desenvolvido há demasiados anos e não seencontra tão actualizado como seria desejável. Se fizermos uma análise dosprogramas Auto Lisp existentes, incluindo programas desenvolvidos recente-mente, iremos constatar que a prática usual é a manipulação directa da repre-sentação dos tipos, saltando por cima de qualquer definição de tipos abstrac-tos. Em particular, a manipulação de coordenadas é feita usando directamenteas operações do tipo lista, i.e., construindo coordenadas com a função list

18Essa penalização variava de linguagem para linguagem. Nalgumas linguagens, ditas estati-camente tipificadas, a penalização poderia ser pequena. Noutras, ditas dinamicamente tipificadas,a penalização era geralmente mais elevada. O Lisp insere-se no grupo das linguagens dinami-camente tipificadas.

42

e acedendo aos seus valores com as funções car, cadr e caddr. Aconteceque esta violação do tipo abstracto ocorre, actualmente, muito mais por razõeshistóricas do que por razões de performance.

Embora sejamos grandes defensores do respeito à pragmática do AutoLisp, neste caso particular vamos adoptar uma abordagem diferente: por mo-tivos de clareza dos programas, de facilidade da sua compreensão e de faci-lidade da sua correcção, vamos empregar as operações do tipo abstracto co-ordenadas, nomeadamente os construtores xy e xyz e os selectores cx, cy ecz e vamos evitar, sempre que possível, aceder directamente à representaçãodas coordenadas, i.e., vamos evitar usar as funções list, car, cadr e caddrpara manipular coordenadas. Isso não impede que usemos essas funções paraoutros fins, em particular, para manipular listas. Uma vez que as coordena-das estão implementadas usando listas, esta distinção poderá parecer confusamas, na verdade, não é: as coordenadas não são listas embora a representaçãodas coordenadas seja uma lista.

Naturalmente, quando o leitor consultar programas escritos por outrosprogramadores de Auto Lisp deverá ter em conta estas subtilezas e deveráconseguir perceber se uma dada lista presente num programa significa coor-denadas ou se significa outro tipo abstracto qualquer.

5.11 Coordenadas Polares

Uma das vantagens da utilização dos tipos abstractos de informação é quese torna fácil desenvolver novas operações com base nas operações do tipo.Por exemplo, embora o tipo coordenadas bi-dimensionais esteja descrito emtermos de coordenadas rectangulares, nada nos impede de definir operações paramanipular coordenadas polares.

Tal como representado da Figura 6, uma posição no plano bi-dimensional édescrita pelos números x e y—significando, respectivamente, a abcissa e a orde-nada—enquanto que a mesma posição em coordenadas polares é descrita pelosnúmeros ρ e φ—significando, respectivamente, o raio vector (também chamadomódulo) e o ângulo polar (também chamado argumento). Com a ajuda da trigo-nometria e do teorema de Pitágoras conseguimos facilmente relacionar estascoordenadas entre si: {

x = ρ cosφy = ρ sinφ ρ =√x2 + y2

φ = arctany

x

Com base nas equações anteriores, podemos agora definir a função pol(abreviatura de “polar”) que contrói coordenadas a partir da sua representa-ção polar simplesmente convertendo-a para a representação rectangular equi-valente.

(defun pol (ro fi)(xy (* ro (cos fi))

(* ro (sin fi))))

43

φ

x

Figura 6: Coordenadas rectangulares e polares.

Assim sendo, o tipo abstracto coordenadas polares fica representado emtermos do tipo coordenadas rectangulares. Por este motivo, os selectores dotipo coordenadas polares—a função pol-ro que nos permite obter o móduloρ e a função pol-fi que nos permite obter o argumento φ—terão de usar asoperações do tipo coordenadas rectangulares:

(defun pol-ro (c)(sqrt (+ (quadrado (cx c)) (quadrado (cy c)))))

(defun pol-fi (c)(atan (cy c) (cx c)))

Eis alguns exemplos do uso destas funções:19

_$ (pol (sqrt 2) (/ pi 4))(1.0 1.0)_$ (pol 1 0)(1.0 0.0)_$ (pol 1 (/ pi 2))(6.12323e-017 1.0)_$ (pol 1 pi)(-1.0 1.22465e-016)

Uma outra operação bastante útil é a que, dado um ponto P = (x, y) eum “vector” com origem em P e descrito em coordenadas polares por umadistância ρ e um ângulo φ, devolve o ponto localizado na extremidade destinodo vector, tal como é visualizado na Figura 7. A trigonometria permite-nosfacilmente concluir que as coordenadas do ponto destino são dadas por P ′ =(x+ ρ cosφ, y + ρ sinφ).

A tradução directa da função para Lisp é:

(defun +pol (p ro fi)(xy (+ (cx p) (* ro (cos fi)))

(+ (cy p) (* ro (sin fi)))))

Como exemplos de utilização, temos:

19Note-se, nestes exemplos, que alguns valores das coordenadas não são zero como seriaexpectável, mas sim valores muito próximos de zero que resultam de erros de arredondamento.

44

ρ

P ′

Figura 7: O deslocamento de um ponto em coordenadas polares.

_$ (+pol (xy 1 2) (sqrt 2) (/ pi 4))(2.0 3.0)_$ (+pol (xy 1 2) 1 0)(2.0 2.0)_$ (+pol (xy 1 2) 1 (/ pi 2))(1.0 3.0)

Embora a função +pol funcione perfeitamente, a sua definição não é tãosimples quanto poderia ser. Isso é evidente quando a comparamos com a dafunção +xy (que repetimos de seguida):

(defun +xy (p dx dy)(xy (+ (cx p) dx)

(+ (cy p) dy)))

É relativamente evidente que a função +pol está a fazer parte do trabalhoque já é feito pela função +xy, nomeadamente no que diz respeito à soma dascoordenadas do ponto p com as componentes que resultam da conversão decoordenadas polares para rectangulares. Assim, é possível simplificar a função+pol escrevendo apenas:

(defun +pol (p ro fi)(+xy p

(* ro (cos fi))(* ro (sin fi))))

Uma observacão atenta das funções +xy e +pol permite-nos constatar queelas partilham um mesmo comportamento: ambas recebem um ponto e um“vector” a somar a esse ponto para produzir um novo ponto: esse “vector” édescrito pelos parâmetros ∆x e ∆y no primeiro caso e por ρ e φ no segundo.

O facto de ambas as funções partilharem um comportamento leva-nos na-turalmente a pensar na sua possível generalização, através da definição deuma única função +c (abreviatura de +coordenada) que, dado um ponto P0

e outro ponto P1 representando a extremidade final de um vector com extre-midade inicial na origem, devolve o ponto que resulta de somar esse vectorao primeiro ponto, tal como está ilustrado da Figura 8. Naturalmente, quer

45

P0φ

x

P ′

φ

x

P1

Figura 8: O deslocamento de um ponto por adição de um vector.

o ponto P0 quer o ponto P1 podem ser especificados em qualquer sistema decoordenadas desde que seja possível relacionar esse sistema com o sistema decoordenadas rectangulares.

Para definirmos esta função a abordagem mais simples será explorar aspropriedades aditivas das coordenadas rectangulares:

(defun +c (p0 p1)(xy (+ (cx p0) (cx p1))

(+ (cy p0) (cy p1))))

Uma vez que as coordenadas polares estão representadas em termos de co-ordenadas rectangulares, mesmo quando especificamos coordenadas polaresa operação +c continua a funcionar correctamente.

5.12 A função command

A partir do momento em que sabemos construir coordenadas torna-se possívelutilizar um conjunto muito vasto de operações gráficas do Auto Lisp. Existemtrês maneiras diferentes de se invocar essas operações gráficas mas, por agora,vamos explorar apenas a mais simples: a função command.

A função command aceita um número arbitrário de expressões que vai ava-liando e passando os valores obtidos para o AutoCad à medida que o AutoCados vai requerendo.20 No caso de utilização mais comum, a função commandrecebe uma cadeia de caracteres que descreve o comando AutoCad que se pre-tende executar seguido de qualquer número de argumentos que serão usadoscomo os dados necessários para a execução desse comando.

A vantagem da função command é permitir criar programas completos quecriam entidades gráficas tal como um normal utilizador de AutoCad o pode-ria fazer de forma manual. A título de exemplo, consideremos a criação de umcírculo. Para se criar um círculo em AutoCad pode-se usar o comando circle

20Este comportamento mostra que, na realidade, command não pertence ao conjunto das fun-ções “normais” pois, ao contrário destas, só avalia os argumentos à medida que eles vão sendonecessários.

46

e fornecer-lhe as coordenadas do centro e o raio do círculo. Se pretenderemoscriar o mesmo círculo a partir do Auto Lisp, ao invés de invocarmos interac-tivamente o comando AutoCad circle e de lhe fornecermos os dados neces-sários, podemos simplesmente invocar a função command e passar-lhe todosos dados necessários como argumentos, começando pela string "circle", se-guida das coordenadas do centro do círculo e, por último, seguida do raio docírculo.

Para concretizarmos o exemplo, consideremos a criação de um círculo deraio r = 1 centrado na posição (x, y) = (2, 3). A invocação Auto Lisp que criaeste círculo é a seguinte:

_$ (command "circle" (list 2 3) 1)nil

Como se pode ver pelo exemplo, as coordenadas do centro do círculo fo-ram especificadas através da criação de uma lista. No entanto, como referimosna secção 5.10, vamos evitar a especificação de coordenadas directamente emtermos da sua representação como listas e vamos, em seu lugar, usar as opera-ções do tipo. Assim, a expressão anterior fica mais correcta na seguinte forma:

_$ (command "circle" (xy 2 3) 1)nil

Como se pode ver também, a invocação da função command devolve nilcomo resultado.

Um outro exemplo da utilização desta função é na colocação de um texto nanossa área de desenho. Para isso, podemos usar novamente a função commandmas, desta vez, os argumentos a passar serão outros, nomeadamente, a cadeiade caracteres "text", as coordenadas onde pretendemos colocar o (canto in-ferior esquerdo do) texto, um número que representa a altura do texto, umnúmero que representa o ângulo (em radianos) que a base do texto deveráfazer com a horizontal e, finalmente, uma cadeia de caracteres com o texto ainserir. Eis um exemplo:

_$ (command "text" (xy 1 1) 1 0 "AutoCad")nil

O resultado da avaliação das duas invocações anteriores é visível da Fi-gura 9.

Finalmente, a função command pode ser usada para alterar os parâmetrosde funcionamento do AutoCad. Por exemplo, para desligar os Object Snapspodemos fazer:

_$ (command "osnap" "off")nil

É importante memorizar esta última expressão pois, sem a sua invocação,todos os usos da função command estarão sob o efeito de Object Snaps, fazendocom que as coordenadas que indicamos para as nossas figuras geométricas não

47

Figura 9: Um círculo com o texto "AutoCad".

Figura 10: Círculos e textos especificados usando coordenadas polares.

sejam estritamente respeitadas pelo AutoCad, que as “arredondará” de acordocom a malha do Object Snaps.

Como exemplo da utilização de comandos em Auto Lisp, a Figura 10 mos-tra o resultado da avaliação das seguintes expressões onde usamos coordena-das polares para desenhar círculos e texto:

(command "erase" "all" "")(command "circle" (pol 0 0) 4)(command "text" (+pol (pol 0 0) 5 0)

1 0 "Raio: 4")(command "circle" (pol 4 (/ pi 4)) 2)(command "text" (+pol (pol 4 (/ pi 4)) 2.5 0)

0.5 0 "Raio: 2")(command "circle" (pol 6 (/ pi 4)) 1)(command "text" (+pol (pol 6 (/ pi 4)) 1.25 0)

0.25 0 "Raio: 1")(command "zoom" "e")

Exercício 5.12.1 Refaça o desenho apresentado na Figura 10 mas utilizando apenas coordena-das rectangulares.

Exercício 5.12.2 Defina uma função denominada circulo-e-raio que, dadas as coordena-das do centro do círculo e o raio desse círculo, cria o círculo especificado no AutoCad e, à

48

semelhança do que se vê na Figura 10, coloca o texto a descrever o raio do círculo à direita docírculo. O texto deverá ter um tamanho proporcional ao raio do círculo.

Exercício 5.12.3 Utilize a função circulo-e-raio definida na pergunta anterior para recons-tituir a imagem apresentada na Figura 10.

Existem algumas particularidades do comportamento da função commandque é importante discutir. Para as compreendermos é preciso recordar que afunção command “simula” a interacção do utilizador com o AutoCad. Consi-deremos então um exemplo de uma interacção: imaginemos que estamos emfrente à interface do AutoCad e pretendemos apagar todo o conteúdo da nossaárea de desenho no AutoCad. Para isso, podemos invocar o comando erasedo AutoCad, o que podemos fazer escrevendo as letras e-r-a-s-e e premindoenter no final. Acontece que o comando não pode ser imediatamente execu-tado pois o AutoCad precisa de saber mais informação, em particular, o queé que é para apagar. Para isso, o AutoCad pede-nos para seleccionar um oumais objectos para apagar. Nessa altura, se respondermos com a palavra all epremirmos novamente a tecla enter, o AutoCad selecciona todos os objectosmas fica a aguardar que indiquemos que terminámos a selecção de objectos, oque se pode fazer premindo simplesmente a tecla enter.

Ora para que a função command possa simular a interacção que acabámosde descrever, ela tem de passar ao AutoCad todas as informações necessá-rias e tem de o fazer à medida que o AutoCad precisa delas. Assim, cadasequência de teclas que foi por nós dada ao AutoCad deverá ser agora dadana forma de string como argumento à função command. No entanto, como nãofaz muito sentido obrigar o utilizador a especificar, numa string o premir datecla enter, a função command assume que, após passar todos os caracteresde uma string ao AutoCad, deve passar-lhe também o correspondente “premirda tecla enter.”

Tentemos agora, à luz desta explicação, construir a invocação da funçãocommand que simula a interacção anterior. O primeiro passo da avaliação daexpressão é, como referimos, o passar da sequência de letras e-r-a-s-e segui-das do premir virtual da tecla enter, o que podemos fazer escrevendo:

(command "erase" ...)

Em seguida, sabemos que o AutoCad nos vai pedir para seleccionarmosos objectos a apagar, pelo que escrevemos a string com a palavra all que afunção irá passar ao AutoCad, novamente terminando-a com o premir virtualda tecla enter:

(command "erase" "all" ...)

Finalmente, sabemos que depois desta interacção, o AutoCad vai continuarà espera que indiquemos que terminámos a selecção de objectos através dopremir da tecla enter, o que implica que temos de indicar à função commandpara simplesmente passar o tal “premir virtual da tecla enter.” Ora já sabe-mos que a função faz isso automaticamente após passar os caracteres de umastring pelo que, se não queremos passar caracteres alguns mas apenas a teclaenter então, logicamente, temos de usar uma string vazia, ou seja:

49

(command "erase" "all" "")

Assim, a string vazia, quando usada como argumento de command, é equi-valente a premir a tecla enter na linha de comandos do AutoCad.

É claro que, se na sequência de uma invocação da função command, o Au-toCad ficar à espera de dados, novas invocações da função command poderãoprovidenciar esses dados ou o utilizador poderá dar essa informação directa-mente no AutoCad. Isto quer dizer que a invocação anterior pode também serfeita na forma:

(command "erase")(command "all")(command "")

Como se pode constatar pelo exemplo anterior, a função command terminaassim que conseguir passar todos os argumentos que tem para o AutoCad, in-dependentemente de eles serem suficientes ou não para o AutoCad conseguircompletar o pretendido. Se não forem, o AutoCad limita-se a continuar à es-pera que lhe forneçamos, via command ou directamente na interface, os dadosque lhe faltam, o que nos permite implementar um processo de “colaboração”com o AutoCad: parte do que se pretende é feito no lado do Auto Lisp e aparte restante no lado do AutoCad.

Por exemplo, imaginemos que pretendemos criar um círculo num dadoponto mas queremos deixar ao utilizador a escolha do raio. Uma solução seráiniciar a construção do círculo centrado num dado ponto através da funçãocommand mas aguardar que o utilizador termine essa construção indicandoexplicitamente no AutoCad (por exemplo, usando o rato) qual é o raio do cír-culo pretendido. Assim, iniciariamos o comando com:

(command "circle" (xy 0 0))

e o utilizador iria então ao AutoCad terminar a criação do círculo através daindicação do raio.

Um problema um pouco mais complicado será criar um círculo de raio fixomas deixar ao utilizador a escolha do centro. A complicação deriva do facto denão sabermos quais são as coordenadas do centro mas queremos passar o raio:se nos falta o argumento do “meio,” ou seja, a posição do centro do círculo,não podemos passar o do “fim,” ou seja, o raio do círculo.

Há várias soluções para este problema mas, por agora iremos explorar ape-nas uma. Para contornar o problema, o AutoCad permite a utilização do carác-ter especial “\” para indicar que se pretende suspender momentaneamente apassagem de argumentos para o AutoCad, para se retomar essa passagem as-sim que o AutoCad obtenha o valor correspondente ao argumento que nãofoi passado. Para facilitar a legibilidade dos programas, o símbolo pause de-signa precisamente a string que contém esse único carácter “\.” A seguinteinteracção mostra um exemplo da utilização desta possibilidade:21

21Note-se que, tal como referido na Tabela ??, o carácter “\” é um carácter de escape e é porisso que tem de ser escrito em duplicado.

50

_$ pause"\\"_$ (command "circle" pause 3)nil

Um outro aspecto importante da função command prende-se com a passa-gem de números e coordenadas (representadas por listas de números). Em-bora nos exemplos anteriores estes tipos de dados tenham sido directamentepassadas à função command, na realidade também podem ser passados “si-mulando” o premir das teclas correspondentes. Isto quer dizer que é possívelcriar um círculo com centro no ponto (2, 3) e raio 1 através da expressão:

(command "circle" "2,3" "1")

O que acontece, na prática, é que os argumentos da função command, paraalém de só serem avaliados quando são necessários, são também automatica-mente convertidos para o formato pretendido pelo AutoCad. Isto permite-nos“trabalhar” as propriedades das entidades geométricas no formato que nos émais conveniente (números, coordenadas, etc) e deixar à função command aresponsabilidade de converter os valores para o formato apropriado para oAutoCad.

Exercício 5.12.4 Pretendemos colocar duas circunferências de raio unitário em torno da origemde modo a que fiquem encostadas uma à outra, tal como se ilustra no seguinte desenho:

Escreva uma sequência de expressões que, quando avaliadas, produzem a figura anterior.

Exercício 5.12.5 Pretendemos colocar quatro circunferências de raio unitário em torno da ori-gem de modo a que fiquem encostadas umas às outras, tal como se ilustra no seguinte desenho:

Escreva uma sequência de expressões que, quando avaliadas, produzem a figura anterior.

Exercício 5.12.6 Pretendemos colocar três circunferências de raio unitário em torno da origemde modo a que fiquem encostadas umas às outras, tal como se ilustra no seguinte desenho:

51

Escreva uma sequência de expressões que, quando avaliadas, produzem a figura anterior.

5.13 Variantes de Comandos

Quando fornecemos um nome na linha de comando do AutoCad, ele tentaperceber o que é pedido seguindo um algoritmo que involve os seguintes pas-sos:22

1. O nome é pesquisado na lista de comandos do AutoCad. Se o nomeestá na lista, então, se o nome é precedido de um ponto “.,” executa ocomando e termina, caso contrário pesquisa o nome na lista de comandosnão-definidos23 e, se não o encontrar, executa o comando e termina.24

2. O nome é pesquisado na lista de comandos externos definidos no ficheirode parâmetros acad.pgp. Se o encontrar, executa o comando e termina.

3. O nome é pesquisado na lista de comandos definidos pelo Auto Lisp. Seo encontrar então, primeiro, verifica se é um comando de carregamentoautomático e, se for, o programa correspondente é carregado e, segundo,executa o comando e termina.

4. O nome é pesquisado na lista de sinónimos (aliases) definidos no ficheirode parâmetros do programa. Se o encontrar, substitui o nome pela suaexpansão e volta ao princípio.

5. Termina com uma mensagem de erro indicando que o nome é desconhe-cido.

Repare-se que o algoritmo anterior fala de comandos cujo nome é prece-dido de um ponto, de comandos não definidos, comandos externos, etc. Todasestas variedades existem porque, na realidade, é possível redefinir ou tornarcomo não definidos os comandos pré-definidos do AutoCad. Isto implica quequando invocamos um comando como, por exemplo, circle, estamos na re-alidade a invocar a mais recente definição que foi feita para esse comando. Sóno caso de não ter sido feita qualquer redefinição do comando e de este nãoter sido tornado não definido é que iremos invocar a definição original. A con-sequência é que, não sabendo se foi feita uma redefinição ou se o comando nãofoi tornado como não definido, não podemos ter a certeza do que vai realmenteser executado.

Como se pode ver pelo algoritmo descrito acima, o AutoCad admite umavariante para todos os comandos: se o nome do comando começar com o ca-rácter ponto “.” então é feita a execução do comando pré-definido do Auto-Cad. Desta forma, este pequeno “truque” permite-nos ultrapassar qualquerredefinição ou não definição de um comando.

22Por motivos pedagógicos, esta descrição é uma simplificação da realidade.23Um comando não-definido não é a mesma coisa que um comando indefinido. O primeiro

corresponde a um comando conhecido que foi explicitamente declarado como não definidoenquanto o segundo corresponde a um comando totalmente desconhecido.

24Este comportamento permite que se possam tornar comandos como não-definidos e, aindaassim, pode executá-los desde que se preceda o seu nome de um ponto.

52

Uma outra característica do AutoCad que pode complicar a utilização dafunção command é a internacionalização: em diferentes países, o AutoCad uti-liza os nomes dos comandos traduzidos para diferentes línguas. Por exem-plo, o comando circle escreve-se kreis no AutoCad Alemão, cercle noFrancês, cerchio no Italiano, kružnice no Checo e circulo no Espanhol.Assim, se tivermos feito um programa que invoca o comando circle e ten-tarmos executar esse comando num AutoCad preparado para outra língua,iremos obter um erro por o comando ser desconhecido nessa língua.

Para evitar este problema, o AutoCad admite ainda outra variante paratodos os comandos: se o nome do comando começar com o carácter “_” entãoé feita a execução do comando na língua original do AutoCad, que é o Inglês.

A combinação destas duas variantes é possível. O nome _.circle (ou._circle) indica a versão original do comando, independentemente de alte-rações linguísticas ou redefinições do comando.

Para evitar problemas, de agora em diante iremos sempre utilizar esta con-venção de preceder com os caracteres “_.” todos os comandos que usarmosna operação command.

Exercício 5.13.1 Redefina e teste a função circulo-e-raio de modo a usar os comandospré-definidos do AutoCad, independentemente de redefinições de comandos ou mudanças delíngua.

Exercício 5.13.2 Defina a função comando-pre-definido que, dado o nome de um comandoem inglês, devolve esse nome convenientemente modificado para permitir aceder ao comandocorrespondente pré-definido no AutoCad independentemente da língua em que este esteja.

5.14 Ângulos em Comandos

Alguns dos comandos do AutoCad necessitam de saber raios e ângulos em si-multâneo. Por exemplo, para criar um polígono regular, o comando polygonnecessita de saber quantos lados ele deverá ter, qual o centro do polígono, seele vai estar inscrito numa circunferência ou circunscrito numa circunferênciae, finalmente o raio dessa circunferência e o ângulo que o “primeiro” vérticedo polígono faz com o eixo dos x. Este dois últimos parâmetros, contudo, nãopodem ser fornecidos separadamente, sendo necessário especificá-los de umaforma conjunta, através de uma string em que se junta o raio e o ângulo se-parados pelo carácter “<” (representando um ângulo) e precedidos do carác-ter “@” (representando um incremento relativamente ao último ponto dado,que era o centro da circunferência). Por exemplo, o raio 2 com um ângulo de30oescreve-se "@2<30". Note-se que o ângulo tem de estar em graus. Se, aoinvés de fornecermos esta string fornecermos apenas um número, o AutoCadirá tratar esse número como o raio e, mesmo que lhe tentemos fornecer outronúmero para o ângulo, irá assumir que o ângulo é zero.

Uma vez que, na maior parte dos casos, os raios e ângulos serão parâme-tros das nossas funções e ainda que, do ponto de vista matemático, é preferíveltrabalhar em radianos, é conveniente definir uma função que, a partir do raio eângulo em radianos, produz a string apropriada com o ângulo em graus. Paraisso, é conveniente usarmos a função strcat para concatenarmos as strings

53

Figura 11: Triângulos, quadrados e pentágonos sobrepostos com diferentesângulos de rotação.

parciais que serão produzidas através da função rtos que converte um nú-mero numa string. Assim, temos:

(defun raio&angulo (raio angulo)(strcat "@"

(rtos raio)"<"(rtos (graus<-radianos angulo))))

Usando esta função é agora trivial criarmos polígonos com diferentes ân-gulos de rotação. Por exemplo, a seguinte sequência de comandos produz aimagem representada na Figura 11:

(command "_.polygon" 3(xy 0 0) "_Inscribed" (raio&angulo 1 0))

(command "_.polygon" 3(xy 0 0) "_Inscribed" (raio&angulo 1 (/ pi 3)))

(command "_.polygon" 4(xy 3 0) "_Inscribed" (raio&angulo 1 0))

(command "_.polygon" 4(xy 3 0) "_Inscribed" (raio&angulo 1 (/ pi 4)))

(command "_.polygon" 5(xy 6 0) "_Inscribed" (raio&angulo 1 0))

(command "_.polygon" 5(xy 6 0) "_Inscribed" (raio&angulo 1 (/ pi 5)))

5.15 Efeitos Secundários

Vimos anteriormente que qualquer expressão Lisp tem um valor. No entanto,aquilo que se pretende de uma expressão que use a função command não ésaber qual o seu valor mas sim qual o efeito que é produzido num determinadodesenho. De facto, a execução de um comando AutoCad produz, em geral,uma alteração do desenho actual, sendo irrelevante o seu valor.

Este comportamento da função command é fundamentalmente diferentedo comportamento das funções que vimos até agora pois, anteriormente, asfunções eram usadas para computar algo, i.e., para produzir um valor a par-tir da sua invocação com determinados argumentos e, agora, não é o valorque resulta da invocação do comando que interessa mas sim o efeito secundário(também chamado efeito colateral) que interessa.

54

Contudo, mesmo no caso em que apenas nos interessa o efeito secundário,é necessário continuar a respeitar a regra de que, em Lisp, qualquer expressãotem um valor e, por isso, também uma invocação de função tem de produ-zir um valor como resultado. É por este motivo que a invocação da funçãocommand devolve sempre nil como resultado. Obviamente que qualquer ou-tro valor serviria (pois não é suposto ser usado) mas convenciona-se que noscasos em que não há um valor mais relevante a devolver deve-se devolver nil(que, em Latim, significa nada).25

Um dos aspectos importantes da utilização de efeitos secundários está napossibilidade da sua composição. A composição de efeitos secundários faz-seatravés da sua sequenciação, i.e., da realização sequencial dos vários efeitos. Nasecção seguinte iremos ver um exemplo da composição de efeitos secundários.

Exercício 5.15.1 Considere a seguinte interacção com o AutoCad para a criação de um troncode cone onde apresentamos a negrito os dados fornecidos pelo utilizador:

Command: coneSpecify center point of base or [3P/2P/Ttr/Elliptical]: 1,1,1Specify base radius or [Diameter] <5.0000>: 3Specify height or [2Point/Axis endpoint/Top radius] <10.0000>: TopSpecify top radius <0.0000>: 2Specify height or [2Point/Axis endpoint] <10.0000>: 4

Tendo em conta a interacção anterior, defina uma função Auto Lisp denominada tronco-conecujos parâmetros são o ponto P do centro da base do sólido, a altura h do sólido e os raios rb ert da base e do topo do sólido e que cria o tronco de cone correspondente em AutoCad.

5.16 A Ordem Dórica

Na Figura 12 apresentamos uma imagem do templo grego de Segesta. Estetemplo, que nunca chegou a ser acabado, foi construído no século quinto an-tes de Cristo e representa um excelente exemplo da Ordem Dórica, a mais an-tiga das três ordens da arquitectura Grega clássica. Nesta ordem, uma colunacaracteriza-se por ter um fuste, um coxim e um ábaco. O ábaco tem a formade uma placa quadrada que assenta sobre o coxim, o coxim assemelha-se a umtronco de cone invertido e assenta sobre o fuste, e o fuste assemelha-se a um

25Em tutoriais de Auto Lisp aparece por vezes a sugestão de terminar a definição de funçõesque apenas realizam efeitos secundários com a expressão de escrita (princ). De facto, quandoinvocada desta forma, a função princ não só não escreve nada como aparenta não devolvernada:

_$ (princ)_$

No entanto, na realidade, a expressão anterior devolveu, de facto, um valor: um símbolo cujarepresentação externa não é escrita no terceiro passo do ciclo read-eval-print. Embora invisível,este símbolo especial não deixa de ser um valor e pode ser usado como qualquer outro va-lor, embora a sua “invisibilidade” possa provocar resultados possivelmente menos expectáveis,como a seguinte comparação mostra:

_$ (cons 1 2)(1 . 2)_$ (cons (princ) (princ))( . )

55

Figura 12: O Templo Grego de Segesta, exemplificando alguns aspectos daOrdem Dórica. Este templo nunca chegou a ser terminado, sendo visível, porexemplo, a falta das caneluras nas colunas. Fotografia de Enzo De Martino.

tronco de cone com vinte caneluras em seu redor. Estas caneluras assemelham-se a uns canais semi-circulares escavados ao longo da coluna.26 Quando os Ro-manos copiaram a Ordem Dórica introduziram-lhe um conjunto de alterações,em particular, nas caneluras que, muitas vezes, são simplesmente eliminadas.

Nesta secção vamos esquematizar o desenho de uma coluna Dórica (semcaneluras). Do mesmo modo que uma coluna Dórica se pode decompor nosseus componentes fundamentais—o fuste, o coxim e o ábaco—também o dese-nho da coluna se poderá decompor no desenho dos seus componentes. Assim,vamos definir funções para desenhar o fuste, o coxim e o ábaco. A Figura 13apresenta um modelo de referência. Comecemos por definir uma função parao desenho do fuste:

(defun fuste ()(command "_.line"

(xy -0.8 10)(xy -1 0)(xy 1 0)(xy 0.8 10)(xy -0.8 10)""))

Neste exemplo, a função command executa o comando AutoCad line que,dada uma sequência de pontos, constrói uma linha poligonal com vértices nes-ses pontos. Para se indicar o fim da sequência de pontos usa-se uma string

26Estas colunas apresentam ainda uma deformação intencional denominada entasis. A entasisconsiste em dar uma ligeira curvatura à coluna e, segundo alguns autores, destina-se a corrigiruma ilusão de óptica que faz as colunas direitas parecerem encurvadas.

56

(0, 0)x

y

(−0.8, 10)

(−1, 0) (1, 0)

(0.8, 10)

(−1, 10.5) (1, 10.5)

(−1, 11) (1, 11)

Figura 13: Uma coluna Dórica de referência.

vazia. Naturalmente, a invocação da função fuste terá, como efeito secundá-rio, a criação do fuste da coluna na área de desenho do AutoCad. Uma outrapossibilidade, eventualmente mais correcta, seria pedir ao AutoCad a criaçãode uma linha fechada, algo que podemos fazer com a opção "close" no lugardo último ponto, i.e.:

(defun fuste ()(command "_.line"

(xy -0.8 10)(xy -1 0)(xy 1 0)(xy 0.8 10)"_close"))

Note-se que o resultado do comando line é a criação de um conjuntode segmentos de recta. Cada um destes segmentos de recta é uma entidadeindividual que pode ser seleccionada e modificada independentemente dosrestantes. Para o caso de não pretendermos esse tratamento independente,o AutoCad disponibiliza polilinhas (também conhecidas por plines). Estas sãocriadas pelo comando pline que, neste contexto, difere do comando line nofacto de ter como resultado a criação de uma única entidade composta pelosvários segmentos.27

Para completar a figura, é ainda necessário definir uma função para o co-xim e outra para o ábaco. No caso do coxim, o raciocínio é semelhante:

(defun coxim ()(command "_.line"

(xy -0.8 10)(xy -1 10.5)(xy 1 10.5)

27Outras diferenças incluem o facto de as plines poderem ter espessura e estarem limitadas aum plano.

57

Figura 14: Uma coluna dórica desenhada pelo AutoCad.

(xy 0.8 10)"_close"))

No caso do ábaco, podemos empregar uma abordagem idêntica ou po-demos explorar outro comando do AutoCad ainda mais simples destinado àconstrução de rectângulos. Este comando apenas necessita de dois pontos paradefinir completamente o rectângulo:

(defun abaco ()(command "_.rectangle"

(xy -1 10.5)(xy 1 11)))

Finalmente, vamos definir uma função que desenha as três partes da co-luna:

(defun coluna ()(fuste)(coxim)(abaco))

Repare-se, na função coluna, que ela invoca sequencialmente as funçõesfuste, coxim e, finalmente, abaco. Para percebermos o funcionamento dafunção coluna é importante saber que quando invocamos uma função quepossui uma sequência de expressões, o Lisp avalia sequencialmente cada umadas expressões, descartando o seu valor, até chegar à última cujo valor é usadocomo valor final da função. O facto de se descartarem todos os valores ex-cepto o último mostra que, numa sequenciação de expressões, o que importaé o efeito secundário da sua avaliação. A sequenciação é o exemplo mais sim-ples de uma estrutura de controle. Em termos muito grosseiros, uma estruturade controle é um mecanismo das linguagens de programação que indica aocomputador a ordem pela qual pretendemos que ele execute o programa.

A Figura 14 mostra o resultado da invocação da função coluna.

58

rt

rb

a

P = (x, y)

P4 = (x− rb, y)

P1 = (x− rt, y + a) P2 = (x+ rt, y + a)

P3 = (x+ rb, y)

Figura 15: Esquema do desenho do fuste de uma coluna.

5.17 Parameterização de Figuras Geométricas

Infelizmente, a coluna que criámos na secção anterior tem todas as suas di-mensões fixas, pelo que será difícil encontrarmos outras situações em que pos-samos reutilizar a função que definimos. Naturalmente, esta função seria maisútil se a criação da coluna fosse parameterizável, i.e., se a criação dependessedos vários parâmetros que caracterizam a coluna como, por exemplo, as coor-denadas da base da coluna, a altura do fuste, do coxim e do ábaco, os raios dabase e do topo do fuste, etc.

Para se compreender a parameterização destas funções vamos começar porconsiderar o fuste representado esquematicamente na Figura 15.

O primeiro passo para parameterizarmos um desenho geométrico consistena identificação dos parâmetros relevantes. No caso do fuste, um dos parâ-metros óbvios é a localização espacial desse fuste, i.e., as coordenadas de umponto de referência em relação ao qual fazemos o desenho do fuste. Assim,comecemos por imaginar que o fuste irá ser colocado com o centro da basenum imaginário ponto P de coordenadas arbitrárias (x, y). Para além desteparâmetro, temos ainda de conhecer a altura do fuste a e os raios da base rb edo topo rt do fuste.

Para mais facilmente idealizarmos um processo de desenho, é convenienteassinalarmos no esquema alguns pontos de referência adicionais. No caso dofuste, uma vez que o seu desenho é, essencialmente, um trapézio, basta-nosidealizar o desenho deste trapézio através de uma sucessão de linhas rectasdispostas ao longo de uma sequência de pontos P1, P2, P3 e P4, pontos essesque conseguimos calcular facilmente a partir do ponto P .

Desta forma, estamos em condições de definir a função que desenha o

59

rb

rt

aP = (x, y)

P1 = (x− rb, y)

P2 = (x− rt, y + a) P3 = (x+ rt, y + a)

P4 = (x+ rb, y)

Figura 16: Esquema do desenho do coxim de uma coluna.

l

aP = (x, y)

P1 = (x− l2 , y)

P2 = (x+ l2 , y + a)

Figura 17: Esquema do desenho do ábaco de uma coluna.

fuste. Para tornar mais claro o programa, vamos empregar os nomes a-fuste,r-base e r-topo para caracterizar a altura a, o raio da base rb e o raio do toport, respectivamente. A definição fica então:

(defun fuste (p a-fuste r-base r-topo)(command "_.line"

(+xy p (- r-topo) a-fuste)(+xy p (- r-base) 0)(+xy p (+ r-base) 0)(+xy p (+ r-topo) a-fuste)"_close"))

Em seguida, temos de especificar o desenho do coxim. Mais uma vez,convém pensarmos num esquema geométrico, tal como apresentamos na Fi-gura 16.

Tal como no caso do fuste, a partir de um ponto P correspondente ao centroda base do coxim, podemos computar as coordenadas dos pontos que estãonas extremidades dos segmentos de recta que delimitam o desenho do coxim.Usando estes pontos, a definição da função fica com a seguinte forma:

(defun coxim (p a-coxim r-base r-topo)(command "_.line"

(+xy p (- r-base) 0)(+xy p (- r-topo) a-coxim)(+xy p (+ r-topo) a-coxim)(+xy p (+ r-base) 0)"_close"))

Terminado o fuste e o coxim, é preciso definir o desenho do ábaco. Paraisso, fazemos um novo esquema que apresentamos na Figura 17.

Mais uma vez, vamos considerar como ponto de partida o ponto P no cen-tro da base do ábaco. A partir deste ponto, podemos facilmente calcular ospontos P1 e P2 que constituem os dois extremos do rectângulo que representao alçado do ábaco. Assim, temos:

60

af

ac

aa

la

rbf

rbc

P

Figura 18: A composição do fuste, coxim e ábaco.

(defun abaco (p a-abaco l-abaco)(command "_.rectangle"

(+xy p (/ l-abaco -2.0) 0)(+xy p (/ l-abaco +2.0) a-abaco)))

Finalmente, para desenhar a coluna completa, temos de combinar os dese-nhos do fuste, do coxim e do ábaco. Apenas precisamos de ter em conta que,tal como a Figura 18 demonstra, o raio do topo do fuste coincide coincide como raio da base do coxim e o raio do topo do coxim é metade da largura doábaco. A mesma Figura mostra também que as coordenadas da base do co-xim correspondem a somar a altura do fuste às coordenadas da base do fustee as coordenadas da base do ábaco correspondem a somar a altura do fuste ea altura do coxim às coordenadas da base do fuste.

Tal como fizemos anteriormente, vamos dar nomes mais claros aos parâ-metros da Figura 18. Usando os nomes p, a-fuste, r-base-fuste, a-coxim,r-base-coxim, a-abaco e l-abaco no lugar de, respectivamente, P , af ,rbf , ac, rbc, aa e la, temos:

(defun coluna (pa-fuste r-base-fustea-coxim r-base-coxima-abaco l-abaco)

(fuste p a-fuste r-base-fuste r-base-coxim)(coxim (+xy p 0 a-fuste) a-coxim r-base-coxim (/ l-abaco 2.0))(abaco (+xy p 0 (+ a-fuste a-coxim)) a-abaco l-abaco))

61

Figura 19: Variações de colunas dóricas.

Com base nestas funções, podemos agora facilmente experimentar varia-ções de colunas. As seguintes invocações produzem o desenho apresentadona Figura 19.

(coluna (xy 0 0) 9 0.5 0.4 0.3 0.3 1.0)(coluna (xy 3 0) 7 0.5 0.4 0.6 0.6 1.6)(coluna (xy 6 0) 9 0.7 0.5 0.3 0.2 1.2)(coluna (xy 9 0) 8 0.4 0.3 0.2 0.3 1.0)(coluna (xy 12 0) 5 0.5 0.4 0.3 0.1 1.0)(coluna (xy 15 0) 6 0.8 0.3 0.2 0.4 1.4)

Como é óbvio pela análise desta figura, nem todas as colunas desenhadasobedecem aos cânones da ordem Dórica. Mais à frente iremos ver que modifi-cações serão necessárias para evitar este problema.

5.18 Documentação

Na função coluna, a-fuste é a altura do fuste, r-base-fuste é o raio dabase do fuste, r-topo-fuste é o raio do topo do fuste, a-coxim é a altura docoxim, a-abaco é a altura do ábaco e, finalmente, r-abaco é o raio do ábaco.Uma vez que a função já tem vários parâmetros e o seu significado poderá nãoser óbvio para quem lê a definição da função pela primeira vez, é convenientedocumentar a função. Para isso, a linguagem Lisp providencia uma sintaxeespecial: sempre que surge o carácter ;, a processo de leitura do Lisp ignoratudo o que vem a seguir até ao fim da linha. Isto permite-nos escrever textonos nossos programas sem correr o risco de o Lisp tentar perceber o que lá estáescrito.

Usando documentação, o nosso programa completo para desenhar colunasdóricas fica com o seguinte aspecto:28

28O exemplo destina-se a mostrar as diferentes formas de documentação usadas em Lisp enão a mostrar um exemplo típico de programa documentado. Na verdade, o programa é tãosimples que não deveria necessitar de tanta documentação.

62

;;;;Desenho de colunas doricas

;;;O desenho de uma coluna dorica divide-se no desenho do;;;fuste, do coxim e do abaco. A cada uma destas partes;;;corresponde uma funcao independente.

;Desenha o fuste de uma coluna dorica.;p: coordenadas do centro da base da coluna,;a-fuste: altura do fuste,;r-base: raio da base do fuste,;r-topo: raio do topo do fuste.(defun fuste (p a-fuste r-base r-topo)

(command "_.line" ;a criacao de linhas(+xy p (- r-topo) a-fuste) ;com a funcao command(+xy p (- r-base) 0) ;tem de ser terminada(+xy p (+ r-base) 0) ;com a opcao "close"(+xy p (+ r-topo) a-fuste) ;para fechar a figura"close"))

;Desenha o coxim de uma coluna dorica.;p: coordenadas do centro da base do coxim,;a-coxim: altura do coxim,;r-base: raio da base do coxim,;r-topo: raio do topo do coxim.(defun coxim (p a-coxim r-base r-topo)

(command "_.line"(+xy p (- r-base) 0)(+xy p (- r-topo) a-coxim)(+xy p (+ r-topo) a-coxim)(+xy p (+ r-base) 0)"close")) ;para fechar a figura

;Desenha o abaco de uma coluna dorica.;p: coordenadas do centro da base da coluna,;a-abaco: altura do abaco,;l-abaco: largura do abaco.(defun abaco (p a-abaco l-abaco)

(command "_.rectangle"(+xy p (/ l-abaco -2.0) 0)(+xy p (/ l-abaco +2.0) a-abaco)))

;Desenha uma coluna dorica composta por fuste, coxim e abaco.;p: coordenadas do centro da base da coluna,;a-fuste: altura do fuste,;r-base-fuste: raio da base do fuste,;r-base-coxim: raio da base do coxim = raio do topo do fuste,;a-coxim: altura do coxim,;a-abaco: altura do abaco,;l-abaco: largura do abaco = 2*raio do topo do coxim.(defun coluna (p

a-fuste r-base-fustea-coxim r-base-coxima-abaco l-abaco)

;;desenhamos o fuste com a base em p(fuste p a-fuste r-base-fuste r-base-coxim);;colocamos o coxim por cima do fuste(coxim (+y p a-fuste) a-coxim r-base-coxim (/ l-abaco 2.0));;e o abaco por cima do coxim(abaco (+y p (+ a-fuste a-coxim)) a-abaco l-abaco))

Desta forma, quem for ler o nosso programa fica com uma ideia muito maisclara do que cada função faz, sem sequer precisar de ir estudar o corpo das fun-ções. Como se vê pelo exemplo anterior, é pragmática usual em Lisp usar umdiferente número de caracteres “;” para indicar a relevância do comentário:

63

;;;; Devem começar na margem esquerda e servem para dividir o programaem secções e dar um título a cada secção.

;;; Devem começar na margem esquerda e servem para fazer comentáriosgerais ao programa que aparece em seguida. Não se devem usar no in-terior das funções.

;; Devem estar alinhadas com a parte do programa a que se vão aplicar, queaparece imediatemente em baixo.

; Devem aparecer alinhados numa mesma coluna à direita e comentam aparte do programa imediatamente à esquerda.

É importante que nos habituemos a documentar as nossas definições masconvém salientar que a documentação em excesso também tem desvantagens:

• O código Lisp deve ser suficientemente claro para que um ser humanoo consiga perceber. É sempre preferível perder mais tempo a tornar ocódigo claro do que a escrever documentação que o explique.

• Documentação que não está de acordo com o programa é pior que nãoter documentação.

• É frequente termos de modificar os nossos programas para os adaptara novos fins. Quanto mais documentação existir, mais documentação énecessário alterar para a pôr de acordo com as alterações que tivermosfeito ao programa.

Por estes motivos, devemos esforçar-nos por escrever o código mais claroque nos for possível e, ao mesmo tempo, providenciar documentação sucintae útil: a documentação não deve dizer aquilo que é óbvio a partir da leitura doprograma.

Exercício 5.18.1 Considere o desenho de uma seta com origem no ponto P , comprimento ρ,inclinação α, ângulo de abertura β e comprimento da “farpa” σ, tal como se representa emseguida:

αP

ρ σβ

Defina uma função denominada seta que, a partir dos parâmetros P , ρ, α, σ e β, constróia seta correspondente.

Exercício 5.18.2 Com base na solução do exercício anterior, defina uma função que, dados oponto P , a distância ρ e o ângulo α, desenha “o norte” tal como se apresenta no esquema embaixo:

64

N

O desenho deve ainda obedecer às seguintes proporções:• O ângulo β de abertura da seta é de 45 ◦.• O comprimento σ da “farpa” é de ρ

2.

• O centro da letra “N” deverá ser posicionado a uma distância de ρ10

da extremidade daseta segundo a direcção da seta.

• O tamanho da letra “N” deverá ser metade da distância ρ.

Exercício 5.18.3 Usando a função seta, defina uma nova função denominada seta-de-paraque, dados dois pontos, cria uma seta que vai do primeiro para o segundo ponto. As farpas daseta deverão ter comprimento unitário e ângulo π

8.

Exercício 5.18.4 Considere o desenho de uma habitação composta apenas por divisões rectan-gulares. Pretende-se que defina a função divisao-rectangular que recebe como parâme-tros a posição do canto inferior esquerdo da divisão, o comprimento e a largura da divisão eum texto a descrever a função dessa divisão na habitação. Com esses valores a função deveráconstruir o rectângulo correspondente e deve colocar no interior desse rectângulo duas linhasde texto, a primeira com a função da divisão e a segunda com a área da divisão. Por exemplo,a sequência de invocações

(divisao-rectangular (xy 0 0) 4 3 "cozinha")(divisao-rectangular (xy 4 0) 2 3 "despensa")(divisao-rectangular (xy 6 0) 5 3 "quarto")(divisao-rectangular (xy 0 5) 5 4 "sala")(divisao-rectangular (xy 5 5) 3 4 "i.s.")(divisao-rectangular (xy 8 3) 3 6 "quarto")

produz, como resultado, a habitação que se apresenta de seguida:

5.19 Depuração

Como sabemos, errare humanum est. O erro faz parte do nosso dia-a-dia e, porisso, em geral, sabemos lidar com ele. Já o mesmo não se passa com as lingua-gens de programação. Qualquer erro num programa tem, como consequência,que o programa tem um comportamento diferente daquele que era esperado.

65

Uma vez que é fácil cometer erros, deve também ser fácil detectá-los ecorrigi-los. À actividade de detecção e correcção de erros denomina-se depura-ção. Diferentes linguagens de programação providenciam diferentes mecanis-mos para essa actividade. Neste domínio, como iremos ver, o AutoCad estáparticularmente bem apetrechado.

Em termos gerais, os erros num programa podem classificar-se em errossintáticos e erros semânticos.

5.19.1 Erros Sintáticos

Os erros sintáticos ocorrem quando escrevemos frases que não obedecem àgramática da linguagem. Como exemplo prático, imaginemos que pretendi-amos definir uma função que criava um única coluna dórica, que iremos de-signar de coluna standard e que tem sempre as mesmas dimensões, não neces-sitando de quaisquer outros parâmetros. Uma possibilidade para a definiçãodesta função será:

(defun coluna-standard(coluna (xy 0 0) 9 0.5 0.4 0.3 0.3 0.5))

No entanto, se avaliarmos aquela definição, o Auto Lisp irá apresentar umerro, avisando-nos de que algo está errado:29

; error: bad DEFUN syntax:(COLUNA-STANDARD (COLUNA (XY 0 0) 9 0.5 0.4 0.3 0.3 0.5))

O erro de que o Auto Lisp nos está a avisar é de que a forma defun quelhe demos para avaliar não obedece à sintaxe exigida para essa forma e, defacto, uma observação atenta da forma anterior mostra que não seguimos asintaxe exigida para uma definição e que, tal como discutimos na secção 4.4,era a seguinte:

(defun nome (parâmetro1 ... parâmetron)corpo)

O nosso erro é agora óbvio: esquecemo-nos da lista de parâmetros. Se afunção não tem parâmetros a lista de parâmetros é vazia mas tem de estar lána mesma. Uma vez que não estava, o Auto Lisp detecta e reporta um errosintático, uma “frase” que não obedece à sintaxe da linguagem.

Há vários outros tipos de erros sintáticos que o Auto Lisp é capaz de de-tectar e que serão apresentados à medida que os formos discutindo. O im-portante, no entanto, não é saber quais são os erros sintáticos detectáveis peloAuto Lisp, mas antes saber que o Auto Lisp é capaz de verificar as expres-sões que escrevemos e fazer a detecção de erros sintáticos antes mesmo de asavaliar.

Para isso, o Visual Lisp disponibiliza na sua interface operações que fa-zem essa verificação para uma selecção ou para todo o ficheiro actual. Na

29Nem todos os dialectos e interpretadores de Lisp possuem exactamente este comporta-mento mas, variações à parte, os conceitos são os mesmos.

66

versão Inglesa do AutoCad, essas operações denominam-se “Check Selection”ou “Check Text in Editor” e estão disponíveis no menu “Tools.” Como con-sequência da invocação destas operações, o AutoCad analiza a selecção ou oficheiro actual e escreve, numa janela à parte, todos os erros sintáticos encon-trados. Nesse janela, se clicarmos duas vezes sobre uma mensagem de erro,somos conduzidos ao local do nosso programa onde o erro ocorre.

5.19.2 Erros Semânticos

Os erros semânticos são muito diferentes dos sintáticos. Um erro semânticonão é um erro na escrita de uma “frase” da linguagem mas sim um erro nosignificado dessa frase. Dito de outra forma, um erro semântico ocorre quandoescrevemos uma frase que julgamos ter um significado e, na verdade, ela temoutro.

Em geral, os erros semânticos apenas são detectáveis durante a invoca-ção das funções que os contêm. Parte dos erros semânticos é detectável peloavaliador de Lisp mas há inúmeros erros cuja detecção só pode ser feita pelopróprio programador.

Como exemplo de erro semântico consideremos uma operação sem signi-ficado como seja a soma de um número com uma string:

_$ (+ 1 "dois"); error: bad argument type: numberp: "dois"

Como se pode ver, o erro é explicado na mensagem que indica que o se-gundo argumento devia ser um número. Neste exemplo, o erro é suficien-temente óbvio para o conseguirmos detectar imediatamente. No entanto, nocaso de programas mais complexos, isso já poderá não ser assim.

Na continuação do exemplo que apresentámos na discussão sobre errossintáticos, consideremos a seguinte modificação à função coluna-standardque corrige a falta de parâmetros mas que introduz um outro erro:30

(defun coluna-standard ()(coluna (xy O 0) 9 0.5 0.4 0.3 0.3 0.5))

Do ponto de vista sintático, a função está correcta. No entanto, quando ainvocamos surge um erro:

_$ (coluna-standard); error: bad argument type: numberp: nil

Como podemos ver pela resposta, o Auto Lisp protesta que um dos argu-mentos devia ser um número mas, em lugar disso, era nil. Uma vez que afunção coluna-standard não tem quaisquer argumentos, a mensagem deerro poderá ser difícil de compreender. No entanto, ela torna-se compreen-sível quando pensamos que o erro poderá não ter ocorrido na invocação dafunção coluna-standard mas sim em qualquer função que tenha sido invo-cada directa ou indirectamente por esta.

30Consegue detectá-lo?

67

Para se perceber melhor onde está o erro, o Visual Lisp providencia algu-mas operações extremamente úteis. Uma delas dá pelo nome de “Error Trace,”está disponível no menu “View” e destina-se a mostra a “cascata” de invoca-ções que acabou por provocar o erro.31 Quando executamos essa operação, oVisual Lisp apresenta-nos, numa pequena janela, a seguinte informação:

<1> :ERROR-BREAK[2] (+ nil -0.3)[3] (+XY (nil 0) -0.3 9)[4] (FUSTE (nil 0) 9 0.5 0.3)[5] (COLUNA (nil 0) 9 0.5 0.4 0.3 0.3 0.5)[6] (COLUNA-STANDARD)...

Na informação acima, as reticências representam outras invocações quenão são relevantes para o nosso problema e que correspondem às funçõesAuto Lisp cuja execução antecede a das nossas funções.32 A listagem apre-sentada pelo Visual Lisp mostra, por ordem inversa, as invocações de funçõesque provocaram o erro. Para cada linha, o Visual Lisp disponibiliza um menucontextual que, entre outras operações, permite visualizarmos imediatamenteno editor qual é a linha do programa em questão.

A leitura da listagem do error trace diz-nos que o erro foi provocado pelatentativa de somarmos nil a um número. Essa tentativa foi feita pela função+xy que foi invocada pela função fuste que foi invocada pela função colunaque, finalmente, foi invocada pela função coluna-standard. Como pode-mos ver, à frente do nome de cada função, aparecem os argumentos com quea função foi invocada. Estes argumentos mostram que o erro de que o AutoLisp se queixa na soma, na realidade, foi provocado muito antes disso, logo nainvocação da função coluna. De facto, é visível que essa função é invocadacom um ponto cuja coordenada x é nil e não um número como devia ser.Esta é a pista que nos permite identificar o erro: ele tem de estar na própriafunção coluna-standard e, mais especificamente, na expressão que cria ascoordenadas que são passadas como argumento da função coluna. Se ob-servarmos cuidadosamente essa expressão (que assinalámos a negrito) vemosque, de facto, está lá um muito subtil erro: o primeiro argumento da funçãoxy não é zero mas sim a letra “ó” maiúsculo.

(defun coluna-standard ()(coluna (xy O 0) 9 0.5 0.4 0.3 0.3 0.5))

Acontece que, em Auto Lisp, qualquer nome que não tenha sido previa-mente definido tem o valor nil e, como o nome constituído pela letra O nãoestá definido, a avaliação do primeiro argumento da função xy é, na verdade,nil. Esta função, como se limita a fazer uma lista com os seus argumentos,cria uma lista cujo primeiro elemento é nil e cujo segundo elemento é zero. Alista resultante é assim passada de função em função até chegarmos à função+xy que, ao tentar fazer a soma, acaba por provocar o erro.

31A expressão error trace pode ser traduzida para “Rastreio de erros.”32Essas funções que foram invocadas antes das nossas revelam que, na verdade, parte da

funcionalidade do Visual Lisp está ela própria implementada em Auto Lisp.

68

Esta “sessão” de depuração mostra o procedimento habitual para detecçãode erros. A partir do momento em que o erro é identificado, é geralmentefácil corrigi-lo mas convém ter presente que o processo de identificação deerros pode ser moroso e frustrante. É também um facto que a experiênciade detecção de erros é extremamente útil e que, por isso, é expectável queenquanto essa experiência for reduzida, o processo de detecção seja mais lento.

6 Modelação Tridimensional

Como vimos na secção anterior, o AutoCad disponibiliza um conjunto de ope-rações de desenho (linhas, rectângulos, círculos, etc) que nos permitem facil-mente criar representações bidimensionais de objectos, como sejam plantas,alçados e cortes.

Embora até este momento apenas tenhamos utilizado as capacidades dedesenho bi-dimensional do AutoCad, é possível irmos mais longe, entrandono que se denomina por modelação tridimensional. Esta modelação visa a repre-sentação gráfica de linhas, superfícies e volumes no espaço tridimensional.

Nesta secção iremos estudar as operações do AutoCad que nos permitemmodelar directamente os objectos tridimensionais.

6.1 Sólidos Tridimensionais Pré-Definidos

As versões mais recentes do AutoCad disponibilizam um conjunto de ope-rações pré-definidas que constroem um sólido a partir da especificação dassuas coordenadas tridimensionais. Embora as operações pré-definidas apenaspermitam construir um conjunto muito limitado de sólidos, esse conjunto ésuficiente para a elaboração de modelos sofisticados.

As operações pré-definidas para criação de sólidos permitem construir pa-ralelipípedos (comando box), cunhas (comando wedge), cilindros (comandocylinder), cones (comando cone), esferas (comando sphere), toros (co-mando torus) e pirâmides (comando pyramid). Os comandos cone e pyramidpermitem ainda a construção de troncos de cone e troncos de pirâmide atravésda utilização do parâmetro "Top". Cada um destes comandos aceita váriasopções que permitem construir sólidos de diferentes maneiras.33 A Figura 20mostra um conjunto de sólidos construídos pela avaliação das seguintes ex-pressões:34

(command "_.box" (xyz 1 1 1) (xyz 3 4 5))(command "_.wedge" (xyz 4 2 0) (xyz 6 6 4))(command "_.cone" (xyz 6 0 0) 1

"_Axis" (xyz 8 1 5))(command "_.cone" (xyz 11 1 0) 2 "_Top" 1

"_Axis" (xyz 10 0 5))

33Para se conhecer as especificidades de cada comando recomenda-se a consulta da docu-mentação que acompanha o AutoCad.

34A construção de uma pirâmide exige a especificação simultânea do raio e ângulo da base.Tal como explicado na secção 5.14, a função raio&angulo constrói essa especificação a partirdos valores do raio e do ângulo.

69

Figura 20: Sólidos primitivos em AutoCad.

(command "_.sphere" (xyz 8 4 5) 2)(command "_.cylinder" (xyz 8 7 0) 1 "_Axis" (xyz 6 8 7))(command "_.pyramid" "_Sides" 5 (xyz 1 6 1) (raio&angulo 1 0)

"_Axis" (xyz 2 7 9))(command "_.torus" (xyz 13 6 5) 2 1)

Note-se que alguns dos sólidos, nomeadamente o paralelipípedo e a cunhasó podem ser construídos com a base paralela ao plano XY . Esta não é umaverdadeira limitação do AutoCad pois é possível alterar a orientação do planoXY através da manipulação do sistema de coordenadas UCS–user coordinatesystem.

A partir dos comandos disponibilizados pelo AutoCad é possível definirfunções Auto Lisp que simplifiquem a sua utilização. Embora o AutoCad per-mita vários modos diferentes de se criar um sólido, esses modos estão maisorientados para facilitar a vida ao utilizador do AutoCad do que propriamentepara o programador de Auto Lisp. Para este último, é preferível dispor de umafunção que, a partir de um conjunto de parâmetros simples, invoca o comandocorrespondente em AutoCad, especificando automaticamente as opções ade-quadas para a utilização desses parâmetros.

Para vermos um exemplo, consideremos a criação de um cilindro. Emborao AutoCad permita construir o cilindro de várias maneiras diferentes, cadauma empregando diferentes parâmetros, podemos considerar que os únicosparâmetros relevantes são o centro da base, o raio da base e o centro do topo.Estes parâmetros são suficientes para especificar completamente um cilindro,permitindo orientá-lo no espaço como muito bem entendermos. Assim, para oprogramador de Auto Lisp, basta-lhe invocar o comando AutoCad cylinder

70

Figura 21: Uma cruz papal.

seleccionando o modo de construção que facilita o uso deste parâmetros. Éprecisamente isso que faz a seguinte função:

(defun cilindro (centro-base raio centro-topo)(command "_cylinder" centro-base raio "_Axis" centro-topo))

Usando esta função é agora mais simples definir outras estruturas maiscomplexas. Por exemplo, uma cruz papal define-se pela união de três cilindroshorizontais de comprimento progressivamente decrescente dispostos ao longode um cilindro vertical, tal como se pode ver na Figura 21. É de salientar que oscilindros têm todos o mesmo raio e que o seu comprimento e posicionamentoé função desse raio. Em termos de proporção, o cilindro vertical da cruz papaltem um comprimento igual a 20 raios, enquanto que os cilindros horizontaispossuem comprimentos iguais a 14, 10 e 6 raios e o seu eixo está posicionadoa uma altura igual a 9, 13 e 17 raios. Estas proporções são implementadas pelaseguinte função:

(defun cruz-papal (p raio)(cilindro p

raio(+xyz p 0 0 (* 20 raio)))

(cilindro (+xyz p (* -7 raio) 0 (* 9 raio))raio(+xyz p (* +7 raio) 0 (* 9 raio)))

(cilindro (+xyz p (* -5 raio) 0 (* 13 raio))raio(+xyz p (* +5 raio) 0 (* 13 raio)))

(cilindro (+xyz p (* -3 raio) 0 (* 17 raio))

71

Figura 22: Alçado frontal, lateral e planta da cruz papal representada na Fi-gura 21.

raio(+xyz p (* +3 raio) 0 (* 17 raio))))

Uma das vantagens das representações tri-dimensionais está no facto deelas conterem toda a informação necessária para a geração automática de vis-tas bi-dimensionais, incluindo as tradicionais projecções ortogonais. Para isso,o AutoCad permite diferentes abordagens, desde a simples utilização de múl-tiplas vistas (através do comando mview), cada uma com uma perspectiva di-ferente, até a criação automática de projecções e cortes (comandos solview,soldraw, flatshot e sectionplane, entre outros). A Figura 22 mostra oalçado frontal, o alçado lateral e a planta produzidos automaticamente pelocomando flatshot a partir do modelo apresentado na Figura 21.

Exercício 6.1.1 Defina uma função denominada prisma que cria um sólido prismático regular.A função deverá receber o número de lados do prisma, as coordenadas tridimensionais docentro da base do prisma, a distância do centro da base a cada vértice, o ângulo de rotação dabase do prisma e as coordenadas tridimensionais do centro do topo do prisma.

A título de exemplo, considere as expressões(prisma 3 (xyz 0 0 0) 0.4 0 (xyz 0 0 5))(prisma 5 (xyz -2 0 0) 0.4 0 (xyz -1 1 5))(prisma 4 (xyz 0 2 0) 0.4 0 (xyz 1 1 5))(prisma 6 (xyz 2 0 0) 0.4 0 (xyz 1 -1 5))(prisma 7 (xyz 0 -2 0) 0.4 0 (xyz -1 -1 5))

cuja avaliação produz a imagem seguinte:

72

6.2 Modelação de Colunas Dóricas

A modelação tri-dimensional tem a virtude de nos permitir criar entidadesgeométricas muito mais realistas do que meros aglomerados de linhas a re-presentarem vistas dessas entidades. A título de exemplo, reconsideremos acoluna Dórica que apresentámos na secção 5.16. Nessa secção desenvolvemosum conjunto de funções cuja invocação criava uma vista frontal dos compo-nentes da coluna Dórica. Apesar dessas vistas serem úteis, é ainda mais útilpoder modelar directamente a coluna como uma entidade tri-dimensional.

Nesta secção vamos empregar algumas das operações mais relevantes paraa modelação tri-dimensional de colunas, em particular, a criação de troncosde cone para modelar o fuste e o coxim e a criação de paralelipípedos paramodelar o ábaco.

Anteriormente, as nossas “colunas” estavam dispostas no plano x-y, comas colunas a “crescer” ao longo do eixo dos y. Agora, será apenas a base dascolunas que ficará assente no plano x-y: o corpo das colunas irá desenvolver-seao longo do eixo dos z. Embora fosse trivial empregar outro arranjo dos eixosdo sistema de coordenadas, este é aquele que é mais próximo da realidade.

À semelhança de inúmeras outras operações do AutoCad, cada uma dasoperações de modelação de sólidos do AutoCad permite vários modos dife-rentes de invocação. No caso da operação de modelação de troncos de cone—cone—o modo que nos é mais conveniente é aquele em que a operação recebeas coordenadas do centro da base do cone e o raio dessa base e, de seguida, es-pecificamos o raio do topo do cone (com a opção Top radius) e, finalmente,a altura do tronco.

Tendo isto em conta, podemos redefinir a operação que constrói o fuste talcomo se segue:

73

(defun fuste (p a-fuste r-base r-topo)(command "_.cone" p r-base

"_t" r-topo a-fuste))

Do mesmo modo, a operação que constrói o coxim ficará com a forma:

(defun coxim (p a-coxim r-base r-topo)(command "_.cone" p r-base

"_t" r-topo a-coxim))

Finalmente, no que diz respeito ao ábaco—o paralelipípedo que é colocadono topo da coluna—temos várias maneiras de o especificarmos. A mais directaconsiste em indicar os dois cantos do paralelipípedo. Uma outra, menos di-recta, consiste em indicar o centro do paralelipípedo seguido do tamanho dosseus lados. Por agora, vamos seguir a mais directa:

(defun abaco (p a-abaco l-abaco)(command "_.box"

(+xyz p (/ l-abaco -2.0) (/ l-abaco -2.0) 0)(+xyz p (/ l-abaco +2.0) (/ l-abaco +2.0) a-abaco)))

Exercício 6.2.1 Implemente a função abaco mas empregando a criação de um paralelipípedocentrado num ponto seguido da especificação do seu comprimento, largura e altura.

Finalmente, falta-nos implementar a função coluna que, à semelhança doque fazia no caso bi-dimensional, invoca sucessivamente as funções fuste,coxim e abaco mas, agora, elevando progressivamente a coordenada z:

(defun coluna (pa-fuste r-base-fustea-coxim r-base-coxima-abaco l-abaco)

(fuste p a-fuste r-base-fuste r-base-coxim)(coxim (+z p a-fuste) a-coxim r-base-coxim (/ l-abaco 2.0))(abaco (+z p (+ a-fuste a-coxim)) a-abaco l-abaco))

Com estas redefinições, podemos agora repetir as colunas que desenhámosna secção 5.17 e que apresentámos na Figura 19, mas, agora, gerando umaimagem tri-dimensional dessas mesmas colunas, tal como apresentamos naFigura 23:

(coluna (xyz 0 0 0) 9 0.5 0.4 0.3 0.3 1.0)(coluna (xyz 3 0 0) 7 0.5 0.4 0.6 0.6 1.6)(coluna (xyz 6 0 0) 9 0.7 0.5 0.3 0.2 1.2)(coluna (xyz 9 0 0) 8 0.4 0.3 0.2 0.3 1.0)(coluna (xyz 12 0 0) 5 0.5 0.4 0.3 0.1 1.0)(coluna (xyz 15 0 0) 6 0.8 0.3 0.2 0.4 1.4)

A partir deste modelo, é agora trivial usarmos as capacidades do AutoCadpara extrair qualquer vista que pretendamos, incluindo perspectivas como aque apresentamos na Figura 24.

74

Figura 23: Modelação tri-dimensional das variações de colunas dóricas.

Figura 24: Perspectiva da modelação tri-dimensional das variações de colunasdóricas.

75

7 Expressões Condicionais

Existem muitas operações cujo resultado depende da realização de um de-terminado teste. Por exemplo, a função matemática |x|—que calcula o valorabsoluto de um número—equivale ao próprio número, se este é positivo, ouequivale ao seu simétrico se for negativo. Esta função terá, portanto de testaro seu argumento e escolher uma de duas alternativas: ou devolve o próprioargumento, ou devolve o seu simétrico.

Estas expressões, cujo valor depende de um ou mais testes a realizar previ-amente, permitindo escolher vias diferentes para a obtenção do resultado, sãodesignadas expressões condicionais.

No caso mais simples de uma expressão condicional, existem apenas duasalternativas a seguir. Isto implica que o teste que é necessário realizar paradeterminar a via de cálculo a seguir deve produzir um de dois valores, em quecada valor designa uma das vias.

Seguindo o mesmo raciocício, uma expressão condicional com mais deduas alternativas deverá implicar um teste com igual número de possíveisresultados. Uma expressão da forma “caso o valor do teste seja 1, 2 ou 3, ovalor da expressão é 10, 20 ou 30, respectivamente” é um exemplo desta ca-tegoria de expressões que existe nalgumas linguagens (Basic, por exemplo).Contudo, estas expressões podem ser facilmente reduzidas à primeira atravésda decomposição da expressão condicional múltipla numa composição de ex-pressões condicionais simples, em que o teste original é também decompostonuma série de testes simples. Assim, poderiamos transformar o exemplo an-terior em: “se o valor do teste é 1, o resultado é 10, caso contrário, se o valor é2, o resultado é 20, caso contrário é 30”.

7.1 Expressões Lógicas

Desta forma, reduzimos todos os testes a expressões cujo valor pode ser ape-nas um de dois, e a expressão condicional assume a forma de “se . . . então. . . caso contrário . . . .” A expressão cujo valor é usado para decidir se devemosusar o ramo “então” ou o ramo “caso contrário” denomina-se expressão lógica ecaracteriza-se por o seu valor ser interpretado como verdade ou falso. Por exem-plo, a expressão lógica (> x y) testa se o valor de x é maior que o valor de y.Se for, a expressão avalia para verdade, caso contrário avalia para falso.

7.2 Valores Lógicos

Algumas linguagens de programação consideram a verdade e o falso comodois elementos de um tipo especial de dados denominado lógico ou booleano.35

Outras linguagens, como o Lisp, entendem que o facto de se considerar umvalor como verdadeiro ou falso não implica necessariamente que o valor tenhade ser de um tipo de dados especial, mas apenas que a expressão condicionalconsidera alguns dos valores como representando o verdadeiro e os restantescomo o falso.

35De George Boole, matemático inglês e inventor da álgebra da verdade e da falsidade.

76

Em Lisp, as expressões condicionais consideram como falso um único va-lor. Esse valor é representado por nil, o mesmo valor que usámos anterior-mente para representar uma lista vazia. Qualquer outro valor diferente de nilé considerado como verdadeiro. Assim, do ponto de vista de uma expressãocondicional, o número 123, por exemplo, é um valor verdadeiro. Contudo,não faz muito sentido para o utilizador humano considerar um número comoverdadeiro ou falso, pelo que se introduziu uma constante na linguagem pararepresentar verdade. Essa constante representa-se por t. A lógica é que se t édiferente de nil e se nil é o único valor que representa a falsidade, então trepresenta necessariamente a verdade.

7.3 Predicados

No caso mais usual, uma expressão lógica é uma invocação de função comdeterminados argumentos. Nesta situação, a função usada como teste é de-nominada predicado e o valor do teste é interpretado como sendo verdadeiroou falso. O predicado é, consequentemente, uma função que devolve apenasverdade ou falso.

Apesar da adopção dos símbolos t e nil, convém alertar que nem todosos predicados devolvem t ou nil exclusivamente. Alguns há que, quandoquerem indicar verdade, devolvem valores diferentes de t (e de nil, obvia-mente).

7.4 Predicados Aritméticos

Os operadores relacionais matemáticos <, >, =, ≤, ≥ e 6= são um dos exemplosmais simples de predicados. Estes operadores comparam números entre si epermitem saber se um número é menor que outro. O seu uso em Lisp segue asregras da notação prefixa e escrevem-se, respectivamente, <, >, =, <=, >= e /=.Eis alguns exemplos:

_$ (> 4 3)t_$ (< 4 3)nil_$ (<= (+ 2 3) (- 6 1))t

7.5 Operadores Lógicos

Para se poder combinar expressões lógicas entre si existem os operadores and,or e not. O and e o or recebem qualquer número de argumentos. O not sórecebe um. O valor das combinações que empregam estes operadores lógicosé determinado do seguinte modo:

• O and avalia os seus argumentos da esquerda para a direita até que umdeles seja falso, devolvendo este valor. Se nenhum for falso o and de-volve verdade.

77

• O or avalia os seus argumentos da esquerda para a direita até que umdeles seja verdade, devolvendo este valor. Se nenhum for verdade o ordevolve falso.

• O not avalia para verdade se o seu argumento for falso e para falso emcaso contrário.

Note-se que embora o significado de falso seja claro pois corresponde ne-cessariamente ao valor nil, o significado de verdade já não é tão claro pois,desde que seja diferente de nil, é considerado verdade.

Exercício 7.5.1 Qual o valor das seguintes expressões?

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

2. (not (or (= 1 2) (= 2 3)))

3. (or (< 1 2) (= 1 2) (> 1 2))

4. (and 1 2 3)

5. (or 1 2 3)

6. (and nil 2 3)

7. (or nil nil 3)

7.6 Predicados com número variável de argumentos

Uma propriedade importante dos predicados aritméticos <, >, =, <=, >= e/= é aceitarem qualquer número de argumentos. No caso em que há maisdo que um argumento, o predicado é aplicado sequencialmente aos paresde argumentos. Assim, (< e1 e2 e3 ... en−1 en) é equivalente a escrever(and (< e1 e2) (< e2 e3) ... (< en−1 en)). Este comportamento é visí-vel nos sequintes exemplos.

_$ (< 1 2 3)T_$ (< 1 2 2)nil

Um caso particular que convém ter em atenção é que embora a expres-são (= e1 e2 ... en) teste se os elementos e1 e2 . . . en são todos iguais,(/= e1 e2 ... en) não testa se os elementos e1 e2 . . . en são todos dife-rentes: uma vez que o teste é aplicado sucessivamente a pares de elementos éperfeitamente possível que existam dois elementos iguais desde que não sejamconsecutivos. Esse comportamento é visível no seguinte exemplo:36

_$ (/= 1 2 3)T_$ (/= 1 2 1)T

36Outros dialectos de Lisp apresentam um comportamento diferente para esta operação.Em Common Lisp, por exemplo, o predicado /= testa se, de facto, os argumentos são todosdiferentes.

78

7.7 Predicados sobre Cadeias de Caracteres

Na verdade, os operadores <, >, =, <=, >= e /= não se limitam a operaremsobre números: eles aceitam também cadeias de caracteres como argumentos,fazendo uma comparação lexicográfica: os argumentos são comparados carác-ter a carácter enquanto forem iguais. Quando são diferentes, a ordem léxico-grafica do primeiro carácter diferente nas duas strings determina o valor lógicoda relação. Se a primeira string “acabar” antes da segunda, considera-se que é“menor,” caso contrário, é “maior.”

_$ (= "pois" "pois")T_$ (= "pois" "poisar")nil_$ (< "pois" "poisar")T_$ (< "abcd" "abce")T

7.8 Predicados sobre Símbolos

Para além de números e strings, o predicado = permite ainda comparar símbo-los.

_$ (= ’pois ’pois)T_$ (= ’pois ’poisar)nil

Nenhum dos outros operadores relacionais é aplicável a símbolos.

7.9 Predicados sobre Listas

Vimos que os dados manipulados pelo Lisp se podiam classificar em atómicosou não-atómicos consoante era impossível, ou não, aplicar-lhes as operaçõesde listas car e cdr.

Para facilitar a identificação das entidades atómicos, a linguagem Lisp dis-ponibiliza uma função denominada atom que só é verdadeira para as entida-des atómicas:

_$ (atom 1)T_$ (atom "dois")T_$ (atom ’quatro)T_$ (atom (cons 1 "dois"))nil_$ (atom (list 1 "dois" 3.0))nil_$ (atom ())T_$ (atom t)

79

T_$ (atom nil)T

A função atom permite-nos, assim, saber quais os tipos de entidades a quepodemos aplicar as operações car e cdr: apenas aquelas que não são átomos.

7.10 Reconhecedores

Para além dos operadores relacionais, existem muitos outros predicados emLisp, como por exemplo o zerop que testa se um número é zero:

_$ (zerop 1)nil_$ (zerop 0)t

O facto de zerop terminar com a letra “p” deve-se a uma convenção adop-tada em Lisp segundo a qual os predicados cujo nome seja uma ou mais pala-vras devem ser distinguidos das restantes funções através da concatenação daletra “p” (de Predicate) ao seu nome. Infelizmente, por motivos históricos, nemtodos os predicados pré-definidos seguem esta convenção. No entanto, nospredicados que definirmos devemos ter o cuidado de seguir esta convenção.

Note-se que o operador zerop serve para reconhecer um elemento em par-ticular (o zero) de um tipo de dados (os números). Este género de predicadosdenominam-se de reconhecedores. Um outro exemplo de um reconhecedor é opredicado null que reconhece a lista vazia. A função null devolve verdadequando aplicada a uma lista vazia e falso em qualquer outro caso:

_$ (list 1 2 3)(1 2 3)_$ (null (list 1 2 3))nil_$ (list)nil_$ (null (list))T

Para se perceberem as duas últimas expressões do exemplo anterior con-vém recordar que nil representa não só a falsidade mas também a lista vazia.

7.11 Reconhecedores Universais

Um outro conjunto importante de predicados são os denominados reconhece-dores universais. Estes não reconhecem elementos particulares de um tipo dedados mas sim todos os elementos de um particular tipo de dados. Um reco-nhecedor universal aceita qualquer tipo de valor como argumento e devolveverdade se o valor é do tipo pretendido.

80

Por exemplo, para sabermos se uma determinada entidade é um númeropodemos empregar o predicado numberp:37

_$ (numberp 1)T_$ (numberp nil)nil_$ (numberp "Dois")nil

Para testar se uma determinada entidade é uma lista podemos empregar opredicado listp. Este predicado é verdade para qualquer lista (incluindo alista vazia) e falso para tudo o resto:

_$ (listp (list 1 2))T_$ (listp 1)nil_$ (listp (list))T_$ (listp (cons 1 2))T

Note-se, no último exemplo, que o reconhecedor universal listp tambémconsidera como lista um mero par de elementos. Na realidade, este reconhece-dor devolve verdade sempre que o seu argumento é um dotted pair ou o nil.As listas, como vimos, não são mais do que arranjos particulares de dotted pairsou, no limite, uma lista vazia.

Para a maioria dos tipos, o Auto Lisp não providencia nenhum reconhe-cedor universal, antes preferindo usar a função genérica type que devolve onome do tipo como símbolo. Como vimos na seccção 4.12, temos:

_$ (type pi)REAL_$ (type 1)INT_$ (type "Ola")STR_$ (type quadrado)USUBR_$ (type +)SUBR

Obviamente, nada nos impede de definir os reconhecedores universais quepretendermos. À semelhança do predicado numberp podemos definir os seussubcasos integerp e realp:

37É tradicional, em vários dialectos de Lisp, terminar o nome dos predicados com a letra p.Noutros dialectos, prefere-se terminar esse nome com um ponto de interrogação precisamenteporque, na prática, a invocação de um predicado corresponde a uma pergunta que fazemos.Neste texto, nos casos em que fizer sentido enquadrarmo-nos com a tradição, iremos empregara letra p; em todos os outros casos iremos empregar o ponto de interrogação.

81

(defun integerp (obj)(= (type obj) ’int))

(defun realp (obj)(= (type obj) ’real))

É igualmente trivial definir um reconhecedor universal de strings e outropara funções (compiladas ou não):

(defun stringp (obj)(= (type obj) ’str))

(defun functionp (obj)(or (= (type obj) ’subr)

(= (type obj) ’usubr)))

7.12 Exercícios

Exercício 7.12.1 O que é uma expressão condicional? O que é uma expressão lógica?

Exercício 7.12.2 O que é um valor lógico? Quais são os valores lógicos empregues em AutoLisp?

Exercício 7.12.3 O que é um predicado? Dê exemplos de predicados em Auto Lisp.

Exercício 7.12.4 O que é um operador relacional? Dê exemplos de operadores relacionais emAuto Lisp.

Exercício 7.12.5 O que é um operador lógico? Quais são os operadores lógicos que conhece emAuto Lisp?

Exercício 7.12.6 O que é um reconhecedor? O que é um reconhecedor universal? Dê exemplosem Auto Lisp.

Exercício 7.12.7 Traduza para Lisp as seguintes expressões matemáticas:

1. x < y

2. x ≤ y3. x < y ∧ y < z

4. x < y ∧ x < z

5. x ≤ y ≤ z6. x ≤ y < z

7. x < y ≤ z

8 Estruturas de Controle

Até agora, temos visto essencialmente a definição e aplicação de funções. Nocaso da aplicação de funções, vimos que o Lisp avalia todos os elementos dacombinação e, em seguida, invoca a função que é o resultado da avaliação doprimeiro elemento da combinação usando, como argumentos, o resultado daavaliação dos restantes elementos. Este comportamento é imposto pela lingua-gem e é um exemplo de uma estrutura de controle.

82

Em termos computacionais, o controle está associado à ordem pela qual ocomputador executa as instruções que lhe damos. Se nada for dito em contrá-rio, o computador executa sequencialmente as instruções que lhe damos. Noentanto, algumas dessas instruções podem provocar o salto de instruções, i.e.,podem forçar o computador a continuar a executar instruções mas a partir deoutro ponto do programa.

Com o tempo, os programadores aperceberam-se que a utilização indiscri-minada de saltos tornava os programas extremamente difíceis de compreen-der por um ser humano e, por isso, inventaram-se formas mais estruturadasde controlar o computador: as estruturas de controle.

As estruturas de controle não são mais do que formas padronizadas de secontrolar um computador. Esta estruturas de controle não são disponibiliza-das pelo computador em si mas sim pela linguagem de programação que es-tivermos a utilizar que, depois, as converte nas estruturas de controle básicasdo computador.

Iremos agora ver algumas das estruturas de controle mas importantes.

8.1 Sequenciação

A estrutura de controle mais simples que existe é a sequenciação.Na estrutura de controle de sequenciação, o Lisp avalia uma sequência de

expressões uma a seguir à outra, devolvendo o valor da última. Logicamente,se apenas o valor da última expressão é utilizado, então os valores de todas asoutras avaliações dessa sequência são descartados e estas apenas são relevantepelos efeitos secundários que possam provocar.

Como já vimos, é possível usar sequenciação na definição de uma função.Esta função, quando invocada, irá sucessivamente avaliar cada uma das ex-pressões, descartando o seu valor, até chegar à última que será avaliada e oseu valor será considerado como o valor da invocação da função.

Aquando da definição da coluna dórica, vimos que a função coluna usavasequenciação para desenhar, sucessivamente, o fuste, o coxim e o ábaco:

(defun coluna (pa-fuste r-base-fustea-coxim r-base-coxima-abaco l-abaco)

(fuste p a-fuste r-base-fuste r-base-coxim)(coxim (+z p a-fuste) a-coxim r-base-coxim (/ l-abaco 2.0))(abaco (+z p (+ a-fuste a-coxim)) a-abaco l-abaco))

Como se pode ver, a função recebe vários parâmetros necessários para es-pecificar completamente as características geométricas da coluna.

Quando invocada, a função coluna limita-se a, primeiro, invocar a fun-ção fuste, passando-lhe os argumentos relevantes, segundo, invocar a fun-ção coxim, passando-lhe os argumentos relevantes e, terceiro, invocar a fun-ção abaco, passando-lhe os argumentos relevantes. É precisamente este “pri-meiro,” “segundo,” “terceiro” que caracteriza a estrutura de controle de se-quenciação: as avaliações são feitas sequencialmente.

83

Se tivermos em conta que uma função, quando invocada, precisa de com-putar e devolver um valor, a presença de sequenciação levanta a pergunta so-bre qual dos valores que foram sucessivamente computados é que representao valor da função invocada: por conveniência, Lisp arbitra que é o último etodos os anteriores são descartados.

Embora o corpo de uma função possa conter várias expressões que, quandoa função é invocada, são avaliadas sequencialmente, na realidade, a sequenci-ação é implementada em Lisp através de um operador denominado progn:

_$ (progn(+ 1 2)(* 3 4))

12

Este operador recebe várias expressões como argumento que avalia se-quencialmente, devolvendo o valor da última. Quando definimos uma fun-ção todas as expressões que colocamos no corpo da função são avaliadas numprogn implícito. Como iremos ver, existem ainda outras formas da linguagemLisp que possuem progns implícitos.38

8.2 Invocação de Funções

A invocação de uma função é a estrutura de controle mais usada em Lisp.Para se invocar uma função, é necessário construir uma combinação cujo

primeiro elemento seja uma expressão que avalia para a função que se pre-tende invocar e cujos restantes elementos são expressões que avaliam para osargumentos que se pretende passar à função. O resultado da avaliação dacombinação é o valor calculado pela função para aqueles argumentos.

A avaliação de uma combinação deste género processa-se nos seguintespassos:

1. Todos os elementos da combinação são avaliados, sendo que o valor doprimeiro elemento é necessariamente uma função.

2. Associam-se os parâmetros formais dessa função aos argumentos, i.e.,aos valores dos restantes elementos da combinação. Cada parâmetro éassociado a um argumento, de acordo com a ordem dos parâmetros eargumentos. É gerado um erro sempre que o número de parâmetros nãoé igual ao número de argumentos.

3. Avalia-se o corpo da função tendo em conta estas associações entre osparâmetros e os argumentos.

38O nome progn parece pouco intuitivo mas, como a maioria dos nomes usados em Lisp,tem uma história que o justifica. Os Lisps originais disponibilizavam um operador denominadoprog (abreviatura de “program”) que, entre várias outras coisas, permitia sequenciação. Paraalém deste operador, foram introduzidas variantes mais simples, denominadas prog1—queavaliava sequencialmente os seus argumentos e retornava o valor da primeira—, prog2—queavaliava sequencialmente os seus argumentos e retornava o valor da segunda—e, finalmente,progn—que avaliava sequencialmente os seus argumentos e retornava o valor da última. Ape-nas este último operador foi implementado em Auto Lisp.

84

Para exemplificar, consideremos a definição da função quadrado e a se-guinte combinação:

_$ (defun quadrado (x) (* x x))QUADRADO_$ (+ 1 (quadrado 2) 3)

Para que o avaliador de Lisp consiga avaliar a última combinação neces-sita de começar por avaliar todos os seus elementos. O primeiro, o símbolo+, avalia para o procedimento que realiza somas mas, antes de poder fazer asoma, precisa de determinar os seus argumentos através da avaliação dos res-tantes elementos da combinação. O primeiro argumento é o número 1 pois,como vimos anteriormente, o valor de um número é o próprio número. De-pois de avaliar o primeiro argumento, o avaliador depara-se com uma novacombinação. Nesse momento, suspende a avaliação que estava a fazer e iniciauma nova avaliação, agora para a combinação (quadrado 2). Novamente,porque se trata da aplicação de uma função, o avaliador vai avaliar todos osseus argumentos que, neste caso, é apenas um número que avalia para ele pró-prio. A partir deste momento, o controle é transferido para o corpo da funçãoquadrado, mas fazendo a associação do parâmetro x ao valor 2. A avaliaçãodo corpo da função quadrado implica a avaliação da combinação (* x x).Trata-se de uma multiplicação cujos argumentos são obtidos pela repetida ava-liação do símbolo x. Uma vez que a invocação da função tinha associado x aoargumento 2, o valor de x é 2. No entanto, para realizar a multiplicação énecessário ter em conta que se trata, mais uma vez, da invocação de uma fun-ção, pelo que a avaliação da invocação da função quadrado é por sua vezsuspensa para se passar à avaliação da multiplicação. No instante seguinte, amultiplicação é realizada e tem como resultado o número 4. Nesse momento,o avaliador termina a invocação da multiplicação e faz o controle regressar àavaliação do quadrado de 2. Uma vez que a multiplicação é a última expressãoda função quadrado, esta invocação vai também terminar tendo como resul-tado o mesmo da multiplicação, i.e., 4. Mais uma vez, o avaliador vai transferiro controle, agora para a expressão que estava a avaliar quando invocou a fun-ção quadrado, avaliando o último argumento da soma (que avalia para elepróprio), permitindo-lhe assim concluir a soma com o valor 8.

Resumidamente, a invocação de funções consiste em “suspender” a avali-ação que se estava a fazer para (1) se associarem os argumentos da invocaçãoaos parâmetros da função invocada, (2) avaliar o corpo da função tendo aquelaassociação em conta e (3) “retomar” a avaliação suspendida usando como va-lor da invocação o valor da última expressão do corpo da função invocada.

As características da linguagem Lisp tornam-na particularmente apta à de-finição e invocação de funções. De facto, as bases matemáticas da lingua-gem Lisp assentam precisamente sobre o conceito de função. Em Lisp, asfunções usam-se não só por uma questão de conveniência mas também poruma questão de modelação: uma função representa algo que devemos tornarclaro. O exemplo anterior pode ser reescrito para a forma (+ 1 (* 2 2) 3)mas perdeu-se o conceito de quadrado que estava anteriormente evidente.

85

Em geral, quando usamos uma função estamos a exprimir uma dependên-cia entre entidades. Uma dessas entidades diz-se então função das restantes e oseu valor é determinado pela invocação de uma função que recebe as restantesentidades como argumentos.

8.3 Variáveis Locais

Consideremos o seguinte triângulo

c b

a

e tentemos definir uma função Auto Lisp que calcula a área do triângulo apartir dos parâmetros a, b e c.

Uma das formas de calcular a área do triângulo baseia-se na famosa fór-mula de Heron:39

A =√s (s− a) (s− b) (s− c)

em que é s o semi-perímetro do triângulo:

s =a+ b+ c

2

Ao tentarmos usar a fórmula de Heron para definir a correspondente fun-ção Auto Lisp, somos confrontados com uma dificuldade: a fórmula está es-crita (também) em termos do semi-perímetro s mas s não é um parâmetro, éum valor derivado dos parâmetros do triângulo.

Uma das maneira de resolvermos este problema consiste em eliminar avariável s, substituindo-a pelo seu significado:

A =

√a+ b+ c

2

(a+ b+ c

2− a)(

a+ b+ c

2− b)(

a+ b+ c

2− c)

A partir desta fórmula já é possível definir a função pretendida:

(defun area-triangulo (a b c)(sqrt (* (/ (+ a b c) 2)

(- (/ (+ a b c) 2) a)(- (/ (+ a b c) 2) b)(- (/ (+ a b c) 2) c))))

39Heron de Alexandria foi um importante matemático e engenheiro Grego do primeiro séculodepois de Cristo a quem se atribuem inúmeras descobertas e invenções, incluindo a máquina avapor e a seringa.

86

Infelizmente, esta definição tem dois problemas. O primeiro é que se per-deu a correspondência entre a fórmula original e a definição da função, tor-nando mais difícil reconhecer que a função está a implementar a fórmula deHeron. O segundo problema é que a função é obrigada a repetidamente em-pregar a expressão (/ (+ a b c) 2), o que não só representa um desperdíciodo nosso esforço, pois tivemos de a escrever quatro vezes, como representaum desperdício de cálculo, pois a expressão é calculada quatro vezes, emborasaibamos que ela produz sempre o mesmo resultado.

Para resolver estes problemas, o Auto Lisp permite a utilização de variáveislocais. Uma variável local é uma variável que apenas tem significado dentro deuma função e que é usada para calcular valores intermédios como o do semi-perímetro s. Empregando uma variável local, podemos reescrever a funçãoarea-triangulo da seguinte forma:

(defun area-triangulo (a b c / s)(setq s (/ (+ a b c) 2))(sqrt (* s (- s a) (- s b) (- s c))))

Na definição anterior há dois aspectos novos que vamos agora discutir: adeclaração da variável local s e a atribuição dessa variável através do operadorsetq.

Para se indicar que uma função vai usar variáveis locais, estas têm de serdeclaradas conjuntamente com a lista de parâmetros da função. Para isso, va-mos agora ampliar a sintaxe da definição de funções que apresentámos anteri-ormente (na secção 4.4 de modo a incorporar a declaração de variáveis locais:

(defun nome (parâmetro1 ... parâmetron/ variável1 ... variávelm)

corpo)

Note-se que as variáveis locais são separadas dos parâmetros por umabarra. É necessário ter o cuidado de não “colar” a barra nem ao último pa-râmetro nem à primeira variável sob pena de o Auto Lisp considerar a barracomo parte de um dos nomes (e, consequentemente, tratar as variáveis locaiscomo se de parâmetros se tratasse).

Embora seja raro, é perfeitamente possível termos funções sem parâmetrosmas com variáveis locais. Neste caso, a sintaxe da função seria simplesmente:

(defun nome (/ variável1 ... variávelm)corpo)

8.4 Atribuição

Para além da declaração de uma variável local é ainda necessário atribuir-lheum valor, i.e., realizar uma operação de atribuição. Para isso, o Auto Lisp dis-ponibiliza o operador setq, cuja sintaxe é:

(setq variável1 expressão1

...variávelm expressãom)

87

A semântica deste operador consiste simplesmente em avaliar a expressãoexpressão1 e associar o seu valor ao nome variável1, repetindo este pro-cesso para todas as restantes variáveis e valores.

A utilização que fazemos deste operador na função area-triangulo éfácil de compreender:

(defun area-triangulo (a b c / s)(setq s (/ (+ a b c) 2))(sqrt (* s (- s a) (- s b) (- s c))))

Ao invocarmos a função area-triangulo, passando-lhe os argumen-tos correspondentes aos parâmetros a, b e c, ela começa por introduzir umnovo nome—s—que vai existir apenas durante a invocação da função. Deseguida, atribui a esse nome o valor que resultar da avaliação da expressão(/ (+ a b c) 2). Finalmente, avalia as restantes expressões do corpo da fun-ção. Na prática, é como se a função dissesse: “Sendo s = a+b+c

2 , calculemos√s (s− a) (s− b) (s− c).”

Há dois detalhes que é importante conhecer relativamente às variáveis lo-cais. O primeiro detalhe, que iremos discutir mais em profundidade na pró-xima secção, é que se nos esquecermos de declarar a variável, ela será tratadacomo variável global. O segundo detalhe é que se nos esquecermos de atri-buir a variável ela fica automaticamente com o valor nil. Logicamente, paraque uma variável nos seja útil, devemos mudar o seu valor para algo que nosinteresse.

Para visualizarmos um exemplo mais complexo, consideremos a equaçãodo segundo grau

ax2 + bx+ c = 0

Como sabemos, esta equação tem duas raízes que se podem obter pela famosafórmula resolvente:

x =−b+

√b2 − 4ac

2a∨ x =

−b−√b2 − 4ac

2a

Dada a utilidade desta fórmula, podemos estar interessados em definiruma função que, dados os coeficientes a, b e c da equação do segundo graunos devolve as suas duas raizes. Para isso, vamos devolver um par de núme-ros calculados usando as duas fórmulas acima:

(defun raizes-equacao-segundo-grau (a b c)(cons (/ (- (- b)

(sqrt (- (quadrado b) (* 4 a c))))(* 2 a))

(/ (+ (- b)(sqrt (- (quadrado b) (* 4 a c))))

(* 2 a))))

Por exemplo, usando a definição anterior, podemos calcular as soluções daequação (x−2)(x−3) = 0. De facto, esta equação é equivalente a x2−5x+6 =0, i.e., basta calcularmos as raízes empregando a fórmula resolvente com oscoeficientes a = 1, b = −5, c = 6, ou seja:

88

_$ (raizes-equacao-segundo-grau 1 -5 6)(2.0 . 3.0)

Aparentemente, tudo está bem. Contudo, há uma ineficiência óbvia: afunção raizes-equacao-segundo-grau calcula duas vezes o mesmo va-lor correspondente ao termo

√b2 − 4ac. Isso é perfeitamente visível na dupla

ocorrência da expressão (sqrt (- (quadrado b) (* 4 a c))).Mais uma vez, é através do uso de variáveis locais e do operador setq

que vamos simplificar e tornar mais eficiente esta função. Para isso, basta-nos calcular o valor de

√b2 − 4ac, associando-o a uma variável local e, em

seguida, usamos esse valor nos dois locais em que é necessário. Do mesmomodo, podemos declarar uma variável para conter o valor da expressão 2a eoutra para conter −b. O resultado fica então:

(defun raizes-equacao-segundo-grau (a b c/ raiz-b2-4ac 2a -b)

(setq raiz-b2-4ac (sqrt (- (quadrado b) (* 4 a c)))2a (* 2 a)-b (- b))

(cons (/ (- -b raiz-b2-4ac)2a)

(/ (+ -b raiz-b2-4ac)2a)))

É importante salientar que raiz-b2-4ac, 2a e -b são apenas nomes asso-ciados a números que resultaram da avaliação das expressões corresponden-tes. Após estas atribuições, quando se vai avaliar a expressão que constrói opar, esses nomes vão ser por sua vez avaliados e, logicamente, vão ter comovalor a atribuição que lhes tiver sido feita anteriormente.

Um outro ponto importante a salientar é que aqueles nomes só existemdurante a invocação da função e, na realidade, existem diferentes ocorrênciasdaqueles nomes para diferentes invocações da função. Este ponto é de crucialimportância para se perceber que, após a invocação da função, os nomes, querde parâmetros, quer de variáveis locais, desaparecem, conjuntamente com asassociações que lhes foram feitas.

Exercício 8.4.1 Como viu, as variáveis locais permitem evitar cálculos repetidos. Uma outraforma de evitar esses cálculos repetidos é através do uso de funções auxiliares. Redefina afunção raizes-equacao-segundo-grau de forma a que não seja necessário usar quaisquervariáveis locais.

8.5 Variáveis Globais

Qualquer referência a um nome que não está declarado localmente implicaque esse nome seja tratado como uma variável global.40 O nome pi, por exem-plo, representa a variável pi e pode ser usado em qualquer ponto dos nossosprogramas. Por esse motivo, o nome pi designa uma variável global.

40Na realidade, o Auto Lisp permite ainda uma terceira categoria de nomes que não são nemlocais, nem globais. Mais à frente discutiremos esse caso.

89

A declaração de variáveis globais é mais simples que a das variáveis lo-cais: basta uma primeira atribuição, em qualquer ponto do programa, paraa variável ficar automaticamente declarada. Assim, se quisermos introduziruma nova variável global, por exemplo, para definir a razão de ouro41

φ =1 +√

52

≈ 1.6180339887

basta-nos escrever:

(setq razao-de-ouro (/ (+ 1 (sqrt 5)) 2))

A partir desse momento, o nome razao-de-ouro pode ser referenciadoem qualquer ponto dos programas.

É importante referir que o uso de variáveis globais deve ser restrito, namedida do possível, à definição de constantes, i.e., variáveis cujo valor nuncamuda como, por exemplo, pi. Outros exemplos que podem vir a ser úteis in-cluem 2*pi, pi/2, 4*pi e pi/4, bem como os seus simétricos, que se definemà custa de:

(setq 2*pi (* 2 pi))

(setq pi/2 (/ pi 2))

(setq 4*pi (* 4 pi))

(setq pi/4 (/ pi 4))

(setq -pi (- pi))

(setq -2*pi (- 2*pi))

(setq -pi/2 (- pi/2))

(setq -4*pi (- 4*pi))

(setq -pi/4 (- pi/4))

O facto de uma variável global ser uma constante implica que a variável éatribuída uma única vez, no momento da sua definição. Esse facto permite-nos usar a variável sabendo sempre qual o valor a que ela está associada.Infelizmente, por vezes é necessário usarmos variáveis globais que não sãoconstantes, i.e., o seu valor muda durante a execução do programa, por acção

41Também conhecida por proporção divina e número de ouro, entre outras designações, e abre-viada por φ em homenagem a Fineas, escultor Grego que foi responsável pela construção doParténon onde, supostamente, usou esta proporção. A razão de ouro foi inicialmente intro-duzida por Euclides quando resolveu o problema de dividir um segmento de recta em duaspartes de tal forma que a razão entre o segmento de recta e a parte maior fosse igual à razãoentre a parte maior e a menor. Se for a o comprimento do parte maior e b o da menor, o pro-blema de Euclides é idêntico a dizer que a+b

a= a

b. Daqui resulta que a2 − ab − b2 = 0 ou que

a =b±√b2+4b2

2= b 1±

√5

2. A raiz que faz sentido é, obviamente, a = b 1+

√5

2e, consequente-

mente, a razão de ouro é φ = ab

= 1+√

52

.

90

de diferentes atribuições feitas em locais diferentes e em momentos diferen-tes. Quando temos variáveis globais que são atribuidas em diversos pontos donosso programa, o comportamento deste pode tornar-se muito mais difícil deentender pois, na prática, pode ser necessário compreender o funcionamentode todo o programa em simultâneo e não apenas função a função, como temosfeito. Por este motivo, devemos evitar o uso de variáveis globais que sejamatribuidas em vários pontos do programa.

8.6 Variáveis Indefinidas

A linguagem Auto Lisp difere da maioria dos outros dialecto de Lisp no tra-tamento que dá a variáveis indefinidas, i.e., variáveis a que nunca foi atribuídoum valor.42Em geral, as linguagens de programação consideram que a avalia-ção de uma variável indefinida é um erro. No caso da linguagem Auto Lisp, aavaliação de qualquer variável indefinida é simplesmente nil.

A consequência deste comportamento é que existe o potencial de os er-ros tipográficos passarem despercebidos, tal como é demonstrado no seguinteexemplo:

_$ (setq minha-lista (list 1 2 3))(1 2 3)_$ (cons 0 mimha-lista)(0)

Reparemos, no exemplo anterior, que a variável que foi definida não é amesma que está a ser avaliada pois a segunda tem uma gralha no nome. Emconsequência, a expressão que tenta juntar o 0 ao ínicio da lista (1 2 3) está,na realidade, a juntar o 0 ao ínicio da lista nil que, como sabemos, é tambéma lista vazia.

Este género de erros pode ser extremamente difícil de detectar e, por isso,a maioria das linguagens abandonou este comportamento. O Auto Lisp, pormotivos históricos, manteve-o, o que nos obriga a termos de ter muito cuidadopara não cometermos erros que depois passem indetectados.

8.7 Proporções de Vitrúvio

A modelação de colunas dóricas que desenvolvemos na secção 6.2 permite-nos facilmente construir colunas, bastando para isso indicarmos os valores dosparâmetros relevantes, como a altura e o raio da base do fuste, a altura e raioda base do coxim e a altura e largura do ábaco. Cada um destes parâmetrosconstitui um grau de liberdade que podemos fazer variar livremente.

Embora seja lógico pensar que quantos mais graus de liberdade tivermosmais flexível é a modelação, a verdade é que um número excessivo de parâ-metros pode conduzir a modelos pouco realistas. Esse fenómeno é evidente naFigura 25 onde mostramos uma perspectiva de um conjunto de colunas cujosparâmetros foram escolhidos aleatoriamente.

42Na terminologia original do Lisp, estas variáveis diziam-se unbound.

91

Figura 25: Perspectiva da modelação tri-dimensional de colunas cujos parâ-metros foram escolhidos aleatoriamente. Apenas uma das colunas obedeceaos cânones da Ordem Dórica.

Na verdade, de acordo com os cânones da Ordem Dórica, os diversos pa-râmetros que regulam a forma de uma coluna devem relacionar-se entre sisegundo um conjunto de proporções bem definidas. Vitrúvio43, no seu famosotratado de arquitectura, descreveu essas proporções em termos do conceito demódulo:44

• A largura das colunas, na base, será de dois modulos e a sua altura, incluindoos capitéis, será de catorze.

Daqui se deduz um módulo iguala o raio da base da coluna e que a alturada coluna deverá ser 14 vezes esse raio. Dito de outra forma, o raio dabase da coluna deverá ser 1

14 da altura da coluna.

• A altura do capitel será de um módulo e a sua largura de dois módulos e umsexto.

Isto implica que a altura do coxim somado à do ábaco será um módulo,ou seja, igual ao raio da base da coluna e a largura do ábaco será de 21

6

43Vitrúvio foi um escritor, arquitecto e engenheiro romano que viveu no século um antes deCristo e autor do único tratado de arquitectura que sobreviveu a antiguidade.

44As proporções da ordem Dórica derivam das proporções do próprio ser humano. SegundoVitrúvio,

Uma vez que pretendiam erguer um templo com colunas mas não tinhamconhecimento das proporções adequadas, [] mediram o comprimento dum péde um homem e viram que era um sexto da sua altura e deram à coluna umaproporção semelhante, i.e., fizeram a sua altura, incluindo o capitel, seis vezes alargura da coluna medida na base. Assim, a ordem Dórica obteve a sua proporçãoe a sua beleza, da figura masculina.

92

módulos ou 136 do raio. Juntamente com o facto de a altura da coluna

ser de 14 módulos, implica ainda que a altura do fuste será de 13 vezes oraio.

• Seja a altura do capitel dividida em três partes, das quais uma formará o ábacocom o seu cimáteo, o segundo o équino (coxim) com os seus aneis e o terceiro opescoço.

Isto quer dizer que o ábaco tem uma altura de um terço de um modulo,ou seja 1

3 do raio da base, e o coxim terá os restantes dois terços, ou seja,23 do raio da base.

Estas considerações levam-nos a poder determinar o valor de alguns dosparâmetros de desenho das colunas dóricas em termos do raio da base dofuste. Em termos de implementação, isso quer dizer que os parâmetros dafunção passam a ser variáveis locais cuja atribuição é feita aplicando as pro-porções estabelecidas por Vitrúvio ao parâmetro r-base-fuste. A definiçãoda função fica então:

(defun coluna (pr-base-fuste r-base-coxim/ a-fuste a-coxim a-abaco l-abaco)

(setq a-fuste (* 13 r-base-fuste)a-coxim (* (/ 2.0 3) r-base-fuste)a-abaco (* (/ 1.0 3) r-base-fuste)l-abaco (* (/ 13.0 6) r-base-fuste))

(fuste p a-fuste r-base-fuste r-base-coxim)(coxim (+z p a-fuste) a-coxim r-base-coxim (/ l-abaco 2.0))(abaco (+z p (+ a-fuste a-coxim)) a-abaco l-abaco))

Usando esta função já é possível desenhar colunas que se aproximam maisdo padrão dórico (tal como estabelecido por Vitrúvio).45 A Figura 26 apresentaas colunas desenhadas pelas seguintes invocações:

(coluna (xyz 0 0 0) 0.3 0.2)(coluna (xyz 3 0 0) 0.5 0.3)(coluna (xyz 6 0 0) 0.4 0.2)(coluna (xyz 9 0 0) 0.5 0.4)(coluna (xyz 12 0 0) 0.5 0.5)(coluna (xyz 15 0 0) 0.4 0.7)

8.8 Selecção

As proporções de Vitrúvio permitiram-nos reduzir o número de parâmetrosindependentes de uma coluna Dórica a apenas dois: o raio da base do fuste eo raio da base do coxim. Contudo, não parece correcto que estes parâmetrossejam totalmente independentes pois isso permite construir colunas aberran-tes em que o topo do fuste é mais largo do que a base, tal como acontece coma coluna a mais à direita na Figura 26.

45A Ordem Dórica estabelece ainda mais uma relação entre os parâmetros das colunas, rela-ção essa que só iremos implementar na secção 8.10.

93

Figura 26: Variações de colunas dóricas segundo as proporções de Vitrúvio.

Na verdade, a caracterização da Ordem Dórica que apresentámos encontra-se incompleta pois, acerca das proporções das colunas, Vitrúvio afirmou aindaque:

. . . se uma coluna tem quinze pés ou menos, divida-se a largurana base em seis partes e usem-se cinco dessas partes para formara largura no topo, . . . [Vitrúvio, Os Dez Livros da Arquitectura,Livro III, Cap. 3.1]

Até agora, fomos capazes de traduzir para Auto Lisp todas as regras quefomos anunciando mas, desta vez, a tradução levanta-nos uma nova dificul-dade: como traduzir um termo da forma “se, então?”

O termo “se, então” permite-nos executar uma acção dependendo de umadeterminada condição ser verdadeira ou falsa: se uma coluna tem quinze pésou menos, então o topo da coluna tem 5

6 da base, caso contrário, . . . . Na lingua-gem Lisp, estes termos descrevem uma forma de actuação que se denomina deestrutura de controle de selecção. A selecção permite-nos escolher uma via deacção mas apenas se uma determinada condição for verdadeira.

A estrutura de controle de selecção é mais sofisticada que a sequenciação.Ela baseia-se na utilização de expressões condicionais para decidir qual a pró-xima expressão a avaliar. A forma mais simples desta estrutura de controle éo if cuja sintaxe é a seguinte:

(if expressão condicionalexpressão consequenteexpressão alternativa)

O valor de uma combinação cujo primeiro elemento é o if é obtido daseguinte forma:

94

1. A expressão condicional é avaliada.

2. Se o valor obtido da avaliação anterior é verdade, o valor da combinaçãoé o valor da expressão consequente.

3. Caso contrário, o valor obtido da avaliação anterior é falso e o valor dacombinação é o valor da expressão alternativa.

Este comportamento pode ser confirmado pelos seguintes exemplos:

_$ (if (> 3 2)12)

1_$ (if (> 3 4)

12)

2

Usando o if podemos definir funções cujo comportamento depende deuma ou mais condições. Por exemplo, consideremos a função max que recebedois números como argumentos e devolve o maior deles. Para definirmosesta função apenas precisamos de testar se o primeiro argumento é maior queo segundo. Se for, a função devolve o primeiro argumento, caso contráriodevolve o segundo. Com base neste raciocínio, podemos escrever:

(defun max (x y)(if (> x y)

xy))

Muitas outras funções podem ser definidas à custa do if. Por exemplo,para calcularmos o valor absoluto de um número x, |x|, temos de saber se eleé negativo. Se for, o seu valor absoluto é o seu simétrico, caso contrário é elepróprio. Eis a definição:

(defun abs (x)(if (< x 0)

(- x)x))

Um outro exemplo mais interessante ocorre com a função matemática sinalsgn, também conhecida como função signum (“sinal,” em Latim). Esta funçãopode ser vista como a função dual da função valor absoluto pois tem-se semprex = sgn(x)|x|. A função sinal é definida por

sgnx =

−1 se x < 00 se x = 01 caso contrário

Quando definimos esta função em Lisp notamos que é necessário empregarmais do que um if:

95

(defun signum (x)(if (< x 0)

-1(if (= x 0)01)))

8.9 Selecção Múltipla—A Forma cond

Quando uma definição de função necessita de vários ifs encadeados, é nor-mal que o código comece a ficar mais difícil de ler. Neste caso, é preferívelusar uma outra forma que torna a definição da função mais legível. Para evi-tar muitos ifs encadeados o Lisp providencia uma outra forma denominadacond cuja sintaxe é a seguinte:

(cond (expr0,0 expr0,1 ... expr0,n)(expr1,0 expr1,1 ... expr1,m)...(exprk,0 exprk,1 ... exprk,p))

O cond aceita qualquer número de argumentos. Cada argumento é umalista de expressões denominada de cláusula. A semântica do cond consisteem avaliar sequencialmente a primeira expressão expri,0 de cada cláusula atéencontrar uma cujo valor seja verdade. Nesse momento, o cond avalia todas asrestantes expressões dessa cláusula e devolve o valor da última. Se nenhumadas cláusulas tiver uma primeira expressão que avalie para verdade, o conddevolve nil. Se a cláusula cuja primeira expressão é verdade não contivermais expressões, o cond devolve o valor dessa primeira expressão.

É importante perceber que os parêntesis que envolvem as cláusulas nãocorrespondem a nenhuma combinação: eles simplesmente fazem parte da sin-taxe do cond e são necessários para separar as cláusulas umas das outras.

A pragmática usual para a escrita de um cond (em especial quando cadacláusula contém apenas duas expressões) consiste em alinhar as expressõesumas debaixo das outras.

Usando o cond, a função sinal pode ser escrita de forma mais simples:

(defun signum (x)(cond ((< x 0)

-1)((= x 0)0)(t1)))

Note-se, no exemplo anterior, que a última cláusula do cond tem, como ex-pressão lógica, o símbolo t. Como já vimos, este símbolo representa a verdadepelo que a sua presença garante que ela será avaliada no caso de nenhumadas cláusulas anteriores o ter sido. Neste sentido, cláusula da forma (t ...)representa um “em último caso . . . .”

Exercício 8.9.1 Qual o significado de (cond (expr1 expr2))?

96

Exercício 8.9.2 Qual o significado de (cond (expr1) (expr2))?

A forma cond não é estritamente necessária na linguagem pois é possívelfazer exactamente o mesmo com uma combinação entre formas if encadeadase formas progn, tal como a seguinte equivalência o demonstra:

(cond (expr0,0 expr0,1 ... expr0,n)(expr1,0 expr1,1 ... expr1,m)...(exprk,0 exprk,1 ... exprk,p))

m

(if expr0,0

(prognexpr0,1 ... expr0,n)

(if expr1,0

(prognexpr1,1 ... expr1,m)...

(if exprk,0(prognexprk,1 ... exprk,p)

nil)))

Contudo, pragmaticamente falando, a forma cond pode simplificar subs-tancialmente o programa quando o número de if encadeados é maior que umou dois ou quando se pretende usar sequenciação no consequente ou alterna-tiva do if.

Exercício 8.9.3 Defina uma função soma-maiores que recebe três números como argumentoe determina a soma dos dois maiores.

Exercício 8.9.4 Defina a função max3 que recebe três números como argumento e calcula omaior entre eles.

8.10 Selecção nas Proporções de Vitrúvio

Uma vez compreendida a estrutura de controle de selecção, estamos em con-dições de implementar a última regra de Vitrúvio sobre as proporções das co-lunas da Ordem Dórica. A regra afirma que:

A diminuição no topo de uma coluna parece ser regulada se-gundo os seguintes princípios: se uma coluna tem quinze pés oumenos, divida-se a largura na base em seis partes e usem-se cincodessas partes para formar a largura no topo. Se a coluna tem en-tre quinze e vinte pés, divida-se a largura na base em seis partes emeio e usem-se cinco e meio dessas partes para a largura no topoda coluna. Se a coluna tem entre vinte e trinta pés, divida-se a lar-gura na base em sete partes e faça-se o topo diminuido medir seisdelas. Uma coluna de trinta a quarenta pés deve ser dividida na

97

base em sete partes e meia e, no princípio da diminuição, deve terseis partes e meia no topo. Colunas de quarenta a cinquenta pés de-vem ser divididas em oito partes e diminuidas para sete delas notopo da coluna debaixo do capitel. No caso de colunas mais altas,determine-se proporcionalmente a diminuição com base nos mes-mos princípios. [Vitrúvio, Os Dez Livros da Arquitectura, Livro III,Cap. 3.1]

Estas considerações de Vitrúvio permitem-nos determinar a razão entre otopo e a base de uma coluna em função da sua altura em pés.46

Consideremos então a definição de uma função, que iremos denominar deraio-topo-fuste, que recebe como parâmetros a largura da base da colunae a altura da coluna e devolve como resultado a largura do topo da coluna.

Uma tradução literal das considerações de Vitrúvio para Lisp permite-noscomeçar por escrever:

(defun raio-topo-fuste (raio-base altura)(cond ((<= altura 15) (* (/ 5.0 6.0) raio-base))

...))

O fragmento anterior corresponde, obviamente, à afirmação: se uma colunatem quinze pés ou menos, divida-se a largura na base em seis partes e usem-se cincodessas partes para formar a largura no topo. No caso de a coluna não ter quinzepés ou menos, então passamos ao caso seguinte: se a coluna tem entre quinzee vinte pés, divida-se a largura na base em seis partes e meio e usem-se cinco e meiodessas partes para a largura no topo da coluna. A tradução deste segundo casopermite-nos escrever:

(defun raio-topo-fuste (raio-base altura)(cond ((<= altura 15) (* (/ 5.0 6.0) raio-base))

((and (> altura 15)(<= altura 20)) (* (/ 5.5 6.5) raio-base))

...))

Uma análise cuidadosa das duas cláusulas anteriores mostra que, na rea-lidade, estamos a fazer testes a mais na segunda cláusula. De facto, se con-seguimos chegar à segunda cláusula é porque a primeira é falsa, i.e., a alturaé maior do que 15. Nesse caso, é inútil estar a testar novamente se a altura émaior do que 15. Assim, podemos simplificar a função e escrever:

(defun raio-topo-fuste (raio-base altura)(cond ((<= altura 15) (* (/ 5.0 6.0) raio-base))

((<= altura 20) (* (/ 5.5 6.5) raio-base))...))

A continuação da tradução levar-nos-á, então, a:46O pé foi a unidade fundamental de medida durante inúmeros séculos mas a sua real di-

mensão variou ao longo do tempo. O comprimento do pé internacional é de 304.8 milímetrose foi estabelecido por acordo em 1958. Antes disso, vários outros comprimentos foram usados,como o pé Dórico de 324 milímetros, os pés Jónico e Romano de 296 milímetros, o pé Ateniensede 315 milímetros, os pés Egípcio e Fenício de 300 milímetros, etc.

98

(defun raio-topo-fuste (raio-base altura)(cond ((<= altura 15) (* (/ 5.0 6.0) raio-base))

((<= altura 20) (* (/ 5.5 6.5) raio-base))((<= altura 30) (* (/ 6.0 7.0) raio-base))((<= altura 40) (* (/ 6.5 7.5) raio-base))((<= altura 50) (* (/ 7.0 8.0) raio-base))...))

O problema agora é que Vitrúvio deixou a porta aberta para colunas ar-bitrariamente altas, dizendo simplesmente que no caso de colunas mais altas,determine-se proporcionalmente a diminuição com base nos mesmos princípios. Parapercebermos claramente de que princípios estamos a falar, consideremos aevolução da relação entre o topo e a base das colunas que é visível na ima-gem lateral.

0

56

15

5 12

6 12

20

67

30

6 12

7 12

40

78

50

A razão entre o raio do topo da coluna e o raio da base da coluna é, tal comose pode ver na margem (e já era possível deduzir da função raio-topo-fuste),uma sucessão da forma

56,51

2

612

,67,61

2

712

,78, · · ·

Torna-se agora óbvio que, para colunas mais altas, “os mesmos princípios”de que Vitrúvio fala se resumem a, para cada 10 pés adicionais, somar 1

2 querao numerador, quer ao denominador. No entanto, é importante reparar queeste princípio só pode ser aplicado a partir dos 15 pés de altura pois o pri-meiro intervalo é maior que os restantes. Assim, temos de tratar de formadiferente as colunas até aos 15 pés e, daí para a frente, basta subtrair 20 pés àaltura e determinar a divisão inteira por 10 para saber o número de vezes queprecisamos de somar 1

2 quer ao numerador quer ao denominador de 67 .

É este “tratar de forma diferente” um caso e outro que, mais uma vez, su-gere a necessidade de utilização de uma estrutura de controle de selecção: énecessário distinguir dois casos e reagir em conformidade para cada um. Nocaso da coluna de Vitrúvio, se a coluna tem uma altura a até 15 pés, a razãoentre o topo e a base é r = 5

6 ; se a altura a é superior a 15 pés, a razão r entre otopo e a base será:

r =6 + ba−20

10 c ·12

7 + ba−2010 c ·

12

A título de exemplo, consideremos uma coluna com 43 pés de altura. Adivisão inteira de 43 − 20 por 10 é 2 portanto temos de somar 2 · 1

2 = 1 aonumerador e denominador de 6

7 , obtendo 78 = 0.875.

Para um segundo exemplo nos limites do absurdo, consideremos uma co-luna da altura do Empire State Building, i.e., com 449 metros de altura. Umpé, na ordem Dórica, media 324 milímetros pelo que em 449 metros existem449/0.324 ≈ 1386 pés. A divisão inteira de 1386− 20 por 10 é 136. A razão en-tre o topo e a base desta hipotética coluna será então de 6+136/2

7+136/2 = 7475 = 0.987.

Este valor, por ser muito próximo da unidade, mostra que a coluna seria pra-ticamente cilíndrica.

Com base nestas considerações, podemos agora definir uma função que,dado um número inteiro representando a altura da coluna em pés, calcula a

99

razão entre o topo e a base da coluna. Antes, contudo, convém simplificar afórmula para as colunas com altura superior a 15 pés. Assim,

r =6 + ba−20

10 c ·12

7 + ba−2010 c ·

12

=12 + ba−20

10 c14 + ba−20

10 c=

12 + b a10c − 214 + b a10c − 2

=10 + b a10c12 + b a10c

A definição da função fica então:

(defun raio-topo-fuste (raio-base altura / divisoes)(setq divisoes (fix (/ altura 10)))(if (<= altura 15)

(* (/ 5.0 6.0)raio-base)

(* (/ (+ 10.0 divisoes)(+ 12.0 divisoes))

raio-base)))

Esta é a última relação que nos falta para especificarmos completamenteo desenho de uma coluna dórica de acordo com as proporções referidas porVitrúvio no seu tratado de arquitectura. Vamos considerar, para este desenho,que vamos fornecer as coordenadas do centro da base da coluna e a sua al-tura. Todos os restantes parâmetros serão calculados em termos destes. Eis adefinição da função:

(defun coluna-dorica (p altura /a-fuste r-base-fustea-coxim r-base-coxima-abaco l-abaco)

(setq r-base-fuste (/ altura 14.0)r-base-coxim (raio-topo-fuste r-base-fuste altura)a-fuste (* 13.0 r-base-fuste)a-coxim (* (/ 2.0 3) r-base-fuste)a-abaco (* (/ 1.0 3) r-base-fuste)l-abaco (* (/ 13.0 6) r-base-fuste))

(fuste p a-fuste r-base-fuste r-base-coxim)(coxim (+z p a-fuste) a-coxim r-base-coxim (/ l-abaco 2.0))(abaco (+z p (+ a-fuste a-coxim)) a-abaco l-abaco))

O seguinte exemplo de utilização da função produz a sequência de colunasapresentadas na Figura 27:47

(coluna-dorica (xy 0 0) 10)

(coluna-dorica (xy 10 0) 15)

(coluna-dorica (xy 20 0) 20)

(coluna-dorica (xy 30 0) 25)

(coluna-dorica (xy 40 0) 30)

(coluna-dorica (xy 50 0) 35)

47Note-se que, agora, a altura da coluna tem de ser especificada em pés dóricos.

100

Figura 27: Variações de colunas dóricas segundo as proporções de Vitrúvio.

Exercício 8.10.1 A função raio-topo-fuste calcula o valor da variável local divisoesmesmoquando o parâmetro altura é menor ou igual a 15. Redefina a função de modo a que a variáveldivisoes só seja definida valor quando o seu valor é realmente necessário.

8.11 Operações com Coordenadas

Vimos, na secção 5.1, a definição e implementação de um tipo abstracto paracoordenadas bi-dimensionais que assentava nas seguinte funções:

(defun xy (x y)(list x y))

(defun cx (c)(car c))

(defun cy (c)(cadr c))

Vimos também, na secção 5.6 que é possível definir as coordenadas tri-dimensionais como uma generalização óbvia do tipo anterior. Para isso, bastou-nos preservar as posições das coordenadas bi-dimensionais x e y na represen-tação de coordenadas tri-dimensionais, o que implica colocarmos a coorde-nada z a seguir às outras duas. Isto levou-nos a definir o construtor de coor-denadas Cartesianas tri-dimensionais xyz com a seguinte forma:

(defun xyz (x y z)(list x y z))

101

O correspondente selector da coordenada z ficou então definido como:

(defun cz (c)(caddr c))

Uma vez que adoptámos uma representação de coordenadas tri-dimensionaisque é uma extensão da representação correspondente para coordenadas bi-dimensionais, os selectores cx e cy que eram válidos para coordenadas bi-dimensionais passaram a ser válidos também para coordenadas tri-dimensionais.Infelizmente, o inverso já não é necessariamente verdade: o selector cz nãopode ser aplicado a coordenadas bi-dimensionais pois estas não possuem,na sua representação, esta terceira coordenada. No entanto, é possível ge-neralizar este selector de modo a torná-lo aplicável também a coordenadasbi-dimensionais, simplesmente assumindo que o caso bi-dimensional se de-senrola no plano z = 0, i.e., assumindo que (x, y) = (x, y, 0). Em termosde implementação, para que o selector cz seja compatível com coordenadasbi- ou tri-dimensionais, ele precisa de saber distinguir as duas representações.Uma vez que uma das representações é uma lista de dois elementos e a outraé uma lista de três elementos, o mais simples é testar se existe algo para lá dosegundo elemento, i.e., se o resto do resto da lista não está vazia. Como vimosna secção 7.10, a função null reconhece a lista vazia, o que nos permite definirum reconhecedor diferente para cada tipo de coordenada:

(defun xy? (c)(null (cddr c)))

(defun xyz? (c)(not (null (cddr c))))

Usando estas funções é agora mais simples definir correctamente o selectorcz: para coordenadas bi-dimensionais, devolve zero. Para coordenadas tri-dimensionais, devolve o terceiro elemento da lista de coordenadas.

(defun cz (c)(if (xy? c)

0(caddr c)))

É de salientar que o selector cz precisa de testar o seu argumento paradecidir o que devolver. Este teste e escolha do caminho a seguir é a marca daestrutura de controle de selecção.

A redefinição do selector cz para lidar com os dois tipos de coordenadastem a vantagem adicional de permitir que outras funções de coordenadas tri-dimensionais se possam aplicar a coordenadas bi-dimensionais. Por exemplo,a seguinte função de coordenadas tri-dimensionais, definida na secção 5.5,

(defun +xyz (p dx dy dz)(xyz (+ (cx p) dx)

(+ (cy p) dy)(+ (cz p) dz)))

102

passa a ser directamente aplicável a coordenadas bi-dimensionais sem qual-quer tipo de alteração.

Infelizmente, nem todas as funções conseguem funcionar inalteradas. Porexemplo, consideremos a função +xy que permitia deslocar coordenadas bi-dimensionais:

(defun +xy (c x y)(xy (+ (cx c) x)

(+ (cy c) y)))

Como é óbvio pela análise da função anterior, se ela for aplicada a umacoordenada tri-dimensional, perde-se a coordenada z, o que é manifestamenteindesejável. Para evitar este problema e, ao mesmo tempo, preservar a com-patibilidade com o caso bi-dimensional, temos de distinguir os dois casos:

(defun +xy (c x y)(if (xy? c)

(xy (+ (cx c) x)(+ (cy c) y))

(xyz (+ (cx c) x)(+ (cy c) y)(cz c))))

Como exemplo, temos:

_$ (+xy (xy 1 2) 4 5)(5 7)_$ (+xy (xyz 1 2 3) 4 5)(5 7 3)

Para além da função +xy também usámos anteriormente a função +polque somava a um ponto uma “distância” especificada em termos de coorde-nadas polares:

(defun +pol (c ro fi)(+xy c

(* ro (cos fi))(* ro (sin fi))))

Uma vez que a função +pol está definida em termos da função +xy e estajá sabe tratar coordenadas tri-dimensionais, também +pol saberá tratar coor-denadas tri-dimensionais e, consequentemente, não precisa de ser redefinida.O mesmo se pode dizer das funções +x e +y que apenas dependem de +xy.

Finalmente, consideremos a definição da função +c que apresentámos nasecção 5.11 e que se destinava a deslocar um ponto ao longo de um vector(especificado pelas coordenadas da sua extremidade):

(defun +c (p0 p1)(xy (+ (cx p0) (cx p1))

(+ (cy p0) (cy p1))))

103

É evidente pela definição da função que ela não é aplicável ao caso tri-dimensional pois só lida com as coordenadas x e y de ambos os pontos. Paralidar correctamente com o caso tri-dimensional teremos de ter em conta quequalquer um dos parâmetros pode corresponder a um ponto em coordenadastri-dimensionais e que será apenas quando ambos os parâmetros forem coorde-nadas bi-dimensionais que poderemos usar a definição anterior. Assim, temos:

(defun +c (p0 p1)(if (and (xy? p0) (xy? p1))

(xy (+ (cx p0) (cx p1))(+ (cy p0) (cy p1)))

(xyz (+ (cx p0) (cx p1))(+ (cy p0) (cy p1))(+ (cz p0) (cz p1)))))

Agora, temos:

_$ (+c (xy 1 2) (xy 3 4))(4 6)_$ (+c (xy 1 2) (xyz 3 4 5))(4 6 5)_$ (+c (xyz 0 1 2) (xy 3 4))(3 5 2)_$ (+c (xyz 0 1 2) (xyz 3 4 5))(3 5 7)

Para além da “soma” de um vector a um ponto é por vezes útil considerara operação inversa, que “subtrai” um vector a um ponto, i.e., que determina aorigem do vector cujo destino é o ponto dado ou, equivalentemente, as projec-ções do vector que liga os dois pontos dados. Por analogia com a operação +c,vamos denominar esta operação de -c e a sua definição será, obviamente:

(defun -c (p0 p1)(if (and (xy? p0) (xy? p1))

(xy (- (cx p0) (cx p1))(- (cy p0) (cy p1)))

(xyz (- (cx p0) (cx p1))(- (cy p0) (cy p1))(- (cz p0) (cz p1)))))

Agora, temos:

_$ (-c (xy 4 6) (xy 3 4))(1 2)_$ (-c (xy 4 6) (xy 1 2))(3 4))_$ (-c (xyz 4 6 3) (xyz 1 2 3))(3 4 0)_$ (-c (xyz 4 6 5) (xy 1 2))(3 4 5)_$ (-c (xyz 5 7 9) (xyz 1 2 3))(4 5 6)

Pode ser também útil poder multiplicar uma coordenada por um factor,correspondendo a uma simples homotetia. Mais uma vez, temos de distinguiros dois casos de pontos bi- e tri-dimensionais:

104

(defun *c (p f)(if (xy? p)

(xy (* (cx p) f)(* (cy p) f))

(xyz (* (cx p) f)(* (cy p) f)(* (cz p) f))))

Como se pode ver pelas definições anteriores, a estrutura de controle deselecção é fundamental para se poder obter os diferentes comportamentos ne-cessários para tratar o caso de coordenadas Cartesianas bi-dimensionais outri-dimensionais.

Finalmente, para além das operações de adição e subtracção de coordena-das, pode ser útil uma operação de comparação de coordenadas que designa-remos por =c. Duas coordenadas serão iguais se as suas componentes x, y e zforem iguais, i.e.:

(defun =c (c0 c1)(and (= (cx c0) (cx c1))

(= (cy c0) (cy c1))(= (cz c0) (cz c1))))

A função anterior funciona igualmente bem com coordenadas bi-dimensionais,tri-dimensionais ou mistas pois elas só poderão diferir na presença, ou não, dacoordenada z mas o selector cz encarrega-se de esconder essa diferença.

O conjunto de funções que descrevemos atrás e que inclui os construtoresxy, +xy, xyz, +xyz, +x, +y, +z, +c, -c e *c, os selectores cx, cy e cz, os reco-nhecedores xy? e xyz? e, finalmente, o teste =c constituem um tipo abstractopara coordenadas Cartesianas que iremos frequentemente usar de futuro.

Para além das coordenadas Cartesianas, é ainda usual empregarem-se doisoutros sistemas de coordenadas que iremos ver de seguida: as coordenadas ci-líndricas e as coordenadas esféricas.

8.12 Coordenadas Cilíndricas

Tal como podemos verificar na Figura 28, um ponto, em coordenadas cilíndri-cas, caracteriza-se por um raio ρ assente no plano z = 0, um ângulo φ que esseraio faz com o eixo x e por uma cota z. É fácil de ver que o raio e o ângulocorrespondem às coordenadas polares da projecção do ponto no plano z = 0.

Dado um ponto (ρ, φ, z) em coordenadas cilíndricas, o mesmo ponto emcoordenadas Cartesianas é

(ρ cosφ, ρ sinφ, z)

De igual modo, dado um ponto (x, y, z) em coordenadas Cartesianas, omesmo ponto em coordenadas cilíndricas é

(√x2 + y2, atan

y

x, z)

Estas equivalências permitem-nos definir uma função que constrói pontosem coordenadas cilíndricas empregando as coordenadas Cartesianas como re-presentação canónica:

105

y

z

x

P

z

ρ

φ

Figura 28: Coordenadas cilíndricas.

(defun cil (ro fi z)(xyz (* ro (cos fi))

(* ro (sin fi))z))

No caso de pretendermos simplesmente somar a um ponto um vector emcoordenadas cilíndricas, podemos tirar partido da função +c e definir:

(defun +cil (p ro fi z)(+c p (cil ro fi z)))

Exercício 8.12.1 Defina os selectores cil-ro, cil-fi e cil-z que devolvem, respectivamente,os componentes ρ, φ e z de um ponto construído pelo construtor cil.

Existem inúmeras curvas, superfícies e volumes que são mais simples-mente descritos usando coordenadas cilíndricas do que usando coordenadasrectangulares. Por exemplo, a equação paramétrica de uma hélice, em coorde-nadas cilíndricas, é trivial:

(ρ, φ, z) = (1, t, t)

No entanto, em coordenadas rectangulares, somos obrigados a empregar al-guma trigonometria para obter a equação equivalente:

(x, y, z) = (cos t, sin t, t)

Exercício 8.12.2 Defina uma função escadas capaz de construir uma escada em hélice. Aimagem seguinte mostra três exemplos destas escadas.

106

Como se pode ver pela imagem anterior, a escada é constituída por um cilindro central noqual se apoiam 10 degraus cilíndricos. Para facilitar a experimentação considere que o cilindrocentral é de raio r. Os degraus são iguais ao cilindro central, possuem 10 raios de comprimentoe estão dispostos a alturas progressivamente crescentes. Cada degrau está a uma altura h emrelação ao degrau anterior e, visto em planta, faz um ângulo α com o degrau anterior.

A função escada deverá receber as coordenadas do centro da base do cilindro central, oraio r, a altura h e o ângulo α. A título de exemplo, considere que as três escadas anterioresforam construídas pelas seguintes invocações:

(escada (xyz 0 0 0) 1.0 3 (/ pi 6))(escada (xyz 0 40 0) 1.5 5 (/ pi 9))(escada (xyz 0 80 0) 0.5 6 (/ pi 8))

8.13 Coordenadas Esféricas

Tal como podemos ver na Figura 29, um ponto, em coordenadas esféricas (tam-bém denominadas polares), caracteriza-se pelo comprimento ρ do raio vector,por um ângulo φ (denominado longitude ou azimute) que a projecção desse vec-tor no plano z = 0 faz com o eixo x e por ângulo ψ (denominado colatitude48,zénite ou ângulo polar) que o vector faz com o eixo z.

Dado um ponto (ρ, φ, ψ) em coordenadas esféricas, o mesmo ponto em co-ordenadas Cartesianas é

(ρ sinψ cosφ, ρ sinψ sinφ, ρ cosψ)

De igual modo, dado um ponto (x, y, z) em coordenadas Cartesianas, omesmo ponto em coordenadas esféricas é

(√x2 + y2 + z2, atan

y

x, atan

√x2 + y2

z)

Tal como fizemos para as coordenadas cilíndricas, vamos definir o constru-tor de coordenadas esféricas e a soma de um ponto a um vector em coordena-das esféricas:

48A colatitude é, como é óbvio, o ângulo complementar à latitude, i.e., a diferença entre opólo (π

2) e a latitude.

107

y

z

x

P

ψ ρ

φ

Figura 29: Coordenadas Esféricas

(defun esf (ro fi psi)(xyz (* ro (sin psi) (cos fi))

(* ro (sin psi) (sin fi))(* ro (cos psi))))

(defun +esf (p ro fi psi)(+c p (esf ro fi psi)))

Exercício 8.13.1 Defina os selectores esf-ro, esf-fi e esf-psi que devolvem, respectiva-mente, os componentes ρ, φ e ψ de um ponto construído pelo construtor esf.

8.14 Recursão

Vimos que as nossas funções, para fazerem algo útil, precisam de invocar ou-tras funções. Por exemplo, se já tivermos a função que calcula o quadradode um número e pretendermos definir a função que calcula o cubo de um nú-mero, podemos facilmente fazê-lo à custa do quadrado e de uma multiplicaçãoadicional, i.e.:

(defun cubo (x)(* (quadrado x) x))

Do mesmo modo, podemos definir a função quarta-potencia à custado cubo e de uma multiplicação adicional, i.e.:

(defun quarta-potencia (x)(* (cubo x) x))

Como é óbvio, podemos continuar a definir sucessivamente novas funçõespara calcular potências crescentes mas isso não só é moroso como será semprelimitado. Seria muito mais útil podermos generalizar o processo e definir sim-plesmente a função potência que, a partir de dois números (a base e o expoente)calcula o primeiro elevado ao segundo.

108

No entanto, aquilo que fizemos para a quarta-potencia, o cubo e oquadrado dão-nos uma pista muito importante: se tivermos uma função quecalcula a potência de expoente imediatamente inferior, então basta-nos uma multipli-cação adicional para calcular a potência seguinte.

Dito de outra forma, temos:

(defun potencia (x n)(* (potencia-inferior x n) x))

Embora tenhamos conseguido simplificar o problema do cálculo de po-tência, sobrou uma questão por responder: como podemos calcular a potên-cia imediatamente inferior? A resposta poderá não ser óbvia mas, uma vezpercebida, é trivial: a potência imediatamente inferior à potência de expoente n éa potência de expoente (n − 1). Isto implica que (potencia-inferior x n)é exactamente o mesmo que (potencia x (- n 1)). Com base nesta ideia,podemos reescrever a definição anterior:

(defun potencia (x n)(* (potencia x (- n 1)) x))

Apesar da nossa solução engenhosa, esta definição tem um problema: qual-quer que seja a potência que tentemos calcular, nunca conseguiremos obter oresultado final. Para percebermos este problema, é mais simples usar um casoreal: tentemos calcular a terceira potência do número 4, i.e., (potencia 4 3).

Para isso, de acordo com a definição da função potencia, será precisoavaliar a expressão(* (potencia 4 2) 4)que, por sua vez, implica avaliar(* (* (potencia 4 1) 4) 4)que, por sua vez, implica avaliar(* (* (* (potencia 4 0) 4) 4) 4)que, por sua vez, implica avaliar(* (* (* (* (potencia 4 -1) 4) 4) 4) 4)que, por sua vez, implica avaliar(* (* (* (* (* (potencia 4 -2) 4) 4) 4) 4) 4)que, por sua vez, implica avaliar(* (* (* (* (* (* (potencia 4 -3) 4) 4) 4) 4) 4) 4)que, por sua vez, implica avaliar . . .

É fácil vermos que este processo nunca mais termina. O problema está nofacto de termos reduzido o cálculo da potência de um número elevado a umexpoente ao cálculo da potência desse número elevado ao expoente imediata-mente inferior mas não dissémos em que situação é que já temos um expoentesuficientemente simples cuja solução seja imediata. Quais são as situações emque isso acontece? Já vimos que quando o expoente é 2, a função quadradodevolve o resultado correcto, pelo que o caso n = 2 é já suficientemente sim-ples. No entanto, é possível ter um caso ainda mais simples: quando o expo-ente é 1, o resultado é simplesmente a base. Finalmente, o caso mais simplesde todos: quando o expoente é zero, o resultado é 1, independentemente da

109

base. Este último caso é fácil de perceber quando vemos que a avaliação de(potencia 4 2) (i.e., do quadrado de quatro) se reduz, em última análise,a (* (* (potencia 4 0) 4) 4). Para que esta expressão seja equivalente a(* 4 4) é necessário que (potencia 4 0) seja 1.

Estamos então em condições de definir correctamente a função potencia:

1. Quando o expoente é zero, o resultado é um.

2. Caso contrário, calculamos a potência de expoente imediatamente infe-rior e multiplicamo-la pela base.

(defun potencia (x n)(if (zerop n)

1(* (potencia x (- n 1)) x)))

A função anterior é um exemplo de uma função recursiva, i.e., uma funçãoque está definida em termos de si própria. Dito de outra forma, uma funçãorecursiva é uma função que se usa a si própria na sua definição. Esse uso éóbvio quando “desenrolamos” a avaliação de (potencia 4 3):

(potencia 4 3)↓

(* (potencia 4 2) 4)↓

(* (* (potencia 4 1) 4) 4)↓

(* (* (* (potencia 4 0) 4) 4) 4)↓

(* (* (* 1 4) 4) 4)↓

(* (* 4 4) 4)↓

(* 16 4)↓64

A recursão é a estrutura de controle que permite que uma função se possainvocar a si própria durante a sua própria avaliação. A recursão é uma dasmais importantes ferramentas de programação pelo que é fundamental que apercebamos bem. Muitos problemas aparentemente complexos possuem so-luções recursivas extraordinariamente simples.

Existem inúmeros exemplos de funções recursivas. Uma das mais simplesé a função factorial que se define matematicamente como:

n! =

{1, se n = 0n · (n− 1)!, caso contrário.

A tradução desta fórmula para Lisp é directa:

110

(defun factorial (n)(if (zerop n)

1(* n (factorial (- n 1)))))

É importante repararmos que em todas as funções recursivas existe:

• Um caso básico (também chamado caso de paragem) cujo resultado é ime-diatamente conhecido.

• Um caso não básico (também chamado caso recursivo) em que se trans-forma o problema original num sub-problema mais simples.

Se analisarmos a função factorial, o caso básico é o teste de igualdade azero (zerop n), o resultado imediato é 1, e o caso recursivo é, obviamente,(* n (factorial (- n 1))).

Geralmente, uma função recursiva só está correcta se tiver uma expressãocondicional que identifique o caso básico, mas não é obrigatório que assim seja.A invocação de uma função recursiva consiste em ir resolvendo subproblemassucessivamente mais simples até se atingir o caso mais simples de todos, cujoresultado é imediato. Desta forma, o padrão mais comum para escrever umafunção recursiva é:

• Começar por testar os casos básicos.

• Fazer uma invocação recursiva com um subproblema cada vez mais pró-ximo de um caso básico.

• Usar o resultado da invocação recursiva para produzir o resultado dainvocação original.

Dado este padrão, os erros mais comuns associados às funções recursivassão, naturalmente:

• Não detectar um caso básico.

• A recursão não diminuir a complexidade do problema, i.e., não passarpara um problema mais simples.

• Não usar correctamente o resultado da recursão para produzir o resul-tado originalmente pretendido.

Repare-se que uma função recursiva que funciona perfeitamente para oscasos para que foi prevista pode estar completamente errada para outros ca-sos. A função factorial é um exemplo: quando o argumento é negativo,o problema torna-se cada vez mais complexo, cada vez mais longe do casosimples:

111

(factorial -1)↓

(* -1 (factorial -2))↓

(* -1 (* -2 (factorial -3)))↓

(* -1 (* -2 (* -3 (factorial -4))))↓

(* -1 (* -2 (* -3 (* -4 (factorial -5))))))↓

(* -1 (* -2 (* -3 (* -4 (* -5 (factorial -6))))))↓

...

O caso mais frequente de erro numa função recursiva é a recursão nuncaparar, ou porque não se detecta correctamente o caso básico ou por a recursãonão diminuir a complexidade do problema. Neste caso, o número de invoca-ções recursivas cresce indefinidamente até esgotar a memória do computador,altura em que o programa gera um erro. No caso do Auto Lisp, esse erro não éinteiramente óbvio pois o avaliador apenas interrompe a avaliação sem apre-sentar qualquer resultado. Eis um exemplo:

_$ (factorial 3)6_$ (factorial -1)_$

É muito importante compreendermos bem o conceito de recursão. Em-bora a princípio possa ser difícil abarcar por completo as implicações desteconceito, a recursão permite resolver, com enorme simplicidade, problemasaparentemente muito complexos.

Como iremos ver, também em arquitectura a recursão é um conceito fun-damental.

Exercício 8.14.1 A função de Ackermann é definida para números não negativos da seguinteforma:

A(m,n) =

8><>:n+ 1 se m = 0

A(m− 1, 1) se m > 0 e n = 0

A(m− 1, A(m,n− 1)) se m > 0 e n > 0

Defina, em Lisp, a função de Ackermann.

Exercício 8.14.2 Indique o valor de

1. (ackermann 0 8)

2. (ackermann 1 8)

3. (ackermann 2 8)

4. (ackermann 3 8)

5. (ackermann 4 8)

112

Exercício 8.14.3 Defina uma função denominada escada que, dado um ponto P , um númerode degraus, o comprimento do espelho e e o comprimento do cobertor c de cada degrau, dese-nha a escada em AutoCad com o primeiro espelho a começar a partir do ponto dado, tal comose vê na imagem seguinte:

c

e

P

Exercício 8.14.4 Defina uma função equilibrio-circulos capaz de criar qualquer uma dasfiguras apresentadas em seguida:

Note que os círculos possuem raios que estão em progressão geométrica de razão f , com0 < f < 1. Assim, cada círculo (excepto o primeiro) tem um raio que é o produto de f peloraio do círculo maior em que está apoiado. O círculo mais pequeno de todos tem raio maior ouigual a 1. A sua função deverá ter como parâmetros o centro e o raio do círculo maior e ainda ofactor de redução f .

Exercício 8.14.5 Considere o desenho de círculos em torno de um centro, tal como apresentadona seguinte imagem:

x

y

p

r0

r1

φ

∆φ

Escreva uma função denominada circulos-concentricos que, a partir das coorde-nadas p do centro de rotação, do número de círculos n, do raio de translacção r0, do raio de

113

circunferência r1, do ângulo inicial φ e do incremento de ângulo ∆φ, desenha os círculos talcomo apresentados na figura anterior.

Teste a sua função com os seguintes expressões:

(circulos-concentricos (xy 0 0) 10 10 2 0 (/ pi 5))(circulos-concentricos (xy 25 0) 20 10 2 0 (/ pi 10))(circulos-concentricos (xy 50 0) 40 10 2 0 (/ pi 20))

cuja avaliação deverá gerar a imagem seguinte:

Exercício 8.14.6 Considere o desenho de flores simbólicas compostas por um círculo interiorem torno da qual estão dispostos círculos concêntricos correspondentes a pétalas. Estes círculosdeverão ser tangentes uns aos outros e ao círculo interior, tal como se apresenta na seguinteimagem:

Defina a função flor que recebe apenas o ponto correspondente ao centro da flor, o raiodo círculo interior e o número de pétalas.

Teste a sua função com as expressões

(flor (xy 0 0) 5 10)(flor (xy 18 0) 2 10)(flor (xy 40 0) 10 20)

que deverão gerar a imagem seguinte:

114

8.15 Depuração de Programas Recursivos

Vimos, na secção 5.19 que os erros de um programa se podem classificar emtermos de erros sintáticos ou erros semânticos. Os erros sintáticos ocorremquando escrevemos frases inválidas na linguagem. Como exemplo de errosintático, consideremos a seguinte definição da função potencia onde nosesquecemos da lista de parâmetros:

(defun potencia(if (= n 0)

1(* (potencia x (- n 1)) x)))

Este esquecimento é o suficiente para baralhar o Auto Lisp: ele esperavaencontrar a lista de parâmetros imediatamente a seguir a nome da função eque, no lugar dela, encontra uma lista que começa com o símbolo if. Istofaz com que o Auto Lisp erradamente acredite que o símbolo if é o nome doprimeiro parâmetro e, daí para a frente, a sua confusão só aumenta. A dadomomento, quando o Auto Lisp deixa, por completo, de compreender aquiloque se pretendia definir, apresenta-nos um erro.

Os erros semânticos são mais complexos que os sintáticos pois, em geral,só podem ser detectados durante a execução do programa.49 Por exemplo, setentarmos calcular o factorial de uma string iremos ter um erro, tal como oseguinte exemplo mostra:

_$ (factorial 5)120_$ (factorial "cinco"); error: bad argument type: numberp: "cinco"

Este último erro, como é óbvio, não tem a ver com a regras da gramática doLisp: a “frase” da invocação da função factorial está correcta. O problema

49Há casos de erros semânticos que podem ser detectados antes da execução do programamas essa detecção depende muito da qualidade da implementação da linguagem e da sua ca-pacidade em antecipar as consequências dos programas.

115

está no facto de não fazer sentido calcular o factorial de uma string pois ocálculo do factorial envolve operações aritméticas e estas não são aplicáveis astrings. Assim sendo, o erro tem a ver com o significado da “frase” escrita, i.e.,com a semântica. Trata-se, portanto, de um erro semântico.

A recursão infinita é outro exemplo de um erro semântico. Vimos que afunção factorial só pode ser invocada com um argumento não negativo ouprovoca recursão infinita. Consequentemente, se usarmos um argumento ne-gativo estaremos a cometer um erro semântico.

8.15.1 Trace

Vimos, na secção5.19, que o Visual Lisp disponibiliza vários mecanismos parafazermos a detecção de erros. Um dos mais simples é a forma trace que per-mite visualizar as invocações das funções. A forma trace recebe o nome dasfunções cuja invocação se pretende analisar e altera essas funções de forma aque elas escrevam as sucessivas invocações com os respectivos argumentos,bem como o resultado da invocação. Se o ambiente do Visual Lisp estiveractivo, a escrita é feita numa janela especial do ambiente do Visual Lisp deno-minada Trace,50 caso contrário, é feita na janela de comandos do AutoCad. Ainformação apresentada em resultado do trace é, em geral, extremamente útilpara a depuração das funções.

Por exemplo, para visualizarmos uma invocação da função factorial,consideremos a seguinte interacção:

_$ (trace factorial)FACTORIAL_$ (factorial 5)120

Em seguida, se consultarmos o conteúdo da janela de Trace, encontramos:

Entering (FACTORIAL 5)Entering (FACTORIAL 4)

Entering (FACTORIAL 3)Entering (FACTORIAL 2)Entering (FACTORIAL 1)Entering (FACTORIAL 0)Result: 1

Result: 1Result: 2

Result: 6Result: 24

Result: 120

Note-se, no texto anterior escrito em consequência do trace, que a invo-cação que fizemos da função factorial aparece encostada à esquerda. Cadainvocação recursiva aparece ligeiramente para a direita, permitindo assim vi-sualizar a “profundidade” da recursão, i.e., o número de invocações recursi-vas. O resultado devolvido por cada invocação aparece alinhado na mesmacoluna dessa invocação.

50Para se visualizar a janela de Trace basta seleccioná-la a partir do menu Window do AutoLisp.

116

É de salientar que a janela de Trace não tem dimensão infinita, pelo que asrecursões excessivamente profundas acabarão por não caber na janela. Nestecaso, o Visual Lisp reposiciona a escrita na coluna esquerda mas indica nume-ricamente o nível de profundidade em que se encontra. Por exemplo, para o(factorial 15), aparece:

Entering (FACTORIAL 15)Entering (FACTORIAL 14)

Entering (FACTORIAL 13)Entering (FACTORIAL 12)Entering (FACTORIAL 11)Entering (FACTORIAL 10)Entering (FACTORIAL 9)Entering (FACTORIAL 8)Entering (FACTORIAL 7)Entering (FACTORIAL 6)

[10] Entering (FACTORIAL 5)[11] Entering (FACTORIAL 4)[12] Entering (FACTORIAL 3)[13] Entering (FACTORIAL 2)[14] Entering (FACTORIAL 1)[15] Entering (FACTORIAL 0)[15] Result: 1[14] Result: 1[13] Result: 2[12] Result: 6[11] Result: 24[10] Result: 120

Result: 720Result: 5040

Result: 40320Result: 362880

Result: 3628800Result: 39916800

Result: 479001600Result: 1932053504

Result: 1278945280Result: 2004310016

No caso de recursões infinitas, apenas são mostradas as últimas invocaçõesrealizadas antes de ser gerado o erro. Por exemplo, para (factorial -1),teremos:

...[19215] Entering (FACTORIAL -19216)[19216] Entering (FACTORIAL -19217)[19217] Entering (FACTORIAL -19218)[19218] Entering (FACTORIAL -19219)[19219] Entering (FACTORIAL -19220)[19220] Entering (FACTORIAL -19221)[19221] Entering (FACTORIAL -19222)[19222] Entering (FACTORIAL -19223)[19223] Entering (FACTORIAL -19224)[19224] Entering (FACTORIAL -19225)...[19963] Entering (FACTORIAL -19964)

117

[19964] Entering (FACTORIAL -19965)[19965] Entering (FACTORIAL -19966)[19966] Entering (FACTORIAL -19967)[19967] Entering (FACTORIAL -19968)[19968] Entering (FACTORIAL -19969)[19969] Entering (FACTORIAL -19970)[19970] Entering (FACTORIAL -19971)[19971] Entering (FACTORIAL -19972)[19972] Entering (FACTORIAL -19973)[19973] Entering (FACTORIAL -19974)[19974] Entering (FACTORIAL -19975)[19975] Entering (FACTORIAL -19976)

Para se parar a depuração de uma função, usa-se a forma especial untrace,que recebe o nome da função ou funções que se pretende retirar do trace.

Exercício 8.15.1 Faça o trace da função potência. Qual é o trace resultante da avaliação (potencia 2 10)?

Exercício 8.15.2 Defina uma função circulos capaz de criar a figura apresentada em seguida:

Note que os círculos possuem raios que estão em progressão geométrica de razão 12

. Ditode outra forma, os círculos mais pequenos têm metade do raio do círculo maior que lhes éadjacente. Os círculos mais pequenos de todos têm raio maior ou igual a 1. A sua funçãodeverá ter como parâmetros apenas o centro e o raio do círculo maior.

Exercício 8.15.3 Defina uma função denominada serra que, dado um ponto P , um númerode dentes, o comprimento c de cada dente e a altura a de cada dente, desenha uma serra emAutoCad com o primeiro dente a começar a partir do ponto P , tal como se vê na imagemseguinte:

c

a

P

Exercício 8.15.4 Defina uma função losangulos capaz de criar a figura apresentada em se-guida:

118

Note que os losângulos possuem dimensões que estão em progressão geométrica de razão12

. Dito de outra forma, os losângulos mais pequenos têm metade do tamanho do losângulomaior em cujas extremidades estão centrados. Os losângulos mais pequenos de todos têm lar-gura maior ou igual a 1. A sua função deverá ter como parâmetros apenas o centro e a largurado losângulo maior.

8.16 Templos Dóricos

Vimos, pelas descrições de Vitrúvio, que os Gregos criaram um elaborado sis-tema de proporções para colunas. Estas colunas eram usadas para a criaçãode pórticos, em que uma sucessão de colunas encimadas por um telhado ser-via de entrada para os edifícios e, em particular, para os templos. Quandoesse arranjo de colunas era avançado em relação ao edifício, denominava-se omesmo de próstilo, classificando-se este pelo número de colunas que possuemem Distilo, Tristilo, Tetrastilo, Pentastilo, Hexastilo, etc. Quando o próstilo sealargava a todo o edifício, colocando colunas a toda a sua volta, denominava-se de peristilo.

Para além de descrever as proporções das colunas, Vitrúvio também expli-cou no seu famoso tratado as regras que se deviam seguir para a separaçãoentre colunas, distinguindo vários casos de templos, desde aqueles em queo espaço entre colunas era muito reduzido (picnostilo) até aos templos comespaçamento excessivamente alargado (araeostilo), passando pelo seu favorito(eustilo) em que o espaço entre colunas é variável, sendo maior nas colunascentrais.

Na prática, nem sempre as regras propostas por Vitrúvio representavam arealidade dos templos e, frequentemente, a distancia intercolunar varia, sendomais reduzida nas extremidades para dar um aspecto mais robusto ao templo.

Para simplificar a nossa implementação, vamos ignorar estes detalhes e, aoinvés de distinguir vários stilo, vamos simplesmente usar como parâmetros ascoordenadas da base do eixo da primeira coluna, a altura da coluna, a sepa-ração entre os eixos das colunas51 (em termos de um “vector” de deslocaçãoentre colunas) e, finalmente, o número de colunas que pretendemos colocar. O“vector” de deslocação entre colunas destina-se a permitir orientar as colunasem qualquer direcção e não só ao longo do eixo dos x ou y.

O raciocínio para a definição desta função é, mais uma vez, recursivo:

51A denominada distância interaxial, por oposição à distância intercolunar que mede o es-paço aberto entre colunas adjacentes.

119

• Se o número de colunas a colocar for zero, então não é preciso fazer nadae podemos simplesmente retornar nil.

• Caso contrário, colocamos uma coluna na coordenada indicada e, recur-sivamente, colocamos as restantes colunas a partir da coordenada que re-sulta de aplicarmos o “vector” de deslocação à coordenada actual. Comosão duas acções que queremos realizar sequencialmente, teremos de usaro operador progn para as agrupar numa acção conjunta.

Traduzindo este raciocínio para Lisp, temos:

(defun colunas-doricas (p altura separacao colunas)(if (= colunas 0)

nil(progn

(coluna-dorica p altura)(colunas-doricas (+c p separacao)

alturaseparacao(- colunas 1)))))

Finalmente, podemos testar a criação das colunas usando, por exemplo:

(colunas-doricas (xy 0 0) 10 (xy 5 0) 8)

cujo resultado está apresentado na Figura 30:A partir do momento em que sabemos construir uma fileira de colunas

torna-se relativamente fácil a construção das quatro fileiras necessárias paraos templos em peristilo. Normalmente, a descrição destes templos faz-se emtermos do número de colunas da fronte e do número de colunas do lado, masassumindo que as colunas dos cantos contam para ambas as medidas. Istoquer dizer que num templo de, por exemplo, 6× 12 colunas existem, na reali-dade, apenas 4 × 2 + 10 × 2 + 4 = 32 colunas. Para a construção do peristilo,para além do número de colunas das frontes e lados, será necessário conhecera distância entre colunas nas frontes e nos lados e, claro, a altura das colunas.

Em termos de algoritmo, vamos começar por construir as frontes, dese-nhando todas as colunas incluindo os cantos. Em seguida construímos os la-dos, tendo em conta que os cantos já foram colocados. Para simplificar, vamosconsiderar que o templo tem as frontes paralelas ao eixo x (e os lados para-lelos ao eixo y) e que a primeira coluna é colocada num ponto P dado comoprimeiro parâmetro. Assim, temos:

(defun peristilo-dorico (pn-fronte n-ladod-fronte d-ladoaltura)

(colunas-doricas paltura (xy d-fronte 0) n-fronte)

(colunas-doricas (+xy p 0 (* (1- n-lado) d-lado))altura (xy d-fronte 0) n-fronte)

(colunas-doricas (+xy p 0 d-lado)altura (xy 0 d-lado) (- n-lado 2))

(colunas-doricas (+xy p (* (1- n-fronte) d-fronte) d-lado)altura (xy 0 d-lado) (- n-lado 2)))

120

Figura 30: Uma perspectiva de um conjunto de oito colunas dóricas com 10unidades de altura e 5 unidades de separação entre os eixos da colunas aolongo do eixo x.

Para um exemplo realista podemos considerar o templo de Segesta que seencontra representado da Figura 12. Este templo é do tipo peristilo, compostopor 6 colunas (i.e., Hexastilo) em cada fronte e 14 colunas nos lados, num totalde 36 colunas de 9 metros de altura. A distância entre os eixos das colunas éde aproximadamente 4.8 metros nas frontes e de 4.6 nos lados. A expressãoque cria o peristilo deste templo é, então:

(peristilo-dorico (xy 0 0) 6 14 4.8 4.6 9)

O resultado da avaliação da expressão anterior está representado na Fi-gura 31

Embora a grande maioria dos templos Gregos fossem de formato rectangu-lar, também foram construídos templos de formato circular a que chamaramTholos. O Santuário de Atenas Pronaia, em Delfos, contém um bom exemplode um destes edifícios. Embora pouco reste deste templo, não é difícil imagi-nar a sua forma original a partir do que ainda é visível na Figura 32.

Para simplificar a construção do Tholos, vamos dividi-lo em duas partes.Numa, iremos desenhar a base e, na outra, iremos posicionar as colunas.

Para o desenho da base, podemos considerar um conjunto de cilindrosachatados, sobrepostos de modo a formar degraus circulares, tal como se apre-senta na Figura 33. Desta forma, a altura total da base ab será dividida empassos de ∆ab e o raio da base também será reduzido em passos de ∆rb.

Para cada cilindro teremos de considerar o seu raio e a altura do espelhodo degrau d-altura. Para passarmos ao cilindro seguinte temos ainda de ter

121

Figura 31: Uma perspectiva do peristilo do templo de Segesta. As colunasforam geradas pela função peristilo-dorico usando, como parâmetros,6 colunas na fronte e 14 no lado, com distância intercolunar de 4.8 metros nafronte e 4.6 metros no lado e colunas de 9 metros de altura.

Figura 32: O Templo de Atenas Pronaia em Delfos, construído no século quartoantes de Cristo. Fotografia de Michelle Kelley

122

x

z

∆rb

rb

∆ab

rp

Figura 33: Corte da base de um Tholos. A base é composta por uma sequênciade cilindros sobrepostos cujo raio de base rb encolhe de ∆rb a cada degrau ecuja altura incrementa ∆ab a cada degrau.

em conta o aumento do raio d-raio devido ao comprimento do cobertor dodegrau. Estes degraus serão construídos segundo um processo recursivo:

• Se o número de degraus a colocar é zero, não é preciso fazer nada.

• Caso contrário, colocamos um degrau (modelado por um cilindro) como raio e a altura do degrau e, recursivamente, colocamos os restantes de-graus em cima deste, i.e., numa cota igual à altura do degrau agora co-locado e com um raio reduzido do comprimento do cobertor do degrauagora colocado.

Este processo é implementado pela seguinte função:

(defun base-tholos (p n-degraus raio d-altura d-raio)(if (= n-degraus 0)

nil(progn

(command "_.cylinder" p raio d-altura)(base-tholos (+xyz p 0 0 d-altura)

(- n-degraus 1)(- raio d-raio)d-alturad-raio))))

Para o posicionamento das colunas, vamos também considerar um pro-cesso em que em cada passo apenas posicionamos uma coluna numa dada

123

y

z

x

ab

rb rp

ap

rp

φ∆φ

Figura 34: Esquema da construção de um Tholos: rb é o raio da base, rp é adistância do centro das colunas ao centro da base, ap é a altura das coluna, abé a altura da base, φ é o ângulo inicial de posicionamento das colunas e ∆φ é oângulo entre colunas.

posição e, recursivamente, colocamos as restantes colunas a partir da posiçãocircular seguinte.

Dada a sua estrutura circular, a construção deste género de edifícios é sim-plificada pelo uso de coordenadas circulares. De facto, podemos conceber umprocesso recursivo que, a partir do raio rp do peristilo e do ângulo inicial φ,coloca uma coluna nessa posição e que, de seguida, coloca as restantes colu-nas usando o mesmo raio mas incrementando o ângulo φ de ∆φ, tal como seapresenta na Figura 34. O incremento angular ∆φ obtém-se pela divisão dacircunferência pelo número n de colunas a colocar, i.e., ∆φ = 2π

n . Uma vezque as colunas se dispõem em torno de um círculo, o cálculo das coordena-das de cada coluna fica facilitado pelo uso de coordenadas polares. Tendo estealgoritmo em mente, a definição da função fica:

(defun colunas-tholos (p n-colunas raio fi d-fi altura)(if (= n-colunas 0)

nil(progn

(coluna-dorica (+pol p raio fi) altura)(colunas-tholos p

(1- n-colunas)raio(+ fi d-fi)d-fialtura))))

Finalmente, definimos a função tholos que, dados os parâmetros neces-

124

Figura 35: Perspectiva do Tholos de Atenas em Delfos, constituído por 20 co-lunas de estilo Dórico, de 4 metros de altura e colocadas num círculo com 7metros de raio.

sários às duas anteriores, as invoca em sequência:

(defun tholos (p n-degraus rb dab drb n-colunas rp ap)(base-tholos p n-degraus rb dab drb)(colunas-tholos (+xyz p 0 0 (* n-degraus dab))

n-colunasrp0(/ 2*pi n-colunas)ap))

A Figura 35 mostra a imagem gerada pela avaliação da seguinte expressão:

(tholos (xyz 0 0 0) 3 7.9 0.2 0.2 20 7 4)

Exercício 8.16.1 Uma observação atenta do Tholos apresentado na Figura 35 mostra que existeum erro: os ábacos das várias colunas são paralelos uns aos outros (e aos eixos das abcissase ordenadas) quando, na realidade, deveriam ter uma orientação radial. Essa diferença é evi-dente quando se compara uma vista de topo do desenho actual (à esquerda) com a mesma vistadaquele que seria o desenho correcto (à direita):

125

Defina uma nova função coluna-dorica-rodada que, para além da altura da coluna,recebe ainda o ângulo de rotação que a coluna deve ter. Uma vez que o fuste e o coxim dacoluna têm simetria axial, esse ângulo de rotação só influencia o ábaco, pelo que deverá tambémdefinir uma função para desenhar um ábaco rodado.

De seguida, redefina a função colunas-tholos de modo a que cada coluna esteja orien-tada correctamente relativamente ao centro do Tholos.

Exercício 8.16.2 Considere a construção de uma torre composta por vários módulos em quecada módulo tem exactamente as mesmas características de um Tholos, tal como se apresentana figura abaixo, à esquerda:

O topo da torre tem uma forma semelhante à da base de um Tholos, embora com maisdegraus.

Defina a função torre-tholos que, a partir do centro da base da torre, do número de mó-dulos, do número de degraus a considerar para o topo e dos restantes parâmetros necessáriospara definir um módulo idêntico a um Tholos, constrói a torre apresentada anteriormente.

Experimente a sua função criando uma torre composta por 6 módulos, com 10 degraus notopo, 3 degraus por módulo, qualquer deles com comprimento de espelho e de cobertor de 0.2,raio da base de 7.9 e 20 colunas por módulo, com raio de peristilo de 7 e altura de coluna de 4.

126

Exercício 8.16.3 Com base na resposta ao exercício anterior, redefina a construção da torre deforma a que a dimensão radial dos módulos se vá reduzindo à medida que se ganha altura, talcomo acontece na torre apresentada no centro da imagem anterior.

Exercício 8.16.4 Com base na resposta ao exercício anterior, redefina a construção da torre deforma a que o número de colunas se vá também reduzindo à medida que se ganha altura, talcomo acontece na torre da direita da imagem anterior.

Exercício 8.16.5 Considere a criação de uma cidade no espaço, composta apenas por cilindroscom dimensões progressivamente mais pequenas, unidos uns aos outros por intermédio deesferas, tal como se apresenta (em perspectiva) na imagem seguinte:

127

Defina uma função que, a partir do centro da cidade e do raio dos cilindros centrais constróiuma cidade semelhante à representada.

8.17 A Ordem Jónica

A voluta foi um dos elementos arquitectónicos introduzidos na transição daOrdem Dórica para a Ordem Jónica. Uma voluta é um ornamento em formade espiral colocado em cada extremo de um capitel Jónico. A Figura 36 mostraum exemplo de um capitel Jónico contendo duas volutas. Embora tenham so-brevivido inúmeros exemplos de volutas desde a antiguidade, nunca foi claroo processo do seu desenho.

Vitrúvio, no seu tratado de arquitectura, descreve a voluta Jónica: umacurva em forma de espiral que se inicia na base do ábaco, desenrola-se numasérie de voltas e junta-se a um elemento circular denominado o olho.

Vitrúvio descreve o processo de desenho da espiral através da composi-ção de quartos de circunferência, começando pelo ponto mais exterior e dimi-nuindo o raio a cada quarto de circunferência, até se dar a conjunção com oolho. Nesta descrição há ainda alguns detalhes por explicar, em particular, oposicionamento dos centros dos quartos de circunferência, mas Vitrúvio refereque será incluida uma figura e um cálculo no final do livro.

Infelizmente, nunca se encontrou essa figura ou esse cálculo, ficando assimpor esclarecer um elemento fundamental do processo de desenho de volutasdescrito por Vitrúvio. As dúvidas sobre esse detalhe tornaram-se ainda maisevidentes quando a análise de muitas das volutas que sobreviveram a antigui-dade revelou diferenças em relação às proporções descritas por Vitrúvio.

Durante a Renascença, estas dúvidas levaram os investigadores a repensaro método de Vitrúvio e a sugerir interpretações pessoais ou novos métodos

128

Figura 36: Volutas de um capitel Jónico. Fotografia de See Wah Cheng.

para o desenho da voluta. De particular relevância foram os métodos propos-tos por:

• Sebastiano Serlio (1537), baseado na composição de semi-circunferências,

• Giuseppe Salviati (1552), baseado na composição de quartos-de-circun-ferência e

• Guillaume Philandrier (1544), baseado na composição de oitavos-de-cir-cunferência.

Todos estes métodos diferem em vários detalhes mas, de forma genérica,todos se baseiam em empregar arcos de circunferência de abertura constantemas raio decrescente. Obviamente, para que haja continuidade nos arcos, oscentros dos arcos vão mudando à medida que estes vão sendo desenhados. AFigura 37 esquematiza o processo para espirais feitas empregando quartos decircunferência.

Como se vê pela figura, para se desenhar a espiral temos de ir desenhandosucessivos quartos de circunferência. O primeiro quarto de circunferência serácentrado no ponto (x0, y0) e terá raio r. Este primeiro arco vai desde o ânguloπ/2 até ao ângulo π. O segundo quarto de circunferência será centrado noponto (x1, y1) e terá raio r · f , sendo f um coeficiente de “redução” da espi-ral. Este segundo arco vai desde o ângulo π até ao ângulo 3

2π. Um detalheimportante é a relação entre as coordenadas p1 = (x1, y1) e p0 = (x0, y0): paraque o segundo arco tenha uma extremidade coincidente com o primeiro arco,o seu centro tem de estar na extremidade do vector ~v0 de origem em p0, com-primento r(1− f) e ângulo igual ao ângulo final do primeiro arco.

Este processo deverá ser seguido para todos os restantes arcos de circunfe-rência, i.e., teremos de calcular as coordenadas (x2, y2), (x3, y3), etc., bem comoos raios r · f · f , r · f · f · f , etc, necessários para traçar os sucessivos arcos decircunferência.

129

(x0, y0)

r

~v0r · f(x1, y1)

~v1

r · f · f

(x2, y2)~v2

(x3, y3)

r · f · f · f

Figura 37: O desenho de uma espiral com arcos de circunferência.

Dito desta forma, o processo de desenho parece ser complicado. No en-tanto, é possivel reformulá-lo de modo a ficar muito mais simples. De facto,podemos pensar no desenho da espiral completa como sendo o desenho de umquarto de circunferência seguido do desenho de uma espiral mais pequena.Mais concretamente, podemos especificar o desenho da espiral de centro noponto p, raio r e ângulo inicial θ como sendo o desenho de um arco de circun-ferência de raio r centrado em p com ângulo inicial θ e final θ + π

2 seguido deuma espiral de centro em p+~v, raio r · f e ângulo inicial θ+ π

2 . O vector ~v teráorigem em p, módulo r(1− f) e ângulo θ + π

2 .Obviamente, sendo este um processo recursivo, é necessário definir o caso

de paragem, havendo (pelo menos) duas possibilidades:

• Terminar quando o raio r é inferior a um determinado limite.

• Terminar quando o ângulo θ é superior a um determinado limite.

Por agora, vamos considerar a segunda possibilidade. De acordo com onosso raciocínio, vamos definir a função que desenha a espiral de modo a re-ceber, como parâmetros, o ponto inicial p, o raio inicial r, o ângulo iniciala-ini, o ângulo final a-fin e o factor de redução f:

(defun espiral (p r a-ini a-fin f)(if (> a-ini a-fin)

nil

130

Figura 38: O desenho da espiral.

(progn(quarto-circunferencia p r a-ini)(espiral (+pol p (* r (- 1 f)) (+ a-ini (/ pi 2)))

(* r f)(+ a-ini (/ pi 2))a-finf))))

Reparemos que a função espiral é recursiva pois está definida em termosde si própria. Obviamente, o caso recursivo é mais simples que o caso originalpois a diferença entre o ângulo inicial e o final é mais pequena, aproximando-se progressivamente do caso de paragem em que o ângulo inicial ultrapassa ofinal.

Para desenhar o quarto de circunferência vamos empregar a operação arcdo Auto Lisp que recebe o centro do circunferência, o ponto inicial do arco eo ângulo em graus. Para melhor percebermos o processo de desenho da espi-ral vamos também traçar duas linhas com origem no centro a delimitar cadaquarto de circunferência. Mais tarde, quando tivermos terminado o desenvol-vimento destas funções, removeremos essas linhas.

Desta forma, o quarto de circunferência apenas precisa de saber o pontop correspondente ao centro da circunferência, o raio r da mesma e o ânguloinicial a-ini:

(defun quarto-circunferencia (p r a-ini)(command "_.arc" "_c" p (+pol p r a-ini) "_a" 90)(command "_.line" p (+pol p r a-ini) "")(command "_.line" p (+pol p r (+ a-ini (/ pi 2))) ""))

Podemos agora experimentar um exemplo:

(espiral (xy 0 0) 10 (/ pi 2) (* pi 6) 0.8)

A espiral traçada pela expressão anterior está representada na Figura 38.

131

A função espiral permite-nos definir um sem-número de espirais, mastem uma restrição: cada arco de círculo corresponde a um ângulo de π

2 . Logi-camente, a função tornar-se-á mais útil se também este incremento de ângulofor um parâmetro.

As modificações a fazer são relativamente triviais, bastando acrescentarum parâmetro a-inc representando o incremento de ângulo de cada arco esubstituir as ocorrências de (/ pi 2) por este parâmetro. Naturalmente, emvez de desenharmos um quarto de circunferência, temos agora de desenharum arco de circunferência.

A nova definição é, então:

(defun espiral (p r a-ini a-inc a-fin f)(if (> a-ini a-fin)

nil(progn

(arco p r a-ini a-inc)(espiral (+pol p (* r (- 1 f)) (+ a-ini a-inc))

(* r f)(+ a-ini a-inc)a-inca-finf))))

A função que desenha o arco é uma variante da que desenha o quarto decircunferência. Apenas é preciso ter em conta que o comando arc pretende oângulo em graus e não em radianos, o que nos obriga a usar uma conversão:

(defun arco (p r a-ini a-inc)(command "_.arc"

"_c" p (+pol p r a-ini)"_a" (graus<-radianos a-inc))

(command "_.line" p (+pol p r a-ini) "")(command "_.line" p (+pol p r (+ a-ini a-inc)) ""))

(defun graus<-radianos (radianos)(* (/ 180 pi) radianos))

Agora, para desenhar a mesma espiral representada na Figura 38, temosde avaliar a expressão:

(espiral (xy 0 0) 10 (/ pi 2) (/ pi 2) (* pi 6) 0.8)

É claro que agora podemos facilmente construir outras espirais. As seguin-tes expressões produzem as espirais representadas na Figura 39:

(espiral (xy 0 0) 10 (/ pi 2) (/ pi 2) (* pi 6) 0.9)

(espiral (xy 20 0) 10 (/ pi 2) (/ pi 2) (* pi 6) 0.7)

(espiral (xy 40 0) 10 (/ pi 2) (/ pi 2) (* pi 6) 0.5)

Outra possibilidade de variação está no ângulo de incremento. As seguin-tes expressões experimentam aproximações aos processos de Sebastiano Serlio

132

Figura 39: Várias espirais com razões de redução de 0.9, 0.7 e 0.5, respectiva-mente.

Figura 40: Várias espirais com razão de redução de 0.8 e incremento de ângulode π, π2 e π

4 , respectivamente.

(semi-circunferências), Giuseppe Salviati (quartos-de-circunferência) e Guil-laume Philandrier (oitavos-de-circunferência):52

(espiral (xy 0 0) 10 (/ pi 2) pi (* pi 6) 0.8)

(espiral (xy 20 0) 10 (/ pi 2) (/ pi 2) (* pi 6) 0.8)

(espiral (xy 40 0) 10 (/ pi 2) (/ pi 4) (* pi 6) 0.8)

Os resultados estão representados na Figura 40.

Exercício 8.17.1 Um óvulo é uma figura geomética que corresponde ao contorno dos ovos dasaves. Dada a variedade de formas de ovos na Natureza, é natural considerar que também existe

52Note-se que se trata, tão somente, de aproximações. Os processos originais eram bastantemais complexos.

133

uma grande variedade de óvulos geométrivos. A figura seguinte apresenta alguns exemplos emque se variam sistematicamente alguns dos parâmetros que caracterizam o óvulo:

Um óvulo é composto por quatro arcos de circunferência concordantes, tal como se apre-senta na imagem seguinte:

P

r0

r2 r2

α α

r1

Os arcos de círculos necessários para a construção do ovo são caracterizados pelos raiosr0, r1 e r2. Note que o arco de circunferência de raio r0 cobre um ângulo de π e o arco decircunferência de raio r2 cobre um ângulo de α.

Defina uma função ovo que desenha um ovo. A função deverá receber, apenas, as coorde-nadas do ponto P , os raios r0 e r1 e, finalmente, a altura h do ovo.

8.18 Recursão na Natureza

A recursão está presente em inúmeros fenómenos naturais. As montanhas, porexemplo, apresentam irregularidades que, quando observadas numa escalaapropriada, são em tudo idênticas a . . . montanhas. Um rio possui afluentes e

134

Figura 41: A estrutura recursiva das árvores. Fotografia de Michael Bezzina.

cada afluente é idêntico a . . . um rio. Uma vaso sanguíneo possui ramificaçõese cada ramificação é idêntica a . . . um vaso sanguíneo. Todas estas entidadesnaturais constituem exemplos de estruturas recursivas.

Uma árvore é outro bom exemplo de uma estrutura recursiva pois os ramosde uma árvore são como pequenas árvores que emanam do tronco. Como sepode ver da Figura 41, de cada ramo de uma árvore emanam outras pequenasárvores, num processo que se repete até se atingir uma dimensão suficiente-mente pequena em que aparecem outras estruturas como folhas, flores, frutos,pinhas, etc.

Se, de facto, uma árvore possui uma estrutura recursiva então deverá serpossível “construir” árvores através de funções recursivas. Para testarmos estateoria, vamos começar por considerar uma versão muito simplista de uma ár-vore, em que temos um tronco que, a partir de uma certa altura, se divideem dois. Cada um destes subtroncos cresce fazendo um certo ângulo com otronco de onde emanou e com um comprimento que deverá ser uma fracçãodo comprimento desse tronco, tal como se apresenta na Figura 42. O caso deparagem ocorre quando o comprimento do tronco se tornou tão pequeno que,em vez de se continuar a divisão, aparece simplesmente uma outra estrutura.Para simplificar, vamos designar a extremidade de um ramo por folha e iremosrepresentá-la com um pequeno círculo.

Para darmos dimensões à árvore, vamos considerar que a função arvorerecebe, como argumento, as coordenadas da base da árvore, o comprimentodo tronco e o ângulo actual do tronco. Para a fase recursiva, teremos comoparâmetros o ângulo de abertura alpha que o novo tronco deverá fazer com

135

(x0, y0)

αα

c

f · cf · c

Figura 42: Parâmetros de desenho de uma árvore.

o actual e o factor de redução f do comprimento do tronco. O primeiro passoé a computação do topo do tronco usando a função +pol. Em seguida, dese-nhamos o tronco desde a base até ao topo. Finalmente, testamos se o troncodesenhado é suficientemente pequeno. Se for, terminamos com o desenho deum círculo centrado no topo. Caso contrário fazemos uma dupla recursão paradesenhar uma sub-árvore para a direita e outra para a esquerda. A definiçãoda função fica:

(defun arvore (base comprimento angulo alfa f / topo)(setq topo (+pol base comprimento angulo))(ramo base topo)(if (< comprimento 2)

(folha topo)(progn

(arvore topo(* comprimento f)(+ angulo alfa)alfa f)

(arvore topo(* comprimento f)(- angulo alfa)alfa f))))

(defun ramo (base topo)(command "_.line" base topo ""))

(defun folha (topo)(command "_.circle" topo 0.2))

Um primeiro exemplo de árvore gerado com a expressão

(arvore (xy 0 0) 20 (/ pi 2) (/ pi 8) 0.7)

está representado na Figura 43.A Figura 44 apresenta outros exemplos em que se fez variar o ângulo de

abertura e o factor de redução. A sequência de expressões que as gerou foi aseguinte:

136

Figura 43: Uma “árvore” de comprimento 20, ângulo inicial π2 , abertura π

8 efactor de redução 0.7.

Figura 44: Várias “árvores” geradas com diferentes ângulos de abertura e fac-tores de redução do comprimento dos ramos.

137

(x0, y0)

αdαe

c

fd · cfe · c

Figura 45: Parâmetros de desenho de uma árvore com crescimento assimétrico.

(arvore (xy 0 0) 20 (/ pi 2) (/ pi 8) 0.6)

(arvore (xy 100 0) 20 (/ pi 2) (/ pi 8) 0.8)

(arvore (xy 200 0) 20 (/ pi 2) (/ pi 6) 0.7)

(arvore (xy 300 0) 20 (/ pi 2) (/ pi 12) 0.7)

Infelizmente, as árvores apresentadas são “excessivamente” simétricas: nomundo natural é literalmente impossível encontrar simetrias perfeitas. Poreste motivo, convém tornar o modelo um pouco mais sofisticado através daintrodução de parâmetros diferentes para o crescimento dos troncos à direita eà esquerda. Para isso, em vez de termos um só ângulo de abertura e um só fac-tor de redução de comprimento, vamos empregar dois, tal como se apresentana Figura 45.

A adaptação da função arvore para lidar com os parâmetros adicionais étrivial:

(defun arvore (base comprimento anguloalfa-e f-e alfa-d f-d/ topo)

(setq topo (+pol base comprimento angulo))(ramo base topo)(if (< comprimento 2)

(folha topo)(progn

(arvore topo(* comprimento f-e)(+ angulo alfa-e)alfa-e f-e alfa-d f-d)

(arvore topo(* comprimento f-d)(- angulo alfa-d)alfa-e f-e alfa-d f-d))))

A Figura 46 apresenta novos exemplos de árvores com diferentes ângulosde abertura e factores de redução dos ramos esquerdo e direito, geradas pelasseguintes expressões:

138

Figura 46: Várias árvores geradas com diferentes ângulos de abertura e facto-res de redução do comprimento para os ramos esquerdo e direito.

(arvore (xy 0 0) 20 (/ pi 2) (/ pi 8) 0.6 (/ pi 8) 0.7)

(arvore (xy 100 0) 20 (/ pi 2) (/ pi 4) 0.7 (/ pi 16) 0.7)

(arvore (xy 200 0) 20 (/ pi 2) (/ pi 6) 0.6 (/ pi 16) 0.8)

As árvores geradas pela função arvore são apenas um modelo muitogrosseiro da realidade. Embora existam sinais evidentes de que vários fenó-menos naturais se podem modelar através de funções recursivas, a naturezanão é tão determinista quanto as nossas funções e, para que a modelação seaproxime mais da realidade, é fundamental incorporar também alguma alea-toriedade. Esse será o tema da próxima seccção.

139

Função Argumentos Resultado+ Vários núme-

rosA adição de todos os argumentos. Sem argu-mentos, devolve zero.

- Vários núme-ros

Com apenas um argumento, o seu simétrico.Com mais de um argumento, a subtracção aoprimeiro argumento de todos os restantes. Semargumentos, zero.

* Vários núme-ros

A multiplicação de todos os argumentos. Semargumentos, zero.6

/ Vários núme-ros

A divisão do primeiro argumento por todos osrestantes. Sem argumentos, devolve zero.7

1+ Um número A soma do argumento com um.1- Um número A substracção do argumento com um.abs Um número O valor absoluto do argumento.sin Um número

(em radianos)O seno do argumento .

cos Um número(em radianos)

O cosseno do argumento.

atan Um ou doisnúmeros

Com um argumento, o arco tangente do argu-mento (em radianos). Com dois argumentos, oarco tangente da divisão do primeiro pelo se-gundo (em radianos). O sinal dos argumentosé usado para determinar o quadrante.

sqrt Um númeronão negativo

A raiz quadrada do argumento.

exp Um número A exponencial de base e.expt Dois números O primeiro argumento elevado ao segundo ar-

gumento.log Um número

positivoO logaritmo natural do argumento.

max Vários núme-ros

O maior dos argumentos.

min Vários núme-ros

O menor dos argumentos.

rem Dois ou maisnúmeros

Com dois argumentos, o resto da divisão do pri-meiro pelo segundo. Com mais argumentos, oresto da divisão do resultado anterior pelo ar-gumento seguinte.

fix Um número O argumento sem a parte fraccionária.float Um número O argumento convertido em número real.gcd Dois números O maior divisor comum dos dois argumentos.

Tabela 1: Funções matemáticas pré-definidas do Auto Lisp.

140

Função Argumentos Resultadostrcat Várias strings A concatenação de todos os argumentos. Sem

argumentos, a string vazia .strlen Várias strings O número de caracteres da concatenação das

strings. Sem argumentos, devolve zero.substr Uma string,

um inteiro (oíndice) e, op-cionalmente,outro inteiro(o número decaracteres)

A parte da string que começa no índice dadoe que tem o número de caracteres dado ou to-dos os restantes caracteres no caso de esse nú-mero não ter sido dado.

strcase Uma string eum boleanoopcional

Com um argumento ou segundo argumentonil, a conversão para maiúsculas do argu-mento, caso contrário, conversão para minús-culas.

atof Uma string O número real cuja representação textual é oargumento.

atoi Uma string O número, sem a parte fraccionária, cuja re-presentação textual é o argumento.

itoa Um número A representação textual do argumento.rtos Um, dois ou

três númerosA representação textual do argumento, deacordo com o modo especificado no segundoargumento e com a precisão especificada noterceiro argumento.

Tabela 2: Operações pré-definidas envolvendo strings.

141