Tutorial de LISP - Parte 2

[PostScript] [RTF]


3.2.6. Funções

Vimos exemplos de funções acima. Aqui mais alguns:

> (+ 3 4 5 6)                   ;this function takes any 
                       ;number of arguments
18
> (+ (+ 3 4) (+ (+ 4 5) 6)) ;isn't prefix notation fun?
22

3.2.6.1. Definindo uma função:

> (defun foo (x y) (+ x y 5)) 
FOO
> (foo 5 0)                     ;chamando a função
10

3.2.6.2. Função Recursiva

> (defun fact (x) 
    (if (> x 0) 
      (* x (fact (- x 1)))
      1
  ) )

FACT
> (fact 5)
120

3.2.6.3. Funções Mutuamente Recursivas

> (defun a (x) (if (= x 0) t (b (- x)))) 
A
> (defun b (x) (if (> x 0) (a (- x 1)) (a (+ x 1))))
B
> (a 5)
T

3.2.6.4. Função com múltiplos comandos em seu corpo

> (defun bar (x) 
    (setq x (* x 3)) 
    (setq x (/ x 2)) 
    (+ x 4)
  )
BAR
> (bar 6)
13

3.2.6.5. Escopo de variáveis

Quando nós definimos foo, nós lhe atribuímos dois argumentos, x e y. Quando chamamos foo, necessitamos prover valores para esses dois argumentos. O primeiro será o valor de x durante a duração da chamada a foo, o segundo o valor de y durante a duração da chamada a foo. Em LISP, a maioria das variáveis são escopadas lexicamente. Isto significa que se foo chama bar e bar tenta referenciar x, bar não obterá os valor de x de foo.

> (defun foo (x y) (+ x y 5)) 
FOO

O processo de se associar um valor a um símbolo durante um certo escopo léxico é chamado em LISP de ateamento. x estava atado ao escopo de foo.

3.2.6.6. Número Variável de Argumentos para Funções

Você pode especificar também argumentos opcionais para funções. Qualquer argumento após o símbolo &optional é opcional:

> (defun bar (x &optional y) (if y x 0))
BAR

É perfeitamente legal chamar a função BAR com um ou dois argumentos. Se for chamada com um argumento, x será atado ao valor deste argumento e y será atado a NIL.

> (bar 5)
0
> (bar 5 t)
5

Se for chamada com dois argumentos, x e y serão atados aos valores do primeiro e segundo argumento respectivamente.



A função BAAZ possui dois argumentos opcionais, porém especifica um valor default para cada um deles.

> (defun baaz (&optional (x 3) (z 10)) (+ x z))
BAAZ
> (baaz 5)
15
> (baaz 5 6)
11
> (baaz)
13

Se quem a chama especificar somente um argumento, z será atado a 10 ao invés de NIL. Se nenhum argumento for especificado, x será atado a 3 e z a 10.

3.2.6.7. Número Indefinido de Parâmetros

Você pode fazer a sua função aceitar um número indefinido de parâmetros terminando a sua lista de parâmetros com o parâmetro &rest. LISP vai coletar todos os argumentos que não sejam contabilizados para algum argumento formal em uma lista a atá-la ao parâmetro &rest :

> (defun foo (x &rest y) y)
FOO
> (foo 3)
NIL
> (foo 4 5 6)
(5 6)

3.2.6.8. Passagem de Parâmetros por Nome

Existe ainda um tipo de parâmetro opcional chamado de parâmetro de palavra-chave. São parâmtros que quem chama pode passar em qualquer ordem, pois os valores são passados precedidos pelo nome do parâmetro formal a que se referem:

> (defun foo (&key x y) (cons x y))
FOO

> (foo :x 5 :y 3)
(5 . 3)

> (foo :y 3 :x 5)
(5 . 3)

> (foo :y 3)
(NIL . 3)

> (foo)
(NIL)

Um parâmetro &key pode ter um valor default também:

> (defun foo (&key (x 5)) x)
FOO

> (foo :x 7)
7

> (foo)
5

3.2.7. Impressão

Algumas funções podem provocar uma sáida. A mais simples é print, que imprime o seu argumento e então o retorna.

> (print 3)
3
3

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

O primeiro argumento a format é ou t, ou NIL ou um arquivo.

O segundo argumento é um template de formatação, o qual é um string contendo opcionalmente diretivas de formatação, de forma similar à Linguagem "C": "An atom: ~S~%and a list: ~S~%and an integer:~D~%"

