UP | HOME

Elementos avançados

Os elementos a seguir descrevem funcionalidades avançadas de Majestic Lisp. Algumas delas continuam sendo comuns a outros dialetos de Lisp, com suas próprias características de acordo com o design da linguagem.

Estes elementos podem requerer maior aprofundamento, por isso, não serão exaustivamente explorados.

1. Macros

Macros são elementos muito importantes em dialetos de Lisp, pois conferem flexibilidade sintática extra quando programas tornam-se muito repetitivos. Diferente de outras linguagens (como C, por exemplo), o uso de macros em Lisp é muito mais fácil, e é uma das marcas registradas da linguagem, desde que não abusemos dos mesmos.

Um macro de Majestic Lisp funciona como uma função de usuário qualquer. Todavia, os dados passados como parâmetro não são interpretados; antes disso, o macro reescreve a expressão passada a ele, e então interpreta-a no escopo onde foi invocado.

O exemplo a seguir mostra a definição do macro when. Este macro é uma especialização da forma especial if, quando o consequente possui mais de uma expressão, e a alternativa pode ser substituída pelo valor de retorno nil.

(defmac when (pred . body)
  `(if ,pred (do ,@body) nil))
when
(when (zerop 5)
  'zero)
nil
(when (zerop 0)
  'zero)
zero

2. Aplicação parcial

Majestic também implementa algumas ideias que não estão presentes na maioria dos Lisps, sendo uma dessas a aplicação parcial de funções.

Dada uma função qualquer, é possível repassar menos argumentos do que o esperado para a mesma. Nesse caso, a expressão retornará uma nova função que espera, como parâmetros, os argumentos que não foram passados.

A função primitiva eq compara dois símbolos. Por isso, eq espera por exatamente dois argumentos:

(eq 'a 'b)
nil

Poderíamos criar uma função de usuário que compara se um certo símbolo é eq ao símbolo a. Isso pode ser definido explicitamente:

(defn is-a (sym)
  (eq 'a sym))
is-a
(is-a 'b)
nil
(is-a 'a)
t

Usando aplicação parcial, podemos fazer isso de outra forma. Note que, como eq espera por dois argumentos, se passarmos apenas um argumento, será retornada uma função que espera por apenas um argumento1:

(eq 'a)
#<function (fn (:G191)) {0x562697ac4ab0}>

Se dermos um nome à função retornada, note que teremos nada mais, nada menos que a exata mesma definição de is-a, como anteriormente feita:

(def is-a (eq 'a))
is-a
(is-a 'b)
nil
(is-a 'a)
t

3. Desestruturação de argumentos

Outro recurso de Majestic Lisp é a desestruturação de argumentos de funções. Ao declararmos uma função, podemos tomar um argumento que espera-se que seja uma lista e o desestruturarmos de acordo com os elementos esperados, economizando espaço na digitação do programa e desmembramento da mesma.

Considere a função map, que percorre uma lista de elementos e aplica uma função a cada um deles. O retorno de cada aplicação de função é, finalmente, acumulado em uma nova lista e retornado.

(defn map (f l)
  (unless (nilp l)
    (cons (f (car l))
          (map f (cdr l)))))
map
(map (fn (x) (* x x))
     '(1 2 3 4 5))
(1 4 9 16 25)

Como o uso de car e cdr não é muito didático, um programador poderia considerar desmembrar a lista l usando let, antes da aplicação do corpo da função:

(defn map (f l)
  (let ((x  (car l))
        (xs (cdr l)))
    (unless (nilp x)
      (cons (f x)
            (map f xs)))))
map

Mesmo que essa definição esclareça o corpo do let, ela acaba não sendo muito compacta.

Como a lista l só possui o intuito de ser desmembrada, podemos declarar diretamente nos argumentos da função como ela deverá ser desmembrada. Em especial, queremos que o primeiro elemento se chame x, e que a lista restante, independente de ser ou não vazia, chame-se xs:

(defn map (f (x . xs))
  (unless (nilp x)
    (cons (f x)
          (map f xs))))

Dessa forma, l passa a não estar declarado no escopo de map, dando lugar apenas a sua desestruturação em x e xs.

Desestruturação ad-hoc.

A definição do macro let, em Majestic Lisp, faz uso da aplicação instantânea de funções anônimas. Isso significa que todo let é, na verdade, uma aplicação de função. Isso é algo pertinente porque dá margem para um recurso incidental: é possível realizar desestruturação de variáveis localmente, através do uso de let. Isso significa que é possível declarar duas ou mais variáveis a partir da desestruturação de uma lista.

O exemplo a seguir mostra a desestruturação de uma lista em três variáveis conhecidas (a, b e c), e os demais elementos da lista são colocados em uma quarta variável, xs.

(let (( (a b c . xs) '(1 2 3 4 5) ))
  (print "a  = {}\nb  = {}\nc  = {}\nxs = {}"
         a b c xs))
a  = 1
b  = 2
c  = 3
xs = (4 5)
nil

Footnotes:

1

Como eq é uma função primitiva, o argumento da função retornada é um símbolo gerado aleatoriamente pelo sistema.

Author: Lucas S. Vieira

Created: 2022-10-24 seg 00:27

Validate