Escolar Documentos
Profissional Documentos
Cultura Documentos
Apostila Lisp
Apostila Lisp
Linguagens de Programao II
Programao Funcional usando LISP
Sumrio
1 - Elementos da Linguagem .............................................................................................. 4 1.1 - Elementos primitivos .......................................................................................... 4 1.2 - Combinaes ....................................................................................................... 5 1.3 - Avaliao de Combinaes ............................................................................... 7 1.4 - Definio de Funes......................................................................................... 8 1.5 Funes bsicas .............................................................................................. 10 1.6 Testando nmeros ........................................................................................... 12 1.7 Avaliando funes............................................................................................ 14 1.8 - Smbolos............................................................................................................. 15 1.9 Inserindo comentrios.......................................................................................... 16 2 Expresses Condicionais ............................................................................................ 17 2.1 - Predicados ......................................................................................................... 17 2.2 - Operadores Lgicos ......................................................................................... 18 2.3 - Seleo simples ................................................................................................ 19 2.4 - Seleo Mltipla ................................................................................................ 19 3 - Funes........................................................................................................................ 21 3.1 - Funes Recursivas ......................................................................................... 21 3.2 Depurao de funes .......................................................................................... 23 3.3 Funes de ordem superior .................................................................................. 23 3.4 Lambda ................................................................................................................ 25 3.5 Variveis Locais................................................................................................... 25 3.6 Funes Locais..................................................................................................... 27 4 mbito e Durao ....................................................................................................... 29 4.1 mbito de uma referncia.................................................................................... 29 4.2 Durao de uma referncia .................................................................................. 30 5 Dados .......................................................................................................................... 32 5.1 - tomos.................................................................................................................. 32 5.2 Combinao de dados .......................................................................................... 33 5.3 Abstrao de dados .............................................................................................. 34 5.4 - Tipos Abstratos de Informao............................................................................. 36 6 Entrada e sada ............................................................................................................ 38 6.1 Leitura de dados a partir do teclado..................................................................... 38 6.1 Impresso de dados .............................................................................................. 39 7 Listas ........................................................................................................................... 41 7.1 Operaes sobre listas............................................................................................. 41 7.2 Funes teis........................................................................................................ 43 7.4 Usando as operaes ............................................................................................ 46 7.5 Listas de argumentos............................................................................................ 49 7.6 Tipos aglomerados ............................................................................................... 50 8 Programao imperativa ............................................................................................. 52 8.1 Atribuio ............................................................................................................ 52 8.2 Sequenciao........................................................................................................ 53 8.3 Alterao de dados............................................................................................... 54 8.4 Repetio.............................................................................................................. 56
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi 9 Modelos de ambientes................................................................................................. 59 9.1 mbito lxico....................................................................................................... 60 9.2 mbito dinmico ................................................................................................. 62 10 Parmetros especiais ................................................................................................. 64 10.1 Parmetros opcionais ......................................................................................... 64 10.2 Parmetros de resto ............................................................................................ 64 10.3 Parmetros de chave ......................................................................................... 65 11 Macros....................................................................................................................... 68 11.1 Avaliao de macros .......................................................................................... 68 11.2 Escrita de macros ............................................................................................... 68 11.3 Depurao de macros......................................................................................... 69 11.4 Caracteres de macro........................................................................................... 70 11.5 Macros utis ....................................................................................................... 71 11.6 Iteradores............................................................................................................ 73 11.7 Fichas ................................................................................................................. 74 Referncias........................................................................................................................ 79
1 - Elementos da Linguagem
Em linguagens Funcionais toda a programao realizada por meio da aplicao de funes a argumentos. As variveis e as operaes de atribuio usadas pelas linguagens imperativas no so necessrias. Os laos de repetio so substitudos por chamadas a funes recursivas. Baseiam-se fortemente nos conceitos das funes matemticas. Qualquer linguagem de programao lida com duas espcies de objetos: dados e procedimentos. Os dados so os objectos que pretendemos manipular. Os procedimentos so descries das regras para manipular esses dados. Se considerarmos a linguagem da matemtica, podemos identificar os nmeros como dados e as operaes algbricas como procedimentos e podemos combinar os nmeros entre si usando aquelas operaes. Por exemplo, 2 2 uma combinao, tal como 2 2 2 e 2 2 2 2. No entanto, a menos que pretendamos ficar eternamente a resolver problemas de aritmtica elementar, somos obrigados a considerar operaes mais elaboradas que representam padres de clculos. Neste ltimo exemplo, evidente que o padro que est a emergir o da operao de potenciao, i.e, multiplicao sucessiva, tendo esta operao sido definida na matemtica h j muito tempo. Tal como a linguagem da matemtica, uma linguagem de programao deve possuir dados e procedimentos primitivos, deve ser capaz de combinar quer os dados quer os procedimentos para produzir dados e procedimentos mais complexos e deve ser capaz de abstrair padres de clculo de modo a permitir trat-los como operaes simples, definindo novas operaes que representem esses padres de clculo.
Como se v no exemplo, em Lisp, os nmeros podem ser inteiros ou reais. Exerccio 2 Descubra qual o maior real que o seu Lisp aceita. Consegue fazer o mesmo para os inteiros?
1.2 - Combinaes
Combinaes so entidades complexas feitas a partir de entidades mais simples. Por exemplo, na matemticas nmeros podem ser combinados usando operaes como a soma ou o produto. Como exemplo de combinaes matemticas, temos 1 + 2 e 1 + 2 3. A soma e o produto de nmeros so exemplos de operaes extremamente elementares consideradas procedimentos primitivos. Em Lisp, criam-se combinao escrevendo uma sequncia de expresses entre um par de parnteses. Uma expresso um elemento primitivo ou uma outra combinao. A expresso (+ 1 2) uma combinao dos elementos primitivos 1 e 2 atravs do procedimento +. J no caso (+ 1 (* 2 3)) a combinao ocorre entre 1 e (* 2 3), sendo esta ltima expresso uma outra combinao. No difcil de ver que as nicas combinaes com utilidade so aquelas em que as expresses correspondem a operadores e operandos. Por conveno, o Lisp considera que o primeiro elemento de uma combinao um operador e os restantes so os operandos. A notao que o Lisp utiliza para construir expresses (operador primeiro e operandos a seguir) designada por notao prefixa. Esta notao costuma causar alguma perplexidade a quem inicia o estudo da linguagem, que espera uma notao mais prxima da que aprendeu em aritmtica e que usada habitualmente nas outras linguagens de programao. Nestas, a expresso (+ 1 (* 2 3)) usualmente escrita na forma 1 + 2 * 3 (designada notao infixa) que, normalmente, mais simples de ler por um ser humano. No entanto, a notao prefixa usada pelo Lisp tem largas vantagens sobre a notao infixa:
muito fcil usar operadores que tm um nmero varivel de argumentos, como por exemplo:
> (+ 1 2 3) 6 > (+ 1 2 3 4 5 6 7 8 9 10)
Numa linguagem do tipo Pascal apenas existem operadores unrios ou binrios, e necessrio explicitar os operador binrios entre cada dois operandos: 1+2+3 ou 1+2+3+4+5+6+7+8+9+10. Um operador deste tipo diz-se infixo (in significa entre). Se se pretender um operador ternrio (ou outro) j no se consegue escrever do mesmo modo, sendo necessrio implement-lo como uma funo.
No existe precedncia entre os operadores. Em Pascal, a expresso 1+2*3 tem de ser calculada como se se tivesse escrito 1+(2*3), e no (1+2)*3, i.e., foi criada uma precedncia que elimina as ambiguidades. Essa precedncia pode ser alterada atravs do emprego de parnteses. Em Lisp, as expresses seriam necessariamente distintas, (+ 1 (* 2 3)) ou (* (+ 1 2) 3), no podendo haver qualquer ambiguidade. Os operadores que ns definimos usam-se exatamente da mesma maneira que os operadores da linguagem: operador primeiro e operandos a seguir. Em Pascal, os operadores so infixos, i.e., esto entre os operandos, e as funes e procedimentos por ns definidos so prefixos, i.e., primeiro a funo ou procedimento e depois os operandos. Isto impede a extenso da linguagem de uma forma coerente. Para exemplificar este ltimo aspecto, consideremos a operao de exponenciao em Pascal. Para ser coerente com o resto da linguagem, deveria existir um operador, por exemplo ** que permitisse escrever 3**4 para indicar a quarta potncia de 3. Como esse operador no existe na linguagem (standard), somos obrigados a criar uma funo que o substitua mas, neste caso, a sintaxe muda radicalmente. Esta funo, como se usa de forma prefixa no se pode tornar coerente com as restantes operaes do Pascal, como a soma e a multiplicao. Em Lisp, pelo contrrio, tanto podemos escrever (* 3 3 3 3) como definir uma funo que permita escrever (** 3 4).
A desvantagem da notao prefixa est na escrita de combinaes complexas. A expresso Pascal 1+2*3-4/5*6, equivale expresso Lisp (- (+ 1 (* 2 3)) (* (/ 4 5) 6)) que embora seja mais simples de ler para uma mquina, pode ser mais difcil de ler para um ser humano devido acumulao de parnteses. No entanto, esta expresso pode ser escrita de forma mais clara usando indentao. A regra para indentao de combinaes Lisp extremamente simples. Numa linha coloca-se o operador e o primeiro operando. Os restantes operandos vm alinhados por debaixo do primeiro.
(- (+ 1 (* 2 3)) (* (/ 4 5) 6))
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Quando a regra de indentao no suficiente, usam-se pequenas variaes, como seja colocar o operador numa linha e os operandos por debaixo, por exemplo:
(umaoperacaocomumnomemuitogrande 1 2 3 4)
A indentao fundamental em Lisp pois muito fcil escrever cdigo complexo. A grande maioria dos editores preparados para Lisp (Emacs, por exemplo) formatam automaticamente os programas medida que os escrevemos e mostram o emparelhamento de parnteses. Desta forma, aps algum tempo de prtica, torna-se muito fcil escrever e ler os programas, por mais complexa que possa parecer a sua estrutura. Exerccio 3 Converta as seguintes expresses da notao infixa da aritmtica para a notao prefixa do Lisp:
1+2-3 1-2*3 1*2-3 1*2*3 (1 - 2) * 3 (1 - 2) + 3 1 - (2 + 3) 2*2+3*3*3
Exerccio 4 Converta as seguintes expresses da notao prefixa do Lisp para a notao infixa da aritmtica:
(* (/ 1 2) 3) (* 1 (- 2 3)) (/ (+ 1 2) 3) (/ (/ 1 2) 3) (/ 1 (/ 2 3)) (- (- 1 2) 3) (- 1 2 3)
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Como j se viu, 1 vale 1 e (* 2 3) uma combinao cujo valor o resultado de multiplicar o valor de 2 pelo valor de 3, o que d 6, e que somado a 1 d 7.
> (+ 1 2) 3 > (+ 1 (* 2 3)) 7
Os nmeros 5 e 6 e a operao * so elementos primitivos. As expresses (* 5 5) e (* 6 6) so combinaes. combinao genrica (* x x) queremos associar um nome, por exemplo, quadrado. Isso equivale a acrescentar uma nova funo linguagem. A ttulo de exemplo, vamos definir a funo quadrado:
> (defun quadrado (x) (* x x)) quadrado
Para se definirem novas funes em Lisp, necessrio criar uma combinao de quatro elementos. O primeiro elemento desta combinao a palavra defun, que informa o avaliador que estamos a definir uma funo. O nome defun uma abreviatura de ``define function''. O segundo elemento o nome da funo que queremos definir, o terceiro elemento uma combinao com os parmetros da
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi funo e o quarto elemento a combinao que determina o valor da funo para aqueles parmetros. Quando se d uma expresso desta forma ao avaliador, ele acrescenta a funo ao conjunto de funes da linguagem, associando-a ao nome que lhe demos e devolve como valor da definio o nome da funo definida. A definio da funo quadrado diz que para se determinar o quadrado de um nmero x, devemos multiplicar esse nmero por ele prprio (* x x). Esta definio associa a palavra quadrado a um procedimento. Este procedimento possui parmetros e um corpo de expresses. De forma genrica temos:
(defun nome (parmetro-1 ...parmetro-n) corpo)
Os parmetros de um procedimento so designados parmetros formais e so os nomes usados no corpo de expresses para nos referirmos aos argumentos correspondentes. Quando escrevemos no avaliador de Lisp (quadrado 5), 5 o argumento. Durante o clculo da funo este argumento est associado ao parmetro formal x. Os argumentos de uma funo so tambm designados parmetros atuais.
> (quadrado 5) 25 > (quadrado 6) 36
Note-se que a regra de avaliao de combinaes tambm vlida para as funes por ns definidas. Assim, a avaliao da expresso (quadrado (+ 1 2)) passa pela avaliao do operando (+ 1 2). Este operando tem como valor 3, valor esse que usado pela funo no lugar do parmetro x. O corpo da funo ento avaliado, i.e., o valor final ser o da combinao (* 3 3). O seguinte exemplo mostra um caso um pouco mais complexo. Nele esto apresentadas as etapas de avaliao dos operandos e de avaliao do corpo da funo.
(quadrado (quadrado (+ 1 2))) (quadrado (quadrado 3)) (quadrado (* 3 3)) (quadrado 9) (* 9 9) 81
A definio de funes permite-nos associar um procedimento a um nome. Isto implica que o Lisp tem de possuir uma memria onde possa guardar o procedimento e a sua associao ao nome dado. Esta memria do Lisp designase ambiente.
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Note-se que este ambiente apenas existe enquanto estamos a trabalhar com a linguagem. Quando terminamos, perde-se todo o ambiente. Isto implica que, se no queremos perder o trabalho que estivemos a escrever, devemos escrever as funes num ficheiro e ir passando-as para o Lisp. A grande maioria das implementaes do Lisp permite fazer isto de forma automtica.
10
( sqrt 4 ) 2.0 ( sqrt 4.0 ) 2.0 ( sqrt 4 ) #C(0,0 2.0) Se o argumento for um nmero negativo, a funo no retornar um erro mas sim um nmero complexo na forma #C(3 2) que equivalente a 3 + 2i. expt ( Exponencial ) ( expt 2 10 ) 1024 ( expt 2 10.0 ) 1024.0 ( expt 2/3 3 ) 8/27 ( expt 4 ) #C(1.22514845490862E-16 2.0) Se algum dos argumentos for do tipo float, o resultado tambm ser convertido para float, podendo ocorrer aproximaes do nmero. No caso de 0.0, o resultado aproximado para #C(1.22514845490862E-16 2.0). log ( Logaritmo ) ( log 1 ) 0.0 ( log 10 ) 2.302585092994046 ( log 10 2 ) 3.3219280948873626 abs ( Valor Absoluto ) ( abs 8 ) 8 ( abs 8 ) 8 ( abs 8.9 ) 8.9 truncate ( Trunca ) 13 0.5 0.7 -1 -0.7
Truncar retornar o valor inteiro de um nmero. Note que a funo retorna dois valores: o primeiro o valor truncado, a parte inteira, e a segunda a parte decimal do nmero, o resto.
11
round ( Arredonda ) ( round 3.1 ) 3 0.1 ( round 3.7 ) 4 -0.3 ( round 3.5 ) 4 -0.5
Esta funo arredonda um nmero ao inteiro mais prximo. Novamente, h o retorno de dois valores: o nmero inteiro arredondado e a parte decimal restante do arredondamento, que pode ser positiva ou negativa. rem ( Resto ) ( rem 9 3 ) 0 ( rem 3 9 ) 3 ( rem 17 4 ) -1 Esta funo retorna o resto inteiro da diviso. float ( Float ) ( float 4 ) ( float 135 ) 4.0 135.0
Converte um dado do tipo inteiro em ponto flutuante ( float). Atribuio O comando ( setf varivel valor ) faz a atribuio de valores a variveis. ( setf x 4 ) 4 ;a varivel x recebe o valor 4 ( setf zero 0 one 1 two 2 three 3 four 4 ) 4 ; como foram feitas atribuies mltiplas, o ; interpretador retorna apenas o ltimo valor.
12
Vamos inicializar quatro variveis com quatro valores distintos para que os exemplos a seguir sejam executados corretamente. ( setf zero 0 one 1 two 2 three 3 four 4 ) 4 (>) ( Ordem Descendente ) ( > 100 10 one 0.1 ) ( > 10 100 1 0.1 ) T NIL
Retorna verdadeiro se os elementos esto dispostos em ordem descendente. (<) ( Ordem Ascendente ) (<1352) ( < 1 two 4 ) NIL T
Retorna verdadeiro se os elementos esto dispostos em ordem ascendente. (=) ( Igual ) ( = ( / 2.0 3.0) ( / 4.0 6.0 ) ) ( = ( 3.141592653 ( /22 7 ) ) max ( Mximo ) ( max 1 -2 3 4 5 6 7 ) ( max ( * 3 7) ( * 2 3 ) ) 7 21 NIL T
Retorna o argumento de maior valor. min ( Mnimo ) ( min 1 2 3 4 5 6 7 ) ( min ( expt 2 10 ) ( expt 10 2 ) ) -2 100
13
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi ( evenp 17 ) ( evenp ( * 3 2 ) ) NIL T
( minusp 17 ) ( minusp 13 )
Verifica se o argumento um nmero negativo. zerop ( Zero ) ( zerop ( * 2 0 ) ) ( zerop ( + 2 0 ) ) ( zerop zero ) T T NIL
Testa se o argumento igual a zero. zerop mais eficiente que seu equivalente ( = number 0).
14
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Se alguns dos argumentos so chamadas de funes, suas avaliaes seguem as mesmas regras que as enunciadas anteriormente. Assim, quando (/ (- 7 1) (- 4 2)) avaliada, observa-se as seguintes etapas: Lisp avalia (- 7 1), onde 7 avalia para 7 e 1 avalia para 1. Estes valores so passados para a funo -, que retorna 6; Lisp valia (- 4 2), onde 4 avalia para 4 e 2 avalia para 2. Estes valores so passados para a funo -, que retorna 2; Os valores 6 e 2 so enviados para a funo /, que retorna 3. Note que todos os operadores em Common Lisp so de fato funes, exceto para alguns casos (por exemplo, o quote comentado mais adiante). As chamadas de funes so sempre avaliadas da mesma maneira. Os argumentos so avaliados da esquerda para a direita, e seus valores passados para a funo, que retorna o valor da expresso como um todo. Isto conhecido em Common Lisp como a evaluation rule (regra de avaliao).
1.8 - Smbolos
A definio de funes em Lisp passa pela utilizao dos seus nomes. Nomes para as funes e nomes para os parmetros das funes. Uma vez que o Lisp, antes de avaliar qualquer expresso, tem de a ler e converter internamente para um objeto, os nomes que criamos tm de ter um objeto associado. Esse objeto designado por smbolo. Um smbolo , pois, a representao interna de um nome. Em Lisp, quase no existem limitaes para a escrita de nomes. Um nome como quadrado to bom como + ou como 1+2*3 pois o que separa um nome dos outros elementos de uma combinao so apenas parnteses e espaos em branco. Por exemplo, perfeitamente correto definir e usar a seguinte funo:
> (defun x+y*z (x y z) (+ (* y z) x)) x+y*z > (x+y*z 1 2 3) 7
Lisp atribui um significado muito especial aos smbolos. Reparemos que, quando definimos uma funo, os parmetros formais da funo so smbolos. O nome da funo tambm um smbolo. Quando escrevemos uma combinao, o avaliador de Lisp usa a definio de funo que foi associada ao smbolo que constitui o primeiro elemento da combinao. Quando, no corpo de uma funo, usamos um smbolo para nos referimos a um parmetro formal, esse smbolo tem como valor o respectivo parmetro atual que lhe foi associado naquela combinao. Por esta descrio se v que os smbolos constituem um dos elementos fundamentais da linguagem Lisp.
15
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Exerccio 6 - Crie uma funo que some trs nmeros. - Crie uma funo que calcule o delta de uma equao do segundo grau. Delta = b2 4*a*c Resposta
16
2 Expresses Condicionais
Existem muitas operaes cujo resultado depende da realizao de um determinado teste. Por exemplo, a funo matemtica abs --que calcula o valor absoluto de um nmero-equivale ao prprio nmero, se este positivo, ou equivale ao seu simtrico se for negativo. Estas expresses, cujo valor depende de um ou mais testes a realizar previamente, permitindo escolher vias diferentes para a obteno do resultado, so designadas expresses condicionais. No caso mais simples de uma expresso condicional, existem apenas duas alternativas a seguir. Isto implica que o teste que necessrio realizar para determinar a via de clculo a seguir deve produzir um de dois valores, em que cada valor designa uma das vias. Seguindo o mesmo raciocnio, uma expresso condicional com mais de duas alternativas dever implicar um teste com igual nmero de possveis resultados. Uma expresso da forma ``caso o valor do teste seja 1, 2 ou 3, o valor da expresso 10, 20 ou 30, respectivamente'' um exemplo desta categoria de expresses que, embora seja considerada pouco intuitiva, existe nalgumas linguagens (Basic, por exemplo). Contudo, estas expresses podem ser facilmente reduzidas primeira. Basta decompor a expresso condicional mltipla numa composio de expresses condicionais simples, em que o teste original tambm decomposto numa srie de testes simples. Assim, poderamos transformar o exemplo anterior em: ``se o valor do teste 1, o resultado 10, caso contrrio, se o valor 2, o resultado 20, caso contrrio 30''.
2.1 - Predicados
Desta forma, reduzimos todos os testes a expresses cujo valor pode ser apenas um de dois, e a expresso condicional assume a forma de ``se ...ento ...caso contrrio ...''. Nesta situao, a funo usada como teste denominada predicado e o valor do teste interpretado como sendo verdadeiro ou falso. Nesta ptica, o fato de se considerar o valor como verdadeiro ou falso no implica que o valor seja de um tipo de dados especial, como o booleano ou lgico de algumas linguagens (como o Pascal), mas apenas que a expresso condicional considera alguns dos valores como representando o verdadeiro e os restantes como o falso. Em Lisp, as expresses condicionais consideram como falso um nico valor. Esse valor representado por nil. Qualquer outro valor diferente de nil considerado como verdadeiro. Assim, do ponto de vista de uma expresso condicional, qualquer nmero um valor verdadeiro. Contudo, no faz muito sentido para o utilizador humano considerar um nmero como verdadeiro ou falso, pelo que se introduziu uma constante na linguagem para representar verdade. Essa constante representa-se por t.
17
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Note-se que, se t diferente de nil, e se nil o nico valor que representa a falsidade, ento t representa verdade. Desta forma, existem muitos predicados em Lisp cujo valor t quando a expresso por eles designada considerada verdadeira.
> (> 4 3) t > (< 4 3) nil
Existem muitos predicados em Lisp. Os predicados numricos mais usados so o zerop, =, >, <, >=, <=. O zerop testa se um nmero zero ou diferente de zero.
> (zerop 1) nil > (zerop 0) t
O fato de zerop terminar com a letra ``p'' deve-se a uma conveno adotada em Common Lisp segundo a qual os predicados devem ser distinguidos das restantes funes atravs da concatenao da letra ``p'' (de predicate) ao seu nome. Apesar da adoo dos smbolos t e nil, convm alertar que nem todos os predicados devolvem t ou nil exclusivamente. Alguns h que, quando querem indicar verdade, devolvem valores diferentes de t (e de nil, obviamente).
O and avalia os seus argumentos da esquerda para a direita at que um deles seja falso, devolvendo este valor. Se nenhum for falso o and devolve o valor do ltimo argumento. O or avalia os seus argumentos da esquerda para a direita at que um deles seja verdade, devolvendo este valor. Se nenhum for verdade o or devolve o valor do ltimo argumento. O not avalia para verdade se o seu argumento for falso e para falso em caso contrrio.
Note-se que embora o significado de falso seja claro pois corresponde necessariamente ao valor nil, o significado de verdade j no to claro pois, desde que seja diferente de nil, considerada verdade. Exerccio 7 Qual o valor das seguintes expresses? 18
Para o if, a condio o primeiro argumento, o consequente no caso de a condio ser verdade o segundo argumento e a alternativa no caso de ser falso o terceiro argumento.
> (if (> 4 3) 5 6) 5
Uma expresso if avaliada determinando o valor da condio. Se ela for verdade, avaliado o consequente. Se ela for falsa avaliada a alternativa. Exerccio 8 Defina uma funo soma-grandes que recebe trs nmeros como argumento e determina a soma dos dois maiores. Exerccio 9 Escreva uma funo que calcule o factorial de um nmero. A funo fact um exemplo de uma funo recursiva, i.e., que se refere a ela prpria.
19
Designa-se o par (condio-i expresso-i) por clusula. O cond testa cada uma das condies em sequncia, e quando uma delas avalia para verdade, devolvido o valor da expresso correspondente, terminando a avaliao. Um exemplo ser:
> (cond ((> 4 3) 5) (t 6)) 5
20
3 - Funes
Um passo bsico (ou mais) cujo resultado imediatamente conhecido. Um passo recursivo em que se tenta resolver um subproblema do problema inicial.
Se analisarmos a funo fatorial, o caso bsico o teste de igualdade a zero (zerop n), o resultado imediato 1, e o passo recursivo (* n (fact (- n 1))). Geralmente, uma funo recursiva s funciona se tiver uma expresso condicional, mas no obrigatrio que assim seja. A execuo de uma funo recursiva consiste em ir resolvendo subproblemas sucessivamente mais simples at se atingir o caso mais simples de todos, cujo resultado imediato. Desta forma, o padro mais comum para escrever uma funo recursiva :
Comear por testar os casos mais simples. Fazer chamadas (ou chamada) recursivas com subproblemas cada vez mais prximos dos casos mais simples.
Dado este padro, os erros mais comuns associados s funes recursivas so, naturalmente:
No caso de erro em funo recursiva, o mais usual a recurso nunca parar. O nmero de chamadas recursivas cresce indefinidamente at esgotar a memria (stack), e o programa gera um erro. Em certas linguagens (Scheme) e implementaes do Common Lisp, isto no assim, e pode nunca ser gerado um erro. A recurso infinita o equivalente das funes recursivas aos ciclos infinitos dos mtodos iterativos do tipo while-do e repeatuntil. Repare-se que uma funo recursiva que funciona perfeitamente para os casos para que foi prevista pode estar completamente errada para outros casos. A funo fact um exemplo. Quando o argumento negativo, o problema torna-se cada vez mais complexo, cada vez mais longe do caso simples. (fact -1) => (fact -2) => (fact -3) =>
21
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Exerccio 10 Considere uma verso extremamente primitiva da linguagem Lisp, em que as nicas funes numricas existentes so zerop e duas funes que incrementam e decrementam o seu argumento em uma unidade, respectivamente, 1+ e 1-. Isto implica que as operaes >, <, = e similares no podem ser utilizadas. Nesta linguagem, que passaremos a designar por nanoLisp, abreviadamente nanoLisp , defina a funo menor, que recebe dois nmero inteiros positivos e determina se o primeiro argumento numericamente inferior ao segundo. Exerccio 11 Defina a operao igual? que testa igualdade numrica de inteiros positivos na linguagem nanoLisp . Exerccio 12 At ao momento, a linguagem nanoLisp apenas trabalha com nmeros inteiros positivos. Admitindo que as operaes 1+, 1- e zerop tambm funcionam com nmeros negativos, defina a funo negativo que recebe um nmero inteiro positivo e retorna o seu simtrico. Assim, pretendemos obter: (negativo 3) => -3. Exerccio 13 Agora que a linguagem nanoLisp pode tambm trabalhar com nmeros inteiros negativos, defina o predicado positivo?, que recebe um nmero e indica se ele positivo ou no. Exerccio 14 Defina o teste de igualdade de dois nmeros na linguagem nanoLisp contemplando a possibilidade de trabalhar tambm com nmeros inteiros negativos. Exerccio 15 Defina a funo simtrico de um nmero qualquer na linguagem nanoLisp . Exerccio 16 possvel definir a soma de dois nmeros inteiros positivos em nanoLisp , i.e., apenas recorrendo s funes 1+ e 1- que somam e subtraem uma unidade, respectivamente. Defina a operao soma. Exerccio 17
22
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Generalize a funo de soma de modo a poder receber nmeros inteiros positivos e negativos. Exerccio 18 Do mesmo modo que a soma pode ser definida exclusivamente em termos de sucessor 1+ e predecessor 1-, a multiplicao pode ser definida exclusivamente em termos da soma. Defina a funo mult que recebe dois nmero e os multiplica usando a funo soma.
Consideremos agora uma outra funo que soma as razes quadradas de todos os inteiros entre a e b, .
(defun soma-raizes (a b) 23
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi (if (> a b) 0 (+ (sqrt a) (soma-raizes (1+ a) b))))
> (soma-raizes 1 4) 6.146264369941973
Em ambas as funes existe uma soma de expresses matemticas entre dois limites, por . O somatrio uma abstrao matemtica para exemplo, existe um somatrio uma soma de nmeros. Dentro do somatrio possvel colocar qualquer operao matemtica relativa ao ndice do somatrio. Esse ndice varia desde o limite inferior at ao limite superior. Para que se possa definir o processo do somatrio na nossa linguagem de programao ela deve ser capaz de fazer abstrao sobre as prprias operaes a realizar, e deve poder us-las como parmetros do processo. O padro a executar seria qualquer coisa do estilo: (defun soma-??? (a b) (if (> a b) 0 (+ (aplica-??? a) (soma-??? (1+ a) b)))) O smbolo ??? representa a operao a realizar dentro do somatrio, e que pretendemos transformar num parmetro. Em Lisp, para se aplicar uma funo que o valor de um argumento, usa-se a funo funcall, que recebe essa funo e os seus parmetros atuais. Para se indicar que pretende-se passar a funo associada a um determinado smbolo, usa-se a forma especial function. > (funcall (function 1+) 9) 10 > (defun teste (f x y) (funcall f x y)) teste > (teste (function +) 1 2) 3 Deste modo pode-se escrever a implementao do nosso somatrio: (defun somatorio (fun a b) (if (> a b) 0 (+ (funcall fun a) (somatorio fun (1+ a) b)))) Pode-se testar a funo para o exemplo anterior. > (somatorio (function quadrado) 1 4) 30
24
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Como se v, a funo somatorio representa a abstrao associada ao somatrio matemtico. Para isso, ela recebe uma funo como argumento e aplica-a aos sucessivos inteiros includos no somatrio. As funes que recebem e manipulam outras funes so designadas funes de ordem superior. Exerccio 20 Repare-se que, tal como a funo somatrio, podemos escrever a abstrao correspondente ao produtrio (tambm designado piatrio) . Esta abstrao corresponde ao produto dos valores de uma determinada expresso para todos os inteiros de um intervalo. Escreva uma funo Lisp que a implemente. Exerccio 21 Escreva a funo fatorial usando o produtrio.
3.4 Lambda
Se voc somente deseja criar uma funo temporria e no deseja perder tempo dandolhe um nome, lambda justamente o que voc precisa. Lambda pode ser vista como uma funo sem nome. A sintaxe da lambda igual da defun, mas em que se omite o nome. > ((lambda (z) (+ z 3)) 2) 5
> (defun teste (fun x y) (funcall fun x y)) > (teste (function (lambda (x y) (* x y))) 2 2) 4
As lambdas so a essncia do Lisp. A qualquer funo corresponde uma lambda. Na realidade, a forma especial defun no faz mais do que criar uma lambda com os parmetros e o corpo da funo e associ-la ao nome da funo que se est a definir. Quando a forma especial function recebe um nome (um smbolo) ela devolve a lambda associada a esse nome. A designao de lambda ( ) deriva duma rea da matemtica que se dedica ao estudo dos conceitos de funo e de aplicao de funo, e que se designa por clculo- . O clculo uma ferramenta muito utilizada para estudar a semntica das linguagens de programao.
25
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi (+ (* (quadrado (+ 1 (* (quadrado x) y))) x) (* (+ 1 (* (quadrado x) y)) y))) Como se v, a expresso (+ 1 (* (quadrado x) y)) aparece repetida duas vezes. Isto, alm de dificultar a leitura da funo torna-a menos eficiente pois aquela expresso vai ter de ser calculada duas vezes. Quase todas as linguagens de programao fornecem os meios para se criarem variveis locais, temporrias, para guardarem resultados parciais que vo ser utilizados em outros stios. Em Lisp, isso pode ser obtido definindo funes intermdias: (defun f (x y) (f* x y (+ 1 (* (quadrado x) y))))
(defun f* (x y temp) (+ (* (quadrado temp) x) (* temp y)))
Mas como j vimos, no h necessidade de se definir uma funo f* no ambiente pois podemos usar as lambdas diretamente. (defun f (x y) ((lambda (temp) (+ (* (quadrado temp) x) (* temp y))) (+ 1 (* (quadrado x) y)))) Repare-se que dentro do corpo da lambda h referncias quer aos parmetros da lambda (temp) quer aos parmetros da funo f em que a lambda est inserida. Uma vez que no muito conveniente separar os valores das variveis, Lisp providencia uma forma especial designada let que convertida para uma lambda. A sua sintaxe : (let ((var-1 exp-1) (var-2 exp-2) (var-n exp-n)) corpo) Quando se avalia um let, cada smbolo var-i associado ao valor da expresso correspondente exp-i (em paralelo) e em seguida o corpo avaliado como se as referncias a var-i estivessem substitudas pelos valores correspondentes de exp-i. Esta forma especial absolutamente equivalente a escrever: ((lambda (var-1 var-2 ...var-n) corpo) exp-1exp-2 ...exp-n)
26
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Embora equivalentes, a utilizao da forma let mais fcil de ler. Este gnero de formas especiais se limita a ser uma traduo mais agradvel para outras formas especiais e so designadas por acar sinttico. O let aucar sinttico para uma lambda. Exerccio 22 Usando o let, reescreva a funo f anterior. Exerccio 23 Qual o valor das seguintes expresses:
a) > (let ((x 10)) (+ (let ((x 20)) (+ x 5)) (+ x 2))) b) > (let ((x 10)) (+ (let ((x 11) (y (+ x 4))) (+ y x)) (+ x 2)))
As funes f-local1, f-local2 e f-local3 so locais funo teste, sendo estabelecidas a cada aplicao desta funo. Tal como as variveis do let, as funes locais de um flet no se podem referir umas s outras, pois so avaliadas em paralelo. Alm disso, tambm no se podem referir a si prpias, impedindo a criao de funes locais recursivas. 27
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Atendendo a que a maioria das vezes as funes que definimos so recursivas, independentemente de serem locais ou no, interessa possuir um meio de o podermos fazer. A forma especial labels providencia esta possibilidade. A sua sintaxe igual do flet, mas a sua semntica ligeiramente diferente. Para o flet, o mbito do nome das funes definidas apenas o corpo do flet. Para o labels, esse mbito extendido prpria forma especial. Isto permite que se possam definir funes locais recursivas ou mutuamente recursivas. Observe o seguinte exemplo: (defun teste (x) (labels ((f-local1 (y) (if (zerop y) 1 (* y (f-local1 (- y 1)))) ) (f-local2 (z) (* x z)) (f-local3 (x) (+ x 2))) (+ (f-local1 x) (f-local2 x) (f-local3 x)))) Para f-local1 calculado o fatorial do valor associado a x. > teste 2 10
28
4 mbito e Durao
Exerccio 24 Que tipo de mbito possui uma varivel de um let? Que tipo de mbito possui o nome de uma funo?
Repare-se que neste exemplo a funo que estabelece o incremento refere-se varivel livre tol. Uma das capacidades fundamentais das lambdas a sua referncia a variveis livres. Uma varivel diz-se livre numa lambda quando no um dos parmetros da lambda onde referida. Quando se aplica uma lambda aos seus argumentos, os parmetros tomam como valor os argumentos correspondentes, enquanto que as variveis livres tomam como valor o valor da primeira varivel igual no contexto em que a lambda definida. por esse motivo que quando a lambda que realiza o incremento aplicada a um nmero, ela sabe qual o valor 30
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi correto de tol. Ele dado pelo contexto lxico (e.x. textual) em que a lambda foi definida. Exerccio 25 Analise os seguintes casos:
a) ((lambda (x) (+ x y)) 10) b) (let ((y 5)) ((lambda (x) (+ x y)) 10))
31
5 Dados
Em todos os exemplos anteriores, so apresentadas funes essencialmente numricas. Os nmeros so um exemplo dos dados que os procedimentos podem usar e produzir. Neste ponto sero apresentados outros tipos de dados que se podem utilizar.
5.1 - tomos
Os nmeros so um dos elementos primitivos do Lisp. Os smbolos (nomes de funes e variveis) so outro dos exemplos. Estes elementos dizem-se atmicos, pois no podem ser decompostos. Para se testar se um elemento atmico pode-se usar a funo atom:
> (atom 1) T
Exerccio 26 Ao testar se o smbolo xpto atmico, escreve-se a expresso (atom xpto) e recebe-se um erro. Explique o que se passa. Para que o Lisp possa considerar um smbolo por si s, e.x., sem o considerar uma varivel, preciso usar a forma especial quote, que devolve o seu argumento sem o avaliar.
> (quote xpto) XPTO > (atom (quote xpto)) T
Existem vrias funes para se testar a igualdade de elementos primitivos. Como j se viu, a igualdade de nmeros dada pela funo =. Esta funo compara nmeros de todos os tipos.
> (= 1 1) t > (= 1 1.0) t
Em Lisp, existe unicidade de smbolos, e.x., dados dois smbolos com o mesmo nome, eles representam necessariamente o mesmo objeto, ou seja, o mesmo espao da memria do computador. Isto permite que a comparao entre dois smbolos possa ser feita testando se eles representam o mesmo espao, e.x., se apontam para a mesma zona da memria. A funo eq realiza essa operao.
> (eq (quote a) (quote a))
32
Para se testar smbolos e nmeros do mesmo tipo existe uma outra funo designada eql.
> (eql (quote a) (quote a)) t > (eql 111111111111111111111111111111 111111111111111111111111111111) t > (eql 1 1.0) nil
Dada uma combinao de objetos (um ``cons'') podemos obter o primeiro elemento da combinao usando a funo car e o segundo usando a funo cdr.
> (car (cons 1 2)) 1 > (cdr (cons 1 2)) 2
Note-se que aplicar o car ou o cdr a um cons no destri esse cons. O cons de dois objetos designado um par com ponto (dotted pair). Como natural, um cons no um objeto atmico:
> (atom (cons 1000 2000)) nil
Note-se que para combinar dois objetos quaisquer necessrio arranjar espao na memria para indicar qual o primeiro objeto e qual o segundo. a funo cons que arranja esse espao. De cada vez que ela chamada, mesmo que seja para juntar os
33
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi mesmos objetos, ela arranja um novo espao de memria. Isto implica que a funo eq sempre falsa para o cons.
> (eq (cons 1 2) (cons 1 2)) nil
J a funo equal teste se dois objetos (com relao ao seu contedo) so iguais.
> (equal (cons 1 2) (cons 1 2)) t > (equal (cons (cons 1 2) (cons 2 3)) (cons (cons 1 2) (cons 2 3))) t
Para sabermos qual o numerador ou o denominador de um dado nmero racional podemos definir:
(defun numerador (racional) (car racional))
Assim, j j podemos escrever a funo que calcula a soma de dois racionais, usando a frmula . 34
Para simplificar a escrita de racionais, podemos definir uma funo que escreve um racional de acordo com uma conveno qualquer.
(defun escreve-racional (racional) (format t "~a/~a" (numerador racional) (denominador racional)))
Obs: Veja captulo 6: Impresso Exerccio 27 Defina as restantes funes do tipo abstrato de informao racional: -racional, *racional, e /racional. Como se v, tratamos um nmero racional como um s objeto, e separamos a parte do programa que usa os racionais da parte que os implementa como pares de inteiros. Esta tcnica designa-se por abstrao de dados. A abstrao a melhor maneira de lidar com a complexidade. A abstrao de dados permite-nos isolar a utilizao dos dados do modo como eles esto implementados, atravs da utilizao de barreiras de abstrao. Essas barreiras consistem em limitar a utilizao dos dados a um pequeno conjunto de funes (racional, numerador e denominador) que escondem a maneira como eles esto implementados. Ao utilizador de um dado tipo de dados, apenas se diz quais as funes que ele pode usar para os manipular, e no qual o funcionamento das funes que implementam aquele tipo de dados. Seguindo esta metodologia, se precisarmos testar a igualdade de racionais, devemos escrever uma funo que o faa usando apenas as funes de manipulao de racionais, i.e., racional, numerador e denominador:
(defun =racional (r1 r2) (and (= (numerador r1) (numerador r2)) (= (denominador r1) (denominador r2))))
Exerccio 28
35
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi A funo que compara dois racionais no funciona corretamente para todos os casos. Assim,
> (=racional (racional 4 6) (racional 4 6)) t > (=racional (racional 4 6) (racional 2 3)) nil
Qual o problema? Como que se pode resolver? Exerccio 29 Escreva uma funo que calcule o maior divisor comum entre dois nmeros. Para isso, use o algoritmo de Euclides que diz que se r o resto da diviso de a por b, ento o maior divisor comum entre a e b tambm o maior divisor comum entre b e r: mdc(a,b)=mdc(b,r). Como natural, quando o resto zero, o maior divisor comum o prprio b. Exerccio 30 Empregue o mtodo de Euclides para reescrever a funo racional de modo a s construir nmeros na forma reduzida.
36
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Para que abstrao de dados seja corretamente realizada, fundamental definir o conjunto de construtores, seletores, reconhecedores e testes. Todos os programas que pretenderem utilizar aquele tipo de dados so obrigados a usar apenas aquelas funes. Isso permite que se possa alterar a implementao do tipo de dados sem afetar os programas que o utilizam. A implementao de estruturas de dados complexas s corretamente realizada quando se segue esta metodologia com extremo rigor. Exerccio 31 Defina o teste >racional. Quando um tipo abstrato de informao tem de interagir com um utilizador, quer para lhe pedir uma descrio de um elemento do tipo, quer para lhe apresentar uma descrio de um elemento do tipo, usa os denominados transformadores de entrada/sada. A funo escreve-racional um exemplo de um transformador de sada para o tipo racional. Ela limita-se a a apresentar uma representao compreensvel de um nmero racional. O transformador de entrada realiza a operao inversa, i.e., constri um elemento do tipo abstrato a partir de uma representao fornecida.
37
6 Entrada e sada
38
O primeiro 3 acima foi impresso, o segundo retornado. Se voc deseja um output mais complexo, voc necessita utilizar format:
>(format t "An atom: ~S~%and a list: ~S~%and an integer:~D~%" nil (list 5) 6) An atom: NIL and a list: (5) and an integer: 6
T especifica que a sada deve ser dirigida para o terminal, NIL especifica que no deve ser impresso nada, mas que format deve retornar um string com o contedo ao invs, uma referncia a um arquivo especifica o arquivo para onde a sada vai ser redirigida.
O segundo argumento um template de formatao, o qual um string contendo opcionalmente diretivas de formatao, de forma similar Linguagem "C": "An atom:
~S~%and a list: ~S~%and an integer:~D~%"
As diretivas de formatao do string sero repostas por LISP por caracteres apropriados com base nos valores dos outros parmetros a que eles se referem e ento imprimir o string resultante. Format sempre retorna NIL, a no ser que seu primeiro argumento seja NIL, caso em que no imprime nada e retorna o string resultante.
A primeira,~S, aceita qualquer objeto LISP e substituda por uma representao passvel de ser impressa deste objeto (a mesma produzida por print). A segunda, ~D, s aceita inteiros. A terceira, ~%, no aceita nada. Sempre reposta por uma quebra de linha. Outra diretiva til ~~, que substituda por um simples ~.
39
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Algumas Derivaes de Print A funo print possui algumas funes derivadas que tambm realizam a impresso dos dados, mas de uma forma diferente. Entre elas, podemos citar: terpri imprime uma linha em branco (no recebe nenhum argumento). prin1 imprime o dado na linha corrente e inicia uma nova linha. princ imprime o dado na linha corrente e no inicia nova linha. print inicia nova linha e imprime o dado.
Exemplo: (defun mediaquatro (primeiro segundo terceiro quarto) (princ "A mdia de") (terpri) ; salto de linha (princ primeiro) (princ " ") (princ segundo) (princ " ") (princ terceiro) (princ " ") (princ quarto) (terpri) (princ "") (/ (+ primeiro segundo terceiro quarto) 4)) MEDIAQUATRO > (mediaquatro 2 4 6 8) A mdia de 2468 5
40
7 Listas
As listas so um dos componentes fundamentais da linguagem Lisp. O nome da linguagem , alis, uma abreviao de ``list processing''. Como iremos ver, as listas constituem uma estrutura de dados extremamente flexvel.
Se o ltimo elemento a constante nil, o Lisp considera que ela representa a lista vazia, pelo que escreve:
> (cons 1 (cons 2 (cons 3 (cons 4 nil)))) (1 2 3 4)
Esta notao designa-se de lista e esta que o Lisp usa para simplificar a leitura e a escrita. Uma lista ento uma sequncia de elementos. Nesta ptica, a funo car devolve o primeiro elemento de uma lista, enquanto a funo cdr devolve o resto da lista. A funo cons pode ser vista como recebendo um elemento e uma lista e devolve como resultado uma nova lista correspondente juno daquele elemento no princpio daquela lista. Segundo esta abordagem, a funo cons um construtor do tipo abstrato de informao lista, enquanto as funes car e cdr so seletores. Uma lista vazia uma sequncia sem qualquer elemento e pode ser escrita como nil ou ainda mais simplesmente (). A lista vazia o elemento mais primitivo do tipo lista. nil o construtor do elemento primitivo. Pode-se testar se uma lista vazia com a funo null. A funo null , portanto, um reconhecedor do tipo lista.
> (null nil) t > (null (cons 1 (cons 2 nil))) nil
Exerccio 32 Escreva uma funo que calcula uma lista de todos os nmeros desde a at b.
> (enumera 1 10) (1 2 3 4 5 6 7 8 9 10)
Embora as listas no sejam mais do que uma estruturao particular de clulas cons, podendo por isso ser acedidas com as funes car e cdr, considerado melhor estilo de 41
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi programao usar as funes equivalentes first e rest. first devolve o primeiro elemento da lista enquanto rest devolve o resto da lista, i.e., sem o primeiro elemento. Do mesmo modo, o predicado null deve ser substitudo pelo seu equivalente endp.
> (first (enumera 1 10)) 1 > (rest (enumera 1 10)) (2 3 4 5 6 7 8 9 10)
Exerccio 33 Escreva uma funo que filtra uma lista, devolvendo uma lista com os elementos que verificam um determinado critrio. Utilize-a para encontrar os nmeros pares entre 1 e 20.
> (filtra (function par?) (enumera 1 20)) (2 4 6 8 10 12 14 16 18 20)
Obs.: lembre-se de definir a funo par?. Esta funo j existe em Lisp e denomina-se remove-if-not. Quando se pretendem construir listas pode-se usar tambm a funo list. Esta funo recebe qualquer nmero de argumentos e constri uma lista com todos eles.
> (list 1 2 3 4) (1 2 3 4) > (first (list 1 2 3 4)) 1 > (rest (list 1 2 3 4)) (2 3 4) > (list 1 2 (list 10 20) 3 4) (1 2 (10 20) 3 4)
Como se v possvel construir listas dentro de listas. Lisp permite tambm a construo de listas diretamente no avaliador. Idealmente, bastaria escrever (1 2 3 ...), s que isso seria avaliado segundo as regras de avaliao das combinaes. O nmero 1 seria considerado um operador e os restantes elementos da lista os operandos. Para evitar que uma lista possa ser avaliada podemos usar a forma especial quote, que devolve o seu argumento sem o avaliar.
> (quote (1 . (2 . (3 . nil)))) (1 2 3) > (quote (1 2 3 4 5 6 7 8 9 10)) (1 2 3 4 5 6 7 8 9 10) > (filtro (function par?) (quote (1 2 3 4 5 6 7 8 9 10))) (2 4 6 8 10)
Uma vez que as formas especiais quote e function so bastante utilizadas, Lisp fornece um meio de se simplificar a sua utilizao. Se dermos ao avaliador uma expresso 42
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi precedida por uma plica (quote em Ingls), como se tivessemos empregue a forma especial quote. A substituio feita durante a leitura da expresso. Do mesmo modo, se precedermos uma funo ou uma lambda por #' (cardinal-plica) como se tivssemos empregue a forma especial function. 'exp equivalente a (quote exp), enquanto que #'exp equivalente a (function exp).
> '(1 2 3 4 5) (1 2 3 4 5) > (filtra #'par '(1 2 3 4 5 6)) (2 4 6)
Exerccio 44 O que que o avaliador de Lisp devolve para a seguinte expresso: (first ''(1 2 3))? Como natural, as operaes car e cdr podem ser encadeadas:
> (car 1 > (cdr (2 3) > (car 2 > (car 3 '(1 2 3)) '(1 2 3)) (cdr '(1 2 3)) (cdr (cdr '(1 2 3))))
Dado que aquele gnero de expresses muito utilizado em Lisp, foram compostas as vrias combinaes, e criaram-se funes do tipo (caddr exp), que correspondem a (car (cdr (cdr exp))). O nome da funo indica quais as operaes a realizar. Um ``a'' representa um car e um ``d'' representa um cdr.
> (cadr '(1 2 3)) 2 > (cdddr '(1 2 3)) nil
43
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Escreva uma funo muda-n-esimo que recebe um nmero n, uma lista e um elemento, e substitui o n-simo elemento da lista por aquele elemento. Note que o primeiro elemento da lista corresponde a n igual a zero. Exerccio 37 Escreva uma funo que calcula o comprimento de uma lista, i.e., determina quantos elementos ela tem. Esta funo j existe em Lisp e denomina-se length. Exerccio 38 Escreva uma funo que recebe um elemento e uma lista que contm esse elemento e devolve a posio desse elemento na lista. Exerccio 39 Escreva uma funo que calcula o nmero de tomos que uma lista (possivelmente com sublistas) tem. Exerccio 40 Escreva uma funo junta que recebe duas listas como argumento e devolve uma lista que o resultado de as juntar uma frente da outra. Esta funo j existe em Lisp e denomina-se append. Exerccio 41 Defina uma funo inverte que recebe uma lista e devolve outra lista que possui os mesmos elementos da primeira s que por ordem inversa. Esta funo j existe em Lisp e denomina-se reverse. Exerccio 42 Escreva uma funo designada inverte-tudo que recebe uma lista (possivelmente com sublistas) e devolve outra lista que possui os mesmo elementos da primeira s que por ordem inversa, e em que todas as sublistas esto tambm por ordem inversa, i.e.:
> (inverte-tudo '(1 2 (3 4 (5 6)) 7)) (7 ((6 5) 4 3) 2 1)
Exerccio 43
44
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Escreva uma funo mapear que recebe uma funo e uma lista como argumentos e devolve outra lista com o resultado de aplicar a funo a cada um dos elementos da lista. Esta funo j existe em Lisp e denomina-se mapcar. Exerccio 44 Escreva uma funo denominada alisa que recebe uma lista (possivelmente com sublistas) como argumento e devolve outra lista com todos os tomos da primeira e pela mesma ordem, i.e.
> (alisa '(1 2 (3 4 (5 6)) 7)) (1 2 3 4 5 6 7)
Exerccio 45 Escreva uma funo membro? que recebe um objecto e uma lista e verifica se aquele objecto existe na lista. Esta funo j existe em Lisp e denomina-se member. Quando ela encontra um elemento igual na lista devolve o resto dessa lista.
> (member 3 '(1 2 3 4 5 6)) (3 4 5 6)
Exerccio 46 Escreva uma funo elimina que recebe um elemento e uma lista como argumentos e devolve outra lista onde esse elemento no aparece. Esta funo j existe em Lisp e denomina-se remove. Exerccio 47 Escreva uma funo substitui que recebe dois elementos e uma lista como argumentos e devolve outra lista com todas as ocorrncias do segundo elemento substitudas pelo primeiro. Esta funo j existe em Lisp e denomina-se subst. Exerccio 48 Escreva uma funo remove-duplicados que recebe uma lista como argumento e devolve outra lista com todos os elementos da primeira mas sem duplicados, i.e.:
> (remove-duplicados '(1 2 3 3 2 4 5 4 1)) (3 2 5 4 1)
45
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Esta funo no mantm a anterior ordenao da lista. Se pretendermos preservar a ordem original, temos de testar se um elemento j existe na lista que estamos a construir e no na que estamos a analisar.
> (remove-duplicados2 '(1 2 3 3 2 4 5 4 1)) (1 2 3 4 5)
Esta funo j existe em Lisp e denomina-se remove-duplicates. Exerccio 49 Escreva as funes inversas do cons, car e cdr, designadas snoc, rac e rdc. O snoc recebe um elemento e uma lista e junta o elemento ao fim da lista. O rac devolve o ltimo elemento da lista. O rdc devolve todos os elementos da lista menos o primeiro. A funo snoc j existe em Lisp atravs da combinao das funes append e list. A funo rac j existe em Lisp atravs da combinao das funes first e last. A funo rdc j existe em Lisp e denomina-se butlast. Exerccio 50 As funes todos?, algum?, nenhum? e nem-todos? so predicados que recebem uma funo e uma lista e verificam, respectivamente, se a funo verdade para todos os elementos da lista, se verdade para pelo menos um, se no verdade para todos e se no verdade para pelo menos um. Defina estas funes. Esta funo j existe em Lisp e denomina-se every. Esta funo j existe em Lisp e denomina-se some. Esta funo j existe em Lisp e denomina-se notany. Esta funo j existe em Lisp e denomina-se notevery.
46
Como se v possvel construir listas dentro de listas. Lisp permite tambm a construo de listas diretamente no avaliador. Idealmente, bastaria escrever (1 2 3 ...), s que isso seria avaliado segundo as regras de avaliao das combinaes. O nmero 1 seria considerado um operador e os restantes elementos da lista os operandos. Para evitar que uma lista possa ser avaliada podemos usar a forma especial quote, que devolve o seu argumento sem o avaliar.
> (quote (1 2 3) (1 2 3) > (quote (1 2 3 4 5 6 7 8 9 10)) (1 2 3 4 5 6 7 8 9 10)
Uma vez que as formas especiais quote e function so bastante utilizadas, Lisp fornece um meio de se simplificar a sua utilizao. Se dermos ao avaliador uma expresso precedida por uma plica (quote em Ingls), como se tivessemos empregue a forma especial quote. A substituio feita durante a leitura da expresso. Do mesmo modo, se precedermos uma funo ou uma lambda por #' (cardinal-plica) como se tivssemos empregue a forma especial function. 'exp equivalente a (quote exp), enquanto que #'exp equivalente a (function exp).
> '(1 2 3 4 5) (1 2 3 4 5) > (remove-if-not #'evenp '(1 2 3 4 5 6)) (2 4 6)
Devolve o n-simo elemento de uma lista. Note que o primeiro elemento da lista corresponde a n igual a zero. nth 0 '(1 2 3) 1 Devolve o comprimento de uma lista, i.e., determina quantos elementos ela tem. length '(1 2 3 4) 4
47
Recebe duas listas como argumento e devolve uma lista que o resultado de as juntar uma frente da outra. append (list 1 2 3) (list 4 5 6) (1 2 3 4 5 6)
Inverte uma lista e devolve outra lista que possui os mesmos elementos da primeira s
Escreva uma funo designada inverte-tudo que recebe uma lista (possivelmente com sublistas) e devolve outra lista que possui os mesmo elementos da primeira s que por ordem inversa, e em que todas as sublistas esto tambm por ordem inversa, i.e.: Recebe uma funo e uma lista como argumentos e devolve outra lista com o resultado de aplicar a funo a cada um dos elementos da lista. mapcar (function sqrt) (list 1 2 3)
(1.0 1.4142135623730951 1.7320508075688772)
Recebe um objeto e uma lista e verifica se aquele objeto existe na lista. Esta funo j existe em Lisp e denomina-se member. Quando ela encontra um elemento igual na lista devolve o resto dessa lista.
> (member 3 '(1 2 3 4 5 6)) (3 4 5 6)
Recebe um elemento e uma lista como argumentos e devolve outra lista onde esse elemento no aparece. remove 3 (list 12 3 4) (12 4) Recebe dois elementos e uma lista como argumentos e devolve outra lista com todas as ocorrncias do segundo elemento substitudas pelo primeiro. subst 1 2 (list 2 3 4 3 2 2) (1 3 4 3 1 1) Recebe uma lista como argumento e devolve outra lista com todos os elementos da primeira mas sem duplicados. remove-duplicates (list 1 1 1 2 2 2 3 3 3 4 4 4) (1 2 3 4) Recebe uma lista e devolve uma outra lista sem o ltimo elemento. 48
butlast (list 2 3 4) (2 3) Recebe uma funo e uma lista e verifica se se a funo verdade para todos os elementos da lista every (function zerop) (list 0 1 2) NIL every (function zerop) (list 0 0 0) T Recebe uma funo e uma lista e verifica se se a funo verdade para pelo menos um elemento da lista some (function zerop) (list 0 1 2) T some (function zerop) (list 2 3 4) NIL
Na linguagem Common Lisp, a funo apply uma fuso entre a funo funcall e a funo apply primitiva, pois da forma:
(apply func arg-1 arg-2 ...rest-args)
Nesta aplicao o ltimo argumento rest-args uma lista com os restantes argumentos. Desta forma, pode-se escrever qualquer das seguintes equivalncias:
(funcall func arg-1 arg-2 ...arg-n) <=> (apply func (list arg-1 arg-2 ...arg-n)) (apply func arg-1 (list arg-2 ...arg-n)) (apply func arg-1 arg-2 ...(list arg-n)) (apply func arg-1 arg-2 ...arg-n nil)) <=> <=> <=>
49
Os seletores do tipo automvel limitam-se a determinar de que que um dado objeto daquele tipo composto:
(defun automovel-marca (automovel) (nth 0 automovel)) (defun automovel-modelo (automovel) (nth 1 automovel)) (defun automovel-portas (automovel) (nth 2 automovel))
Estando na posse destas funes, podemos criar um automvel especfico, por exemplo:
> (novo-automovel 'honda 'civic 2) (honda civic 2)
Dado aquele objeto do tipo automvel, podemos estar interessados em alterar-lhe o nmero de portas, passando-as de 2 para 4, por exemplo. Contudo, para manipularmos um tipo abstrato devemos restringirmo-nos s operaes desse tipo. Precisamos, portanto,
50
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi de criar novas operaes que nos permitem modificar um objeto. Para o tipo automvel poderamos definir:
(defun muda-automovel-marca (automovel nova-marca) (muda-n-esimo 0 automovel nova-marca)) (defun muda-automovel-modelo (automovel novo-modelo) (muda-n-esimo 1 automovel novo-modelo)) (defun muda-automovel-portas (automovel novo-portas) (muda-n-esimo 2 automovel novo-portas))
A funo muda-n-esimo recebia um nmero n, uma lista e um novo elemento, e substitua o n-simo elemento da lista pelo novo elemento. Esta funo no alterava a lista original, produzindo uma nova lista. Desta forma, qualquer destas funes do tipo automvel deixa o automvel a modificar absolutamente inalterado, produzindo um novo automvel. Por este motivo, estas operaes devem ser vistas como construtores do tipo automvel, pois elas criam um novo automvel a partir de um outro j existente. Elas no permitem alterar um automvel j criado.
51
8 Programao imperativa
Todas as funes que apresentadas anteriormente realizam operaes muito variadas e algumas so at relativamente complexas, mas nenhuma afeta os seus argumentos. Elas limitam-se a produzir novos objetos a partir de outros j existentes, sem alterar estes ltimos seja de que forma for. At as prprias variveis que introduzimos nas funes e que se destinavam a guardar valores temporrios no eram mais do que parmetros de uma lambda, e a sua inicializao correspondia a invocar a lambda com os valores iniciais como argumentos, sendo por isso inicializadas uma nica vez e nunca modificadas. Por este motivo, nem sequer foi apresentado nenhum operador de atribuio, to caracterstico em linguagens como C e Pascal. Este estilo de programao, sem atribuio, sem alterao dos argumentos de funes, e em que estas se limitam a produzir novos valores, designado programao funcional. Neste paradigma de programao, qualquer funo da linguagem considerada uma funo matemtica pura que, para os mesmos argumentos produz sempre os mesmos valores. Nunca nada destrudo. Uma funo que junta duas listas produz uma nova lista sem alterar as listas originais. Uma funo que muda o nmero de portas de um automvel produz um novo automvel. A programao funcional tem muitas vantagens sobre outros estilos de programao, em especial no que diz respeito a produzir programas muito rapidamente e minimizando os erros. Contudo, tem tambm as suas limitaes, e a sua incapacidade em modificar seja o que for a maior. A partir do momento em que introduzimos a modificao de objetos, estamos a introduzir o conceito de destruio. A forma anterior do objeto que foi modificado deixou de existir, passando a existir apenas a nova forma. A modificao implica a introduo do conceito de tempo. Os objetos passam a ter uma histria, e isto conduz a um novo estilo de programao.
8.1 Atribuio
Para se alterar o valor de uma varivel Lisp possui um operador de atribuio. A forma especial setq recebe uma varivel e um valor e atribui o valor varivel.
> (let ((x 2)) (setq x (+ x 3)) (setq x (* x x)) (setq x (- x 5)) x) 20
Cada vez que se realiza uma atribuio, perde-se o valor anterior que a varivel possua. Muito embora a forma especial setq, como todas as funes e forma especiais, retorne um valor, esse valor no possui qualquer interesse. O operador de atribuio serve apenas para modificar o estado de um programa, neste exemplo, alterando o valor de x. Por este motivo, a atribuio assemelha-se mais a um comando do que a uma funo. A atribuio uma ordem que tem de ser cumprida e de que no interessa o resultado. A ordem a 52
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi operao fundamental da programao imperativa, em que um programa composto por sucessivos comandos. Neste estilo de programao, os comandos funcionam por efeitos secundrios. O valor de cada comando no tem interesse e muitas vezes nem sequer tem significado falar dele.
8.2 Sequenciao
A forma especial progn est especialmente vocacionada para este gnero de utilizao. Ela recebe um conjunto de expresses que avalia sequencialmente retornando o valor da ltima. Desta forma possvel incluir vrias aes no consequente ou alternativa de um if, por exemplo.
(if (> 3 2) (progn (print 'estou-no-consequente) (+ 2 3)) (progn (print 'estou na alternativa) (* 4 5)))
Algumas das formas especiais do Lisp incluem um progn implcito. Vimos atrs um exemplo de um let que realizava quatro operaes em sequncia. Isto implica que o let e, por arrastamento, as lambdas e tudo o que for definido com defun, possuem tambm a capacidade de realizar operaes em sequncia. O cond est tambm nesta categoria. A sua sintaxe , na realidade, a seguinte:
(cond (condio-1 expresso-11...expresso-1l) (condio-2 expresso-21...expresso-2m) (condio-n expresso-n1...expresso-nk))
O cond testa cada uma das condies em sequncia, e quando uma delas avalia para verdade, so avaliadas todas as expresses da clusula correspondente sendo devolvido o valor da ltima dessas expresses. Usando o cond, o exemplo anterior ficar mais elegante:
(cond ((> 3 2) (print 'estou-no-consequente) (+ 2 3)) (t (print 'estou na alternativa) (* 4 5)))
A forma especial prog1 semelhante ao progn. Ela recebe um conjunto de expresses que avalia sequencialmente retornando o valor da primeira. A expresso equivalente ao exemplo anterior mas utilizando o prog1 ser:
(if (> 3 2)
53
Note-se que a ordem de avaliao das expresses de um prog1 igual de um progn. Apenas o valor retornado diferente: o primeiro no caso do prog1 e o ltimo no caso do progn. A sequenciao tambm suportada por qualquer lambda. Em consequncia, as formas especiais que implicam a criao de lambdas, como o let e o prprio defun permitem tambm especificar mais do que uma expresso, sendo estas avaliadas em sequncia e devolvido o valor da ltima.
Note-se que estas funes so uma forma de atribuio e, como tal, destroem o contedo anterior da estrutura a que se aplicam. Por este motivo, este gnero de funes diz-se destrutivo. Na sequncia lgica da conveno usual em Lisp para a definio de reconhecedores, que terminam sempre com um ponto de interrogao (ou com a letra ``p'' de predicado), deve-se colocar um ponto de exclamao no fim do nome das funes destrutivas para salientar o seu carcter imperativo. Exerccio 51 Escreva uma funo junta! (note-se o ponto de exclamao) que recebe duas listas como argumento e devolve uma lista que o resultado de as juntar destrutivamente uma frente da outra, i.e., faz a cauda da primeira lista apontar para a segunda. Esta funo j existe em Lisp e denomina-se nconc.
54
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Exerccio 52 Analise o seguinte exemplo funcional e imperativo:
> (let ((x '(1 2 3))) (junta x x)) (1 2 3 1 2 3) > (let ((x '(1 2 3))) (junta! x x)) (1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 ...
Exerccio 53 Escreva uma funo muda-n-esimo! (note-se o ponto de exclamao) que recebe um nmero n, uma lista e um elemento, e substitui o n-simo elemento da lista por aquele elemento. Note que o primeiro elemento da lista corresponde a n igual a zero. Exerccio 54 Reescreva as operaes do tipo abstrato de informao automvel que alteravam as caractersticas de um elemento do tipo de forma a torn-las destrutivas. Quando as operaes de um tipo abstrato alteram um elemento do tipo, essas operaes so classificadas como modificadores do tipo abstrato. Os modificadores, como caso especial da atribuio, so muito empregues em programao imperativa. Note-se que os modificadores possuem todos os problemas da atribuio simples, nomeadamente a alterao ser destrutiva. Isto levanta problemas quando se testa igualdade em presena de modificao.
> (let ((x (novo-automovel 'honda 'civic 2))) (let ((y (muda-automovel-portas x 4))) (eql x y))) NIL > (let ((x (novo-automovel 'honda 'civic 2))) (let ((y (muda-automovel-portas! x 4))) (eql x y))) T
Repare-se que no primeiro exemplo (funcional), o automvel modificado , logicamente, diferente do automvel original. x e y representam automveis diferentes. No segundo exemplo (imperativo), a modificao do automvel que x representa realizada sobre esse prprio automvel, de modo que x e y acabam por representar um mesmo automvel (modificado). Muito embora esta situao possa ter vantagens, ela permite tambm a introduo de erros muito subtis e extremamente difceis de tirar. Para dar apenas um pequeno exemplo, repare-se que o automvel que x representa passou a ter quatro portas embora uma leitura superficial do cdigo sugira que ele foi criado com apenas duas. 55
8.4 Repetio
Para alm dos operadores de atribuio (setq, rplaca, rplacd, etc.) e de sequenciao (progn, prog1, etc.) a linguagem Common Lisp possui muitas outras formas especiais destinadas a permitir o estilo de programao imperativa. De destacar so as estruturas de controle de repetio, tais como o loop, o do, o dotimes e ainda outras adequadas para iterar ao longo de listas. O loop a mais genrica de todas as formas de repetio. Ela recebe um conjunto de expresses que avalia sequencialmente, repetindo essa avaliao em ciclo at que seja avaliada a forma especial return. Esta ltima recebe uma expresso opcional e termina o ciclo em que est inserida, fazendo-o devolver o valor daquela expresso. A seguinte expresso exemplifica um ciclo que escreve todos os nmeros desde 0 at 100, retornando o smbolo fim no final do ciclo.
(let ((n 0)) (loop (print n) (setq n (1+ n)) (when (> n 100) (return 'fim))))
A forma especial do um pouco mais sofisticada que o loop. Ela permite estabelecer variveis, inicializ-las e increment-las automaticamente, testar condies de paragem com indicao do valor a retornar e repetir a execuo de cdigo. Se reescrevermos o exemplo anterior usando a forma especial do, obtemos:
(do ((n 0 (1+ n))) ((> n 100) 'fim) (print n))
Tal como o loop, a forma especial do pode ser interrompida em qualquer altura com um return, retornando o valor opcional fornecido com o return. Apesar do estilo mais utilizado na maioria das linguagens de programao ser o imperativo, ele muito pouco natural em Lisp. A falta de naturalidade resulta, por um lado, de os programas em Lisp se decomporem geralmente em pequenas funes que calculam valores, invalidando uma abordagem baseada em ciclos de alterao de variveis, tpica da programao imperativa. Por outro lado, a grande maioria de tipos de dados existentes em Lisp so inerentemente recursivos, o que dificulta o seu tratamento segundo o estilo imperativo.
56
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Apesar de muito pouco prtico para usar em Lisp, a programao imperativa tem algumas vantagens, das quais a possibilidade de atribuio a maior (e tambm a mais perigosa).
57
58
9 Modelos de ambientes
At agora vimos que as variveis eram apenas designaes para valores. Quando se avaliava uma expresso, as variveis desapareciam, sendo substitudas pelos seus valores. A partir do momento em que podemos alterar o valor de uma varivel, o seu comportamento torna-se menos claro. Para se explicar correctamente este comportamento necessrio passar para um modelo de avaliao mais elaborado designado modelo de avaliao em ambientes. Neste modelo, uma varivel j no uma designao de um valor mas sim uma designao de um objecto que contm um valor. Esse objecto pode ser visto como uma caixa onde se guardam coisas. Em cada instante, a varivel designa sempre a mesma caixa, mas esta pode guardar coisas diferentes. Segundo o modelo de ambientes, o valor de uma varivel o contedo da caixa que ela designa. A forma especial setq a operao que permite meter valores dentro da caixa. As variveis so guardadas em estruturas denominadas enquadramentos. Por exemplo, cada vez que usamos a forma let criado um novo enquadramento para conter as variveis estabelecidas pelo let. Todas as expresses pertencentes ao corpo do let sero avaliadas em relao a este enquadramento. Imaginemos agora a seguinte situao:
(let ((x 1)) (let ((y 2) (z 3)) (+ x y z)))
Neste exemplo, o corpo do primeiro let um novo let. Existem portanto dois enquadramentos. Estes enquadramentos esto organizados de modo a que o corpo do segundo let consiga fazer referncia s trs variveis x, y e z. Para isso, os enquadramentos so estruturados sequencialmente, desde aquele que for textualmente mais interior at ao mais exterior. Essa sequncia de enquadramentos designada por ambiente. Cada enquadramento uma tabela de ligaes, que associa as variveis aos seus valores correspondentes. Uma varivel nunca pode estar repetida num enquadramento, embora possa aparecer em vrios enquadramentos de um ambiente. Cada enquadramento aponta para o ambiente envolvente, excepto o ambiente global, que composto por um nico enquadramento sem ambiente envolvente. no ambiente global que esto guardadas todas as funes que usamos normalmente.
59
Como se v pelo exemplo. A partir do momento em que se estabeleceu uma ligao no ambiente global para a varivel y, j possvel avaliar aquele let apesar de ele fazer referncia a uma varivel livre. No entanto, a utilizao da forma especial setq para criar variveis globais considerada pouca correcta e o compilador emitir um aviso se encontrar uma destas formas de utilizao. A utilizao do setq deve ser restricta a modificaes do valor de variveis previamente estabelecidas. As regras de avaliao do modelo de ambientes so, em tudo, equivalentes s do modelo clssico, excepto no que diz respeito aplicao de funes. No modelo de ambientes todas as funes possuem um ambiente associado, que corresponde quele que existia quando a funo foi definida. Quando se aplica uma funo aos seus argumentos, cria-se um novo ambiente, cujo primeiro enquadramento contm as ligaes dos parmetros formais da funo aos seus argumentos e cujo
60
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi ambiente envolvente aquele em que a funo foi definida. em relao a este novo ambiente que se avalia o corpo da funo. Note-se que a forma especial defun define funes (i.e., cria uma ligao entre o nome da funo e a lambda correspondente ao seu corpo) sempre no ambiente global, enquanto que setq altera a ligao de uma varivel no primeiro enquadramento do ambiente em que a forma especial avaliada. S se a varivel no for encontrada na sequncia de enquadramentos que o setq cria uma no ambiente global. Exerccio 55 Interprete o comportamento da seguinte funo:
(let ((valor 0)) (defun incrementa () (setq valor (1+ valor)) valor)) > (incrementa) 1 > (incrementa) 2 > (incrementa) 3
Exerccio 56 Uma excelente aplicao de funes com estado local na criao de geradores de sequncias de nmeros, i.e., funes sem argumentos que, a cada invocao, devolvem o elemento que sucede logicamente a todos os que foram gerados anteriormente. Seguindo este paradigma, defina o gerador da sequncia de Fibonacci, que representada pela sucesso crescente 1, 2, 3, 5, 8, ..., em que cada nmero a soma dos dois ltimos que o precedem.
> (gera-fib) 1 > (gera-fib) 2 > (gera-fib) 3
Exerccio 57 Infelizmente, a funo gera-fib no sabe recomear. Para isso, necessrio defini-la (compil-la) outra vez. Este processo, como lgico, muito pouco prtico, em especial porque obriga o utilizador a ter acesso ao cdigo do gerador. Complemente a definio da funo gera-fib com uma outra funo denominada repoe-fib que repe o gerador em condies de recomear de novo desde o princpio.
> (gera-fib) 1
61
Note-se a conveno adotada para as variveis globais de usar nomes compreendidos entre um par de asteriscos. Quando se definem constantes essa conveno no se aplica. O fato de podermos ter variveis globais introduz uma alterao nas regras de avaliao. Tnhamos visto que as variveis que eram parmetros de funes (e, como tal, as variveis introduzidas por um let) tinham mbito lxico, ou seja, apenas podiam ser referidas dentro da regio textual que as introduziu. No entanto, as variveis globais como aceleracao-gravidade, *y* ou *z* podem ser referidas de qualquer ponto do programa, fazendo com que o seu mbito passe a ser vago. No entanto, apesar de ser possvel refernciar a varivel *y*, ser produzido um erro quando tentarmos determinar o seu valor, uma vez que ele ainda est indefinido. Ser preciso ligarmos um valor quela varivel antes de a podermos avaliar e, para isso, podemos usar um let.
> (defvar *y*) *Y* > (defun teste () (+ *y* *y*)) TESTE > (teste) Error: Unbound variable: *Y* > (let ((*y* 10)) (teste)) 20 >*y* Error: Unbound variable: *Y*
Repare-se que apesar de, momentaneamente, termos atribudo um valor varivel *y* por intermdio de um let, ela perdeu esse valor assim que terminou o let. A durao da varivel *y* , assim, dinmica. Apenas as variveis lxicas possuem durao indefinida. 62
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Variveis como *y* e *z* dizem-se especiais e possuem mbito vago e durao dinmica. Esta combinao d origem a um comportamento que se designa de mbito dinmico. Um dos aspectos mais crticos na utilizao de variveis de mbito dinmico o fato de, geralmente, no ser suficiente ler o cdigo de um programa para perceber o que que ele vai fazer-- tambm preciso execut-lo. O seguinte exemplo explica este ponto. Imaginemos as seguintes definies:
> (let ((x 2)) (defun soma-2 (y) (+ x y))) SOMA-2 > (let ((x 1000)) (soma-2 1)) 3 > (defparameter *x* 1) *X* > (let ((*x* 2)) (defun soma-2 (y) (+ *x* y))) SOMA-2 > (let ((*x* 1000)) (soma-2 1)) 1001
O primeiro exemplo envolve apenas variveis lxicas. Da que baste observar o texto da funo soma-2 para se perceber que a varivel x usada em (+ x y) toma sempre o valor 2. No segundo exemplo, a nica diferena est no fato de a varivel *x* ser especial. Nesta situao a funo soma-2 no usa o valor de *x* que existia no momento da definio da funo, mas sim o valor de *x* que existe no momento da execuo da funo. Desta forma, j no suficiente observar o texto da funo soma-2 para perceber o que ela faz. Por este motivo, o uso excessivo de variveis dinmicas pode tornar um programa difcil de ler e, consequentemente, difcil de desenvolver e difcil de corrigir.
63
10 Parmetros especiais
Exerccio 58 Defina a funo eleva, que eleva um nmero a uma determinada potncia. Se a potncia no for indicada dever ser considerada 2. Nota: a expresso (expt x y) determina a potncia y de x, i.e., . Exerccio 59 Reescreva a funo factorial de forma a gerar um processo iterativo mas sem usar funes auxiliares.
64
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Para alm do qualificador &optional existem ainda o &rest e o &key. O &rest s pode qualificar o ltimo parmetro de uma funo, e indica que esse parmetro vai ficar ligado a uma lista com todos os restantes argumentos. A ttulo de exemplo, temos:
> ((lambda (x y &rest z) (list x y z)) 1 2 3 4 5 6) (1 2 (3 4 5 6))
O qualificador &rest permite assim construir funes com qualquer nmero de argumentos. Exerccio 60 Defina a funo lista que recebe qualquer nmero de argumentos e constri uma lista com todos eles.
>(lista 1 2 3 4 5 6 7 8 9 0) (1 2 3 4 5 6 7 8 9 0)
A grande maioria das funes pr-definidas na linguagem para manipular listas possui parmetros opcionais e de chave. Repare-se que as chaves so tratadas de forma especial pelo avaliador. Efetivamente, se assim no fosse, quando especificvamos os argumentos de uma funo com parmetros de chave, o avaliador iria tentar determinar o valor das chaves, gerando ento um erro por estas no terem valor. Na realidade, quando um smbolo qualquer precedido por dois pontos, esse smbolo considerado como especial, pertencendo ao conjunto dos smbolos chaves, e avaliando para si prprio.
> ola Error: Unbound variable: OLA > 'ola OLA > :ola :OLA
65
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Os tipos aglomerados constituem uma das grandes utilizaes dos parmetros de chave. Nestes tipos de dados, os construtores limitam-se a realizar um agrupamento de valores para os diversos constituintes do tipo. Vimos o exemplo de um automvel, que era constitudo por uma marca, um modelo, um nmero de portas, etc. Embora no exista qualquer razo para que a marca de um automvel seja mais importante que o seu nmero de portas, infelizmente a ordenao implcita dos argumentos das funes Lisp impem que assim seja. Podemos resolver este problema usando parmetros de chave, de forma a eliminar a ordenao dos argumentos e permitir ao utilizador especific-los pela ordem que entender, por exemplo:
(defun novo-automovel (&key marca modelo portas) (list marca modelo portas)) > (novo-automovel :portas 2 :marca 'honda :modelo 'civic) (honda civic 2)
Para alm da possibilidade de alterao da ordem dos argumentos, o qualificador &key ajuda legibilidade do programa, ao tornar explcito o papel que cada argumento tem na funo.
66
67
11 Macros
Como referimos na apresentao da linguagem Lisp, existem certas formas da linguagem que no obedecem s regras de avaliao usuais. Essas formas designam-se formas especiais e o if um exemplo. Cada forma especial possui a sua prpria regra de avaliao. Vimos que, por isso, era impossvel definir o if como se fosse uma funo, pois todos os operandos (o teste, o consequente e a alternativa) seriam avaliados. Embora a linguagem Lisp possua muitas formas especiais, possvel ``criar'' outras formas especiais atravs da utilizao de macros. Uma macro uma forma que a linguagem expande para outra forma, superando assim as dificuldades inerentes avaliao dos argumentos que as funes realizam. Na realidade, Lisp possui muito poucas formas especiais reais. A grande maioria das formas especiais so implementadas atravs de macros, usando a forma especial defmacro cuja sintaxe igual da defun.
68
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi 3. Escrever a expanso da macro. Nesta fase determina-se a expresso Lisp que a macro deve produzir quando expandida. A expanso qualquer expresso Lisp, que pode inclusive fazer referncia a outras macros. 4. Escrever a macro usando a forma especial defmacro. esta a fase mais delicada do processo, em que se programa um processo de transformar a forma especial que queremos definir numa expresso que use outras formas especiais j definidas. A ttulo de exemplo vamos definir a forma especial meu-if cujo objetivo simplificar o uso do cond quando s existe um teste, um consequente e uma alternativa. A sintaxe da forma meu-if :
(meu-if teste consequente alternativa)
A definio da macro :
(defmacro meu-if (teste consequente alternativa) (list 'cond (list teste consequente) (list t alternativa)))
Exerccio 61 Implemente a macro quando, que recebe um teste e um conjunto de expresses. Esta forma especial avalia o teste e, quando este verdade, avalia sequencialmente as expresses, devolvendo o valor da ltima. Se o teste falso, a forma retorna nil sem avaliar mais nada. Esta macro j existe em Lisp e denomina-se when.
69
A linguagem Lisp possui, previamente definidos, vrios caracteres de macro. A plica um deles, o ponto e vrgula (que representa um comentrio) outro, etc. Alguns dos caracteres de macro so precedidos por um caracter especial, considerado o caracter de despacho, permitindo aumentar o nmero de possveis caracteres de macro sem reduzir o nmero de caracteres normais utilizveis. O caracter de despacho mais usado o 70
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi cardinal, mas pode ser qualquer outro. Como exemplos destes caracteres de macro com despacho temos o cardinal-plica para indicar funes, o cardinal-barra para indicar caracteres, etc. De todos os caracteres de macro, aqueles que so particularmente teis para a escrita de macros so o plica para trs (`--backquote), a vrgula (,--comma) e o vrgula-arroba (,@-comma-at). O backquote indica ao avaliador que no deve avaliar uma expresso exceto quando for indicado em contrrio. Quando usado isoladamente, o backquote funciona exactamente como o quote. No entanto, a sua combinao com o comma e o comma-at permite simplificar imenso a escrita de expresses complexas. O comma s pode ser utilizado numa expresso (uma lista, tipicamente) precedida do backquote, e informa o avaliador que a expresso que se segue para avaliar e o seu resultado inserido na lista. O commaat idntico mas o resultado da avaliao tem de ser uma lista cujos elementos so inseridos. A ttulo de exemplo, temos:
> `((+ 1 2) ,(+ 1 2) (list 1 2) ,(list 1 2) ,@(list 1 2)) ((+ 1 2) 3 (LIST 1 2) (1 2) 1 2)
71
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi A implementao da macro meu-cond anterior implica que a expanso apenas parcial, i.e., enquanto houver clusulas, o meu-cond expande para outro meu-cond. Implemente a macro meu-cond de modo a realizar a expanso de uma s vez. Exerccio 66 A macro seja implementa a mesma funcionalidade que a forma especial let. Como se sabe, o let no mais do que uma macro que expande para uma lambda. A idia ser definir a macro seja exatamente da mesma forma, i.e., dever existir uma correspondncia entre a expresso:
(seja ((x 10) (y 20)) (+ x y))
e a sua expanso:
((lambda (x y) (+ x y)) 10 20)
Como se disse, esta macro j existe em Lisp e designa-se let. Exerccio 67 Escreva a macro enquanto, que recebe um teste e um conjunto de expresses. A forma enquanto deve avaliar o teste e, caso seja verdade, avaliar sequencialmente as expresses e voltar ao princpio. Caso o teste seja falso, deve terminar com o valor nil. Exerccio 68 Escreva a macro caso, que recebe uma expresso e um conjunto de pares tomoexpresses. A forma especial caso avalia a primeira expresso, e compara o resultado (usando a funo eql) com cada um dos tomos em sequncia. Se um dos tomos emparelhar, so avaliadas as expresses a ele associadas e retornado o valor da ltima. Um exemplo de utilizao seria:
(defun inverso-fact (x) (caso x (1 (print 'basico) 1) (2 (print 'menos-basico) 2) (6 (print 'ainda-menos-basico) 3))) > (inverso-fact 1)
72
11.6 Iteradores
Como se pode depreender dos exemplos apresentados, as macros destinam-se essencialmente criao de acar sinttico, i.e., de expresses que sejam mais simples de utilizar que outras j existentes. Esta caracterstica torna as macros ferramentas extremamente teis para criao de tipos abstratos de informao capazes de dar ao utilizador iteradores sobre os objetos desse tipo. A ttulo de exemplo, vamos considerar a definio de um iterador para os elementos de uma lista. Este iterador dever ser uma forma especial que recebe um smbolo (uma varivel), uma lista e um conjunto de expresses, e itera aquelas expresses com o smbolo ligado a cada elemento da lista. Apresenta-se agora um exemplo da sintaxe da forma especial:
(itera-lista (elem (list 1 2 3 4 5)) (print elem))
73
O problema est no fato de a macro estabelecer uma varivel denominada lista, que interfere com a varivel exterior do segundo exemplo, pois tem o mesmo nome, ficando assim obscurecida. A referncia a lista feita no corpo da macro refere-se assim varivel interna da macro, e no que seria desejvel. Nesta situao diz-se que a macro capturou variveis. A soluo para este problema est na utilizao de variveis que no possam interferir de modo algum com outras j existentes. Um remdio possvel ser criar as variveis necessrias s macros com nomes estranhos, com pouca probabilidade de serem usados pelo utilizador da macro, como por exemplo, %%%$$$lista$$$%%%. No entanto esta soluo no perfeita. O melhor a fazer usar novos smbolos que no se possam confundir com os j existentes. A funo gensym produz um smbolo novo e nico de cada vez que chamada, sendo ideal para resolver estas dificuldades. Exerccio 69 Defina a macro itera-lista usando a referida funo gensym para proteger as variveis do utilizador de uma captura indevida.
> (let ((lista '(1 2 3))) (itera-lista (x '(4 5 6)) (print (cons x lista)))) (4 1 2 3) (5 1 2 3) (6 1 2 3) NIL
Esta forma especial j existe em Lisp e denomina-se dolist. Exerccio 70 Defina de novo a forma especial itera-lista, mas recorrendo desta vez abordagem da programao funcional, i.e., sem utilizar formas especiais para ciclos nem atribuio de valores a variveis. Exerccio 71 A forma especial caso definida anteriormente sofria do mesmo problema do iteralista, pois a varivel usada para guardar o valor temporrio pode obscurecer variveis idnticas declaradas exteriormente. Redefina a macro de forma a evitar esse perigo.
11.7 Fichas
74
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Como vimos, uma ficha (no sentido da linguagem Pascal) no mais do que um tipo aglomerado. Uma ficha possui um nome e composta por um conjunto de campos, cada um com um nome distinto. Cada elemento de um dado tipo de ficha corresponde a um conjunto de valores apropriados para cada campo desse tipo. A utilizao de fichas de tal modo simples que todas as linguagens de programao possuem capacidades prprias para lidar com elas. Um dado tipo de ficha no necessita mais do que um construtor para os elementos desse tipo, um seletor e um modificador para cada um dos campos da ficha e, possivelmente, um reconhecedor de fichas de um dado tipo. Dadas as capacidades da linguagem Lisp em aglomerar facilmente objetos, a implementao de fichas uma tarefa de tal modo simples que bastante fcil automatiz-la. Vimos na descrio do tipo aglomerado automvel que ele era definido por um construtor:
(defun novo-automovel (&key marca modelo portas) ...)
e pelos selectores:
(defun automovel-marca (automovel) ...) (defun automovel-modelo (automovel) ...) (defun automovel-portas (automovel) ...)
Vamos tambm incluir um reconhecedor de automveis muito til para distinguirmos os diversos tipos de fichas:
(defun automovel? (obj) ...)
Repare-se que o conjunto de funes que apresentamos constituem um modelo para a definio de fichas. A ficha automvel possui um construtor criado atravs da concatenao da palavra ``novo'' ao nome da ficha. Cada seletor dado pela concatenao do nome da ficha ao nome do campo a que ele diz respeito. Cada modificador dado pela concatenao da palavra ``muda'' ao nome do seletor correspondente e terminado com a letra ``!'. O reconhecedor dado pelo nome da ficha terminado com a letra ``?'. Qualquer outra ficha seria definida de forma idntica, pelo que podemos fazer uma abstrao da definio de fichas, criando uma macro que encapsule a definio de todas aquelas funes.
75
Linguagem de ProgramaoII: Programao Funcional usando Lisp Crisitano Biancardi Um dos problemas que temos de resolver a implementao das fichas. Vimos no exemplo do automvel que o podamos implementar como uma lista com os valores dos vrios campos. Infelizmente, aquela implementao no nos permite distinguir o tipo de registo automvel de outros tipos de registo, pelo que temos de modificar a implementao de modo a isso ser possvel. Para minimizar as alteraes, podemos considerar que cada registo implementado por uma lista cujo primeiro elemento o nome da ficha e cujos restantes elementos so os valores dos campos da ficha. Isto permite manter a mesma lgica de acesso e modificao dos campos desde que os ndices desses campos na lista sejam incrementados de uma unidade. Assim sendo, o construtor ficar qualquer coisa do gnero:
(defun novo-automovel (&key marca modelo portas) (list 'automovel marca modelo portas))
Um modificador ser:
(defun muda-automovel-marca! (automovel nova-marca) (muda-n-esimo! 1 automovel nova-marca))
Podemos agora definir uma macro designada ficha, cuja utilizao ser da seguinte forma:
(ficha automovel marca modelo portas)
Para definirmos as vrias operaes vamos necessitar de concatenar smbolos. Para isso, necessrio criar um smbolo, atravs da funo intern, cujo nome seja a concatenao, atravs da funo concatenate, dos nomes dos smbolos, obtidos mapeando a funo string nesses smbolos, i.e.:
(defun junta-nomes (&rest nomes) (intern (apply #'concatenate 'string (mapcar #'string nomes))))
77
Exerccio 71 Um dos melhoramentos que seria interessante incluir na definio de fichas seria a incluso de valores por omisso para qualquer campo. Poderamos indicar que um automvel possui geralmente quatro portas da seguinte forma:
(ficha automovel marca modelo (portas 4))
Assim, se se criasse um automvel sem especificar o nmero de portas, ele seria de 4. Implemente essa alterao.
> (macroexpand-1 '(ficha automovel marca modelo (portas 4)))) (PROGN (DEFUN NOVO-AUTOMOVEL (&KEY MARCA MODELO (PORTAS 4)) (LIST 'AUTOMOVEL MARCA MODELO PORTAS)) (DEFUN AUTOMOVEL-MARCA (AUTOMOVEL) (NTH 1 AUTOMOVEL)) ...
Esta forma de criao de fichas j existe em Common Lisp atravs da macro defstruct.
78
Referncias
Steelem, Guy L..Commom Lisp the Language. Ed. Digital Press, 1990. dispovel online: http://www.supelec.fr/docs/cltl/cltl2.html.
79