Todos os argumentos restantes devem ser referenciados a partir do string de formatação.

No exemplo acima, há três diretivas de formatação: ~S, ~D e ~%.:

3.2.8. Forms e o Laço Top-Level

As coisas que você digita para o interpretador LISP são chamadas forms. O interpretador repetidamente lê um form, o avalia e imprime o resultado.

Alguns forms vão provocar erros. Após um erro, LISP vai pô-lo/la no ambiente do debugger.

Debuggers de interpretadores LISP são muito diferentes entre si. A maioria aceita um "help" ou ":help" para ajudá-lo/la na debugação.

No debugador do CLISP você pode sair dando um Control-Z (em DOS) ou Control-D (em Unix).

Em geral, um form é ou um átomo (p.ex.: um símbolo, um inteiro ou um string) ou uma lista.

Se o form for um átomo, LISP o avalia imediatamente. Símbolos avaliam para seu valor, inteiros e strings avaliam para si mesmos.

Se o form for uma lista, LISP trata o seu primeiro elemento como o nome da função, avaliando os elementos restantes de forma recursiva. Então chama a função com com os valores dos elementos restantes como argumentos.

Por exemplo, se LISP vê o form (+ 3 4):

Irá tratar + como o nome da função.

Ele então avaliará 3 para obter 3, avaliará 4 para obter 4 e finalmente chamará + com 3 e 4 como argumentos.

A função + retornará 7, que LISP então imprime.

O top-level loop provê algumas outras conveniências.

Uma particularmente interessante é a habilidade de falar a respeito dos resultados de forms previamente digitados: LISP sempre salva os seus três resultados mais recentes. Ele os armazena sob os símbolos *, ** e ***.

> 3
3
> 4
4
> 5
5
> ***
3
> ***
4
> ***
5
> **
4
> *
4

3.2.9. Forms especiais

Há um número de forms especiais que se parecem com chamadas a funções mas não o são. Um form muito útil é o form aspas. As aspes prevêm um argumento de ser avaliado.

> (setq a 3)
3
> a
3
> (quote a)
A
> 'a                    ;'a is an abbreviation for (quote a)
A

Outro form especial similar é o form function.

Function provoca que seu argumento seja interpretado como uma função ao invés de ser avaliado:

> (setq + 3)
3
> +
3
> '+
+
> (function +)
#<Function + @ #x-fbef9de>
> #'+                   ;#'+ is an abbreviation for (function +)
#<Function + @ #x-fbef9de>

O form especial function é útil quando você deseja passar uma função como parâmetro para outra função. Mais tarde apresentaremos alguns exemplos de funções que aceitam outras funções como parâmetros.

3.2.10. Binding - Atamento/Amarração

Binding é uma atribuição escopada lexicamente. Ela ocorre com as variáveis de uma lista de parâmetros de uma função sempre que a função é chamada: os parâmetros formais são atados aos parâmetros reais pela duração da chamada à função.

Você pode também amarrar variáveis em qualquer parte de um programa com o form especial let:

        (let ((var1 val1)
              (var2 val2)
              ...
             )
          body
        )

Let ata var1 a val1, var2 a val2, e assim por diante; então executa os comandos de seu corpo. O corpo de um let segue as mesmas regras de um corpo de função:

> (let ((a 3)) (+ a 1))
4

> (let ((a 2) 
        (b 3)
        (c 0))
    (setq c (+ a b))
    c
  )
5

> (setq c 4)
4

> (let ((c 5)) c)
5

> c
4

A invés de (let ((a nil) (b nil)) ...)você pode escrever
(let (a b) ...).

Os valores val1, val2, etc. dentro de um let não podem referenciar as variáveis var1, var2, etc. que o let está atando:

> (let ((x 1)
        (y (+ x 1)))
    y
  )
Error: Attempt to take the value of the unbound symbol X

Se o símbolo x já possui um valor, coisas estranhas podem acontecer:

> (setq x 7)
7
> (let ((x 1)
        (y (+ x 1)))
    y
  )
8

O form especial let* é semelhante, só que permite que sejam referenciadas variáveis definidas anteriormente:

> (setq x 7)
7
> (let* ((x 1)
         (y (+ x 1)))
    y
  )
2

O form

        (let* ((x a)
               (y b))
          ...
        ) 

é equivalente a:

        (let ((x a))
          (let ((y b))
            ...
        ) )

