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:
Como eq
é uma função primitiva, o argumento da função
retornada é um símbolo gerado aleatoriamente pelo sistema.