Docsity
Docsity

Prepare-se para as provas
Prepare-se para as provas

Estude fácil! Tem muito documento disponível na Docsity


Ganhe pontos para baixar
Ganhe pontos para baixar

Ganhe pontos ajudando outros esrudantes ou compre um plano Premium


Guias e Dicas
Guias e Dicas


Apostila Lisp, Notas de aula de Informática

Um apostila sobre a linguagem Lisp

Tipologia: Notas de aula

Antes de 2010

Compartilhado em 28/10/2010

amanda-de-araujo-motta-6
amanda-de-araujo-motta-6 🇧🇷

1 documento

1 / 79

Toggle sidebar

Esta página não é visível na pré-visualização

Não perca as partes importantes!

bg1
UVV
Centro Universitário Vila Velha
Linguagens de Programação II
Programação Funcional usando LISP
Professor: Cristiano Biancardi
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28
pf29
pf2a
pf2b
pf2c
pf2d
pf2e
pf2f
pf30
pf31
pf32
pf33
pf34
pf35
pf36
pf37
pf38
pf39
pf3a
pf3b
pf3c
pf3d
pf3e
pf3f
pf40
pf41
pf42
pf43
pf44
pf45
pf46
pf47
pf48
pf49
pf4a
pf4b
pf4c
pf4d
pf4e
pf4f

Pré-visualização parcial do texto

Baixe Apostila Lisp e outras Notas de aula em PDF para Informática, somente na Docsity!

UVV

Centro Universitário Vila Velha

Linguagens de Programação II

Programação Funcional usando LISP

Professor: Cristiano Biancardi

Sumário

1 - Elementos da Linguagem

Em linguagens Funcionais toda a programação é realizada por meio da aplicação de funções a argumentos. As variáveis e as operações de atribuição usadas pelas linguagens imperativas não são necessárias. Os laços de repetição são substituídos por chamadas a funções recursivas. Baseiam-se fortemente nos conceitos das funções matemáticas.

Qualquer linguagem de programação lida com duas espécies de objetos: dados e procedimentos. Os dados são os objectos que pretendemos manipular. Os procedimentos são descrições das regras para manipular esses dados.

Se considerarmos a linguagem da matemática, podemos identificar os números como dados e as operações algébricas como procedimentos e podemos combinar os números entre si usando aquelas operações. Por exemplo, 2 × 2 é uma combinação, tal como 2 × 2 × 2 e 2 × 2 × 2 × 2. No entanto, a menos que pretendamos ficar eternamente a resolver problemas de aritmética elementar, somos obrigados a considerar operações mais elaboradas que representam padrões de cálculos. Neste último exemplo, é evidente que o padrão que está a emergir é o da operação de potenciação, i.e, multiplicação sucessiva, tendo esta operação sido definida na matemática há já muito tempo.

Tal como a linguagem da matemática, uma linguagem de programação 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 padrões de cálculo de modo a permitir tratá-los como operações simples, definindo novas operações que representem esses padrões de cálculo.

1.1 - Elementos primitivos

Elementos primitivos são as entidades mais simples com que a linguagem lida. Um número, por exemplo, é um dado primitivo.

Como dissemos anteriormente, o Lisp executa um ciclo read-eval-print. Isto implica que tudo o que escrevemos no Lisp tem de ser avaliado, i.e., tem de ter um valor, valor esse que o Lisp escreve no ecran.

Assim, se dermos um número ao avaliador, ele devolve-nos o valor desse número. Quanto vale um número? O melhor que podemos dizer é que ele vale aquilo que vale. Por exemplo, o número 1 vale 1.

1 1 12345 12345

Como se vê no exemplo, em Lisp, os números podem ser inteiros ou reais.

Exercício 2

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

1.2 - Combinações

Combinações são entidades complexas feitas a partir de entidades mais simples. Por exemplo, na matemáticas números podem ser combinados usando operações como a soma ou o produto. Como exemplo de combinações matemáticas, temos 1 + 2 e 1 + 2 × 3. A soma e o produto de números são exemplos de operações extremamente elementares consideradas procedimentos primitivos.

Em Lisp, criam-se combinação escrevendo uma sequência de expressões entre um par de parênteses. Uma expressão é um elemento primitivo ou uma outra combinação. A expressão (+ 1 2) é uma combinação dos elementos primitivos 1 e 2 através do procedimento +. Já no caso (+ 1 (* 2 3)) a combinação é ocorre entre 1 e (* 2 3), sendo esta última expressão uma outra combinação.

Não é difícil de ver que as únicas combinações com utilidade são aquelas em que as expressões correspondem a operadores e operandos. Por convenção, o Lisp considera que o primeiro elemento de uma combinação é um operador e os restantes são os operandos.

A notação que o Lisp utiliza para construir expressões (operador primeiro e operandos a seguir) é designada por notação prefixa. Esta notação costuma causar alguma perplexidade a quem inicia o estudo da linguagem, que espera uma notação mais próxima da que aprendeu em aritmética e que é usada habitualmente nas outras linguagens de programação. Nestas, a expressão (+ 1 (* 2 3)) é usualmente escrita na forma 1 + 2 * 3 (designada notação infixa) que, normalmente, é mais simples de ler por um ser humano. No entanto, a notação prefixa usada pelo Lisp tem largas vantagens sobre a notação infixa:

! É muito fácil usar operadores que têm um número variável de argumentos, como por exemplo: ! > (+ 1 2 3) ! 6 ! > (+ 1 2 3 4 5 6 7 8 9 10)

Quando a regra de indentação não é suficiente, usam-se pequenas variações, como seja colocar o operador numa linha e os operandos por debaixo, por exemplo:

(umaoperacaocomumnomemuitogrande 1 2 3 4)

A indentação é fundamental em Lisp pois é muito fácil escrever código 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 parênteses. Desta forma, após algum tempo de prática, torna-se muito fácil escrever e ler os programas, por mais complexa que possa parecer a sua estrutura.

Exercício 3

Converta as seguintes expressões da notação infixa da aritmética para a notação 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

Exercício 4

Converta as seguintes expressões da notação prefixa do Lisp para a notação infixa da aritmética: (* (/ 1 2) 3) (* 1 (- 2 3)) (/ (+ 1 2) 3) (/ (/ 1 2) 3) (/ 1 (/ 2 3)) (- (- 1 2) 3) (- 1 2 3)

1.3 - Avaliação de Combinações

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

O avaliador determina o valor de uma combinação como o resultado de aplicar o procedimento especificado pelo operador ao valor dos operandos. O valor de cada operando é designado de argumento do procedimento. Assim, o valor da combinação (+ 1 (* 2 3)) é o resultado de somar o valor de 1 com o valor de (* 2 3).

Como já se viu, 1 vale 1 e (* 2 3) é uma combinação 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

Exercício 5

Calcule o valor das seguintes expressões Lisp: (* (/ 1 2) 3) (* 1 (- 2 3)) (/ (+ 1 2) 3) (/ (/ 1 2) 3) (/ 1 (/ 2 3)) (- (- 1 2) 3) (- 1 2 3) (- 1)

1.4 - Definição de Funções

Tal como em matemática, pode-se definir numa linguagem de programação a operação de elevar um número ao quadrado. Assim, se pretendermos determinar o produto de um número por ele próprio, escrevemos a combinação (* x x) sendo x o número, i.e.

(* 5 5) 25 (* 6 6)

36

Os números 5 e 6 e a operação * são elementos primitivos. As expressões (* 5 5) e (* 6 6) são combinações. À combinação genérica (* x x) queremos associar um nome, por exemplo, quadrado. Isso equivale a acrescentar uma nova função à linguagem.

A título de exemplo, vamos definir a função quadrado:

(defun quadrado (x) (* x x)) quadrado

Para se definirem novas funções em Lisp, é necessário criar uma combinação de quatro elementos. O primeiro elemento desta combinação é a palavra defun, que informa o avaliador que estamos a definir uma função. O nome defun é uma abreviatura de ``define function''. O segundo elemento é o nome da função que queremos definir, o terceiro elemento é uma combinação com os parâmetros da

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 não queremos perder o trabalho que estivemos a escrever, devemos escrever as funções num ficheiro e ir passando-as para o Lisp. A grande maioria das implementações do Lisp permite fazer isto de forma automática.

1.5 – Funções básicas

( + ) ( Soma )

Note que 9/2 e 17/2 são números racionais que podem ser manipulados da mesma forma de inteiros e floats, e que a função soma não se restringe a apenas dois parâmetros.

( - ) ( Subtração )

( * ) ( Multiplicação )

( / ) ( Divisão )

( / 2 ) " 1/2 - inverso do número

Note que se houver mais de dois argumentos na divisão, o primeiro deles será dividido sucessivamente pelos demais.

sqrt ( Raiz quadrada )

( sqrt 4 ) " 2. ( sqrt 4.0 ) " 2. ( sqrt –4 ) " #C(0,0 2.0)

Se o argumento for um número negativo, a função não retornará um erro mas sim um número complexo na forma #C(3 2) que é equivalente a 3 + 2i.

expt ( Exponencial )

( expt 2 10 ) " 1024 ( expt 2 10.0 ) " 1024. ( expt 2/3 3 ) " 8/ ( expt –4 ½ ) " #C(1.22514845490862E-16 2.0)

Se algum dos argumentos for do tipo float, o resultado também será convertido para float, podendo ocorrer aproximações do número. No caso de 0.0, o resultado é aproximado para #C(1.22514845490862E-16 2.0).

log ( Logaritmo )

( log 1 ) " 0. ( log 10 ) " 2. ( log 10 2 ) " 3.

abs ( Valor Absoluto )

( abs 8 ) " 8 ( abs –8 ) " 8 ( abs –8.9 ) " 8.

truncate ( Trunca )

( truncate 13.5 ) " 13 0. ( truncate 0.7 ) " 0 0. ( truncate –1.7 ) " -1 -0.

Truncar é retornar o valor inteiro de um número. Note que a função retorna dois valores: o primeiro é o valor truncado, a parte inteira, e a segunda é a parte decimal do número, “o resto”.

Vamos inicializar quatro variáveis 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 ) " T ( > 10 100 1 0.1 ) " NIL

Retorna verdadeiro se os elementos estão dispostos em ordem descendente.

( < ) ( Ordem Ascendente )

( < 1 3 5 2 ) " NIL

( < 1 two 4 ) " T

Retorna verdadeiro se os elementos estão dispostos em ordem ascendente.

( = ) ( Igual )

( = ( / 2.0 3.0) ( / 4.0 6.0 ) ) " T

( = ( 3.141592653 ( /22 7 ) ) " NIL

max ( Máximo )

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

Retorna o argumento de maior valor.

min ( Mínimo )

( min 1 –2 3 4 5 6 7 ) " - ( min ( expt 2 10 ) ( expt 10 2 ) ) " 100

Retorna o argumento de menor valor.

evenp ( Par )

( evenp 17 ) " NIL ( evenp ( * 3 2 ) ) " T

Testa se o argumento é par.

oddp ( Ímpar )

( oddp 17 ) " T ( oddp two ) " NIL

Testa se o argumento é ímpar.

minusp ( Negativo )

( minusp 17 ) " NIL ( minusp –13 ) " T

Verifica se o argumento é um número negativo.

zerop ( Zero )

( zerop ( * 2 0 ) ) " T ( zerop ( + 2 0 ) ) " NIL ( zerop zero ) " T

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

1.7 – Avaliando funções

Em Lisp, + é uma função, assim uma expressão como (+ 2 3) é uma chamada de função envolvendo dois argumentos.

Quando Lisp avalia uma chamada de função, ele o faz em duas etapas: ! primeiramente os argumentos são avaliados, da esquerda para a direita. Neste contexto, cada argumento se "auto–avalia". Logo, 2 avalia para 2 e 3 valia para 3, e, assim, os valores dos argumentos são 2 e 3, respectivamente; ! os valores dos argumentos são passados a função designada pelo operador. Neste caso, é a função +, que retorna 5.

Exercício 6

  • Crie uma função que some três números.
  • Crie uma função que calcule o delta de uma equação do segundo grau. Delta = b^2 – 4ac

Resposta

1.9 – Inserindo comentários

Para facilitar a compreensão de um código faz-se uso de comentários. Em Lisp, os comentários devem ser precedidos de ponto-e-vírgula “ ; ”, caracter que indica que a respectiva linha de código não deve ser avaliada pelo interpretador.

( defun novogosto ( nome ) ; adiciona nome no início da lista gosta ( setf gosta ( cons nome gosta ) ) ; deleta nome da lista detesta ( setf detesta ( remove nome detesta) ) )

2 – Expressões Condicionais

Existem muitas operações cujo resultado depende da realização de um determinado teste. Por exemplo, a função matemática abs --que calcula o valor absoluto de um número-- equivale ao próprio número, se este é positivo, ou equivale ao seu simétrico se for negativo. Estas expressões, cujo valor depende de um ou mais testes a realizar previamente, permitindo escolher vias diferentes para a obtenção do resultado, são designadas expressões condicionais.

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

Seguindo o mesmo raciocínio, uma expressão condicional com mais de duas alternativas deverá implicar um teste com igual número de possíveis resultados. Uma expressão da forma caso o valor do teste seja 1, 2 ou 3, o valor da expressão é 10, 20 ou 30, respectivamente'' é um exemplo desta categoria de expressões que, embora seja considerada pouco intuitiva, existe nalgumas linguagens (Basic, por exemplo). Contudo, estas expressões podem ser facilmente reduzidas à primeira. Basta decompor a expressão condicional múltipla numa composição de expressões condicionais simples, em que o teste original é também decomposto numa série de testes simples. Assim, poderíamos transformar o exemplo anterior em:se o valor do teste é 1, o resultado é 10, caso contrário, se o valor é 2, o resultado é 20, caso contrário é 30''.

2.1 - Predicados

Desta forma, reduzimos todos os testes a expressões cujo valor pode ser apenas um de dois, e a expressão condicional assume a forma de ``se ...então ...caso contrário ...''. Nesta situação, a função 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 não implica que o valor seja de um tipo de dados especial, como o booleano ou lógico de algumas linguagens (como o Pascal), mas apenas que a expressão condicional considera alguns dos valores como representando o verdadeiro e os restantes como o falso.

Em Lisp, as expressões 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 expressão condicional, qualquer número é um valor verdadeiro. Contudo, não faz muito sentido para o utilizador humano considerar um número como verdadeiro ou falso, pelo que se introduziu uma constante na linguagem para representar verdade. Essa constante representa-se por t.

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

2.3 - Seleção simples

O if é a expressão condicional mais simples do Lisp. O if determina a via a seguir em função do valor de uma expressão lógica. A sintaxe do if é:

(if condição consequente alternativa )

Para o if, a condição é o primeiro argumento, o consequente no caso de a condição ser verdade é o segundo argumento e a alternativa no caso de ser falso é o terceiro argumento.

(if (> 4 3) 5

5

Uma expressão if é avaliada determinando o valor da condição. Se ela for verdade, é avaliado o consequente. Se ela for falsa é avaliada a alternativa.

Exercício 8

Defina uma função soma-grandes que recebe três números como argumento e determina a soma dos dois maiores.

Exercício 9

Escreva uma função que calcule o factorial de um número.

A função fact é um exemplo de uma função recursiva, i.e., que se refere a ela própria.

2.4 - Seleção Múltipla

Além do if, existe outra expressão condicional em Lisp. O cond é uma versão mais potente que o if. É uma espécie de switch-case do C. A sua sintaxe é:

(cond ( condição-1 expressão-1 )

( condição-2 expressão-2 )

( condição-n expressão-n ))

Designa-se o par ( condição-i expressão-i ) por cláusula. O cond testa cada uma das condições em sequência, e quando uma delas avalia para verdade, é devolvido o valor da expressão correspondente, terminando a avaliação. Um exemplo será:

(cond ((> 4 3) 5) (t 6)) 5

O cond permite uma análise de casos mais simples do que o if.

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

A função equivalente usando if seria mais complicada.

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