3.2.11. Dynamic Scoping - Escopo Dinâmico

Os forms let e let* provêem escop léxico, que é o que você está acostumado quando programa em "C" ou PASCAL.

Escopo dinâmico é o que você tem em BASIC: se você atribui um valor a uma variável escopada dinamicamente, TODA menção desta variável vai retornar aquele valor até que você atribua outro valor à mesma variável.

Em LISP variáveis dinamicamente escopadas são variáveis especiais. Você pode criar uma variável especial através do form defvar.

Abaixo seguem alguns exemplos de variáveis escopadas lexicamente e dinamicamente.

  1. Neste exemplo a função check-regular referencia uma variável regular (ie, lexicamente escopada).
    Como check-regular está lexicamente fora do let que ata regular, check-regular retorna o valor global da variável:
> (setq regular 5)
5 

> (defun check-regular () regular)
CHECK-REGULAR 
> (check-regular)
5 

> (let ((regular 6)) (check-regular))
5 
  1. Neste exemplo, a função check-special referencia uma variável especial (ie, escopada dinamicamente).
    Uma vez que a chamada a check-special é temporariamente dentro do let que amarra special, check-special retorna o valor local da variável:

> (defvar *special* 5) *SPECIAL* > (defun check-special () *special*) CHECK-SPECIAL > (check-special) 5 > (let ((*special* 6)) (check-special)) 6 Por uma questão de convenção, o nome de uma variável especial começa e termina com *.

Variáveis especial são principalmente usadas como variáveis globais.

3.2.12. Arrays

A função make-array faz um array. A função aref acessa seus elementos. Todos os elementos de um array são inicialmente setados para nil:

> (make-array '(3 3))
#2a((NIL NIL NIL) (NIL NIL NIL) (NIL NIL NIL))

> (aref * 1 1)
NIL
> (make-array 4) ;1D arrays don't need the extra parens
#(NIL NIL NIL NIL)

Índices de um array sempre começam em 0

Veja abaixo como setar os elementos de um array.

3.2.13. Strings

Um string é uma seqüência de caracteres entre aspas duplas. LISP representa um string como um array de tamanho variável de caracteres.

Você pode escrever um string que contém a aspa dupla, precedendo a de um backslash \ . Um backslash duplo \\ está para um \ :

        "abcd" has 4 characters
        "\"" has 1 character
        "\\" has 1 character

Algumas funções para manipulação de strings:

> (concatenate 'string "abcd" "efg")
"abcdefg"
> (char "abc" 1)
#\b             ;LISP writes characters preceded by #\
> (aref "abc" 1)
#\b             ;remember, strings are really arrays

A função concatenate pode trabalhar sobre qquer tipo de seqüência:

> (concatenate 'string '(#\a #\b) '(#\c))
"abc"
> (concatenate 'list "abc" "de")
(#\a #\b #\c #\d #\e)
> (concatenate 'vector '#(3 3 3) '#(3 3 3))
#(3 3 3 3 3 3)

3.2.14. Estruturas

Estruturas LISP são análogas a structs em "C" ou records em PASCAL:

> (defstruct foo
    bar
    baaz
    quux
  )
FOO

Este exemplo define um tipo de dado chamado FOO contendo 3 campos.

Define também 4 funções que operam neste tipo de dado:

make-foo, foo-bar, foo-baaz, and foo-quux. 

A primeira cria um novo objeto do tipo FOO.

As outras acessam os campos de um objeto do tipo FOO.

> (make-foo)
#s(FOO :BAR NIL :BAAZ NIL :QUUX NIL) 
> (make-foo :baaz 3)
#s(FOO :BAR NIL :BAAZ 3 :QUUX NIL) 
> (foo-bar *)
NIL
> (foo-baaz **)
3

A função make-foo pode tomar um argumento para cada um dos campos da estrutura do tipo. As funções de acesso a campo tomam cada uma um argumento.

Veja abaixo como setar os campos de uma estrutura.



3.2.15. Setf

Alguns forms em LISP naturalmente definem uma locação na memória.

Por exemplo, se x é uma estrutura do tipo FOO, então (foo-bar x) define o campo BAR do valor de x.

Ou, se o valor de y é um array unidimensional, então (aref y 2) define o terceiro elemento de y.

O form especial setf usa seu primeiro argumento para definir um lugar na memória, avalia o seu segundo argumento e armazena o valor resultante na locação de memória resultante:

> (setq a (make-array 3))
#(NIL NIL NIL)
> (aref a 1)
NIL
> (setf (aref a 1) 3)
3
> a
#(NIL 3 NIL)
> (aref a 1)
3
> (defstruct foo bar)
FOO
> (setq a (make-foo))
#s(FOO :BAR NIL)
> (foo-bar a)
NIL
> (setf (foo-bar a) 3)
3
> a
#s(FOO :BAR 3)
> (foo-bar a)
3

Setf é a única maneira de se setar os valores de um array ou os campos de uma estrutura.

Alguns exemplos de setf e funções relacionadas:

> (setf a (make-array 1))       ;setf on a variable is equivalent to setq
#(NIL)
> (push 5 (aref a 1))           ;push can act like setf
(5)
> (pop (aref a 1))              ;so can pop
5
> (setf (aref a 1) 5)
5
> (incf (aref a 1))             ;incf reads from a place, increments,
6                               ;and writes back
> (aref a 1)
6

3.2.16. Booleanos e Condicionais



LISP usa o símbolo auto-avaliante NIL para significar FALSO. Qualquer outra coisa significa VERDADEIRO.

Nós usualmente utilizaremos o símbolo auto-avaliante t para significar TRUE.

LISP provê uma série de funções booleanas-padrão, como and, or e not. Os conetivos and e or são curto-circuitantes. AND não vai avaliar quaisquer argumentos à direita daquele que faz a função avaliar para NIL, enquanto OR não avalia nenhum à direita do primeiro verdadeiro.

LISP também provê uma série de forms para execução condicional. O mais simples é o IF, onde o primeiro argumento determina se o segundo ou o terceiro será avaliado.

Exemplos:

> (if t 5 6)
5
> (if nil 5 6)
6
> (if 4 5 6)
5

Se você necessita colocar mais de um comando em uma das cláusulas, então use o form progn. Progn executa cada comando em seu corpo e retorna o valor do último.

> (setq a 7)
7
> (setq b 0)
0
> (setq c 5)
5
> (if (> a 5)
    (progn
      (setq a (+ b 7))
      (setq b (+ c 8)))
    (setq b 4)
  )
13

Um if statement que não possui uma clausula then ou uma cláusula else pode ser escrito utilizando-se when ou unless:

> (when t 3)
3
> (when nil 3)
NIL
> (unless t 3)
NIL
> (unless nil 3)
3

When e unless, ao contrário de if, aceitam qquer número de comandos em seus corpos:

(when x a b c) é equivalente a(if x (progn a b c)).

> (when t
    (setq a 5)
    (+ a 6)
  )
11

Condicionais mais complexos podem ser construídos através do form cond, que é equivalente a if ... else if ... fi.

Um cond consiste de símbolo con seguido por um número de cláusulas-cond, cada qual é uma lista. O primeiro elemento de uma cláusula-cond é a condição, os lementos restantes são a ação.

O cond encontra a primeira cláusula que avalia para true, executando a ação respectiva e retornando o valor resultante. Nenhuma das restantes é avaliada.

> (setq a 3)
3
> (cond
   ((evenp a) a)                 ;if a is even return a
   ((> a 7) (/ a 2));else if a is bigger than 7 return a/2
   ((< a 5) (- a 1));else if a is smaller than 5 return a-1
   (t 17)                ;else return 17
  )
2
Se não há nenhuma ação na cláusula cond selecionada, cond retorna o valor verdadeiro:

> (cond ((+ 3 4)))
7

O comando LISP case é semelhante a um "C" switch statement:

> (setq x 'b)
B
> (case x
   (a 5)
   ((d e) 7)
   ((b f) 3)
   (otherwise 9)
  )
3

A cláusula otherwise significa que se x não for nem a, b, d, e, ou f, o case statement vai retornar 9.



3.2.17. Além de progn...

...existem MACROS em LISP para definir blocos. Uma muito usada é prog. Prog permite, entre outras coisas, a DECLARAÇ[Atilde]O explícita de variáveis locais, além de retorno explícito:

(defun F3.2.17a nil
        (prog (i j)             ;define i e j como variáveis
                        ;locais inicializadas com nil
                (setq i (read))
                (setq j (read)
                (return (print (- i j)))
        )
)

3.2.18. Lista de Exercícios Nº 2:

3.2.18.1. Escreva uma declaração em LISP para executar cada uma das operações abaixo:

3.2.18.2. Escreva uma função que: