Seguinte: Índice remissivo Acima: Macros Anterior: Iteradores
Índice remissivo

Fichas

Como vimos, uma ficha (no sentido da linguagem Pascal) não é 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 utilização de fichas é de tal modo simples que todas as linguagens de programação possuem capacidades próprias para lidar com elas. Um dado tipo de ficha não necessita mais do que um construtor para os elementos desse tipo, um selector 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 objectos, a implementação de fichas é uma tarefa de tal modo simples que é bastante fácil automatizá-la.

Vimos na descrição do tipo aglomerado automovel que ele era definido por um constructor:

(defun novo-automovel (&key marca modelo portas) ...)

e pelos selectores:

(defun automovel-marca (automovel) ...)

(defun automovel-modelo (automovel) ...)

(defun automovel-portas (automovel) ...)

e ainda pelos modificadores:

(defun muda-automovel-marca! (automovel nova-marca) ...)

(defun muda-automovel-modelo! (automovel novo-modelo) ...)

(defun muda-automovel-portas! (automovel novo-portas) ...)

Vamos também incluir um reconhecedor de automóveis muito útil para distinguirmos os diversos tipos de fichas:

(defun automovel? (obj) ...)

Repare-se que o conjunto de funções que apresentámos constituem um modelo para a definição de fichas. A ficha automovel possui um construtor criado através da concatenação da palavra ``novo'' ao nome da ficha. Cada selector é dado pela concatenação do nome da ficha ao nome do campo a que ele diz respeito. Cada modificador é dado pela concatenação da palavra ``muda'' ao nome do selector correspondente e terminado com a letra ``!'. O reconhecedor é dado pelo nome da ficha terminado com a letra ``?'. Qualquer outra ficha seria definida de forma idêntica, pelo que podemos fazer uma abstracção da definição de fichas, criando uma macro que encapsule a definição de todas aquelas funções.

Um dos problemas que temos de resolver é a implementação das fichas. Vimos no exemplo do automóvel que o podiamos implementar como uma lista com os valores dos vários campos. Infelizmente, aquela implementação não nos permite distinguir o tipo de registo automóvel de outros tipos de registo, pelo que temos de modificar a implementação de modo a isso ser possível. Para minimizar as alterações, podemos considerar que cada registo é implementado por uma lista cujo primeiro elemento é o nome da ficha e cujos restantes elementos são os valores dos campos da ficha. Isto permite manter a mesma lógica de acesso e modificação dos campos desde que os índices desses campos na lista sejam incrementados de uma unidade.

Assim sendo, o construtor ficará qualquer coisa do género:

(defun novo-automovel (&key marca modelo portas)
  (list 'automovel marca modelo portas))

Um dado selector será :

(defun automovel-marca (automovel)
  (nth 1 automovel))

Um modificador será:

(defun muda-automovel-marca! (automovel nova-marca)
  (muda-n-esimo! 1 automovel nova-marca))

O reconhecedor de automóveis ficará:

(defun automovel? (obj)
  (and (listp obj) (eql (first obj) 'automovel)))

Podemos agora definir uma macro designada ficha, cuja utilização será da seguinte forma:

(ficha automovel
  marca modelo portas)

A expansão da macro deverá ser qualquer coisa da forma:

(progn
 (defun novo-automovel (&key marca modelo portas) ...)
 (defun automovel-marca (automovel) ...)
 (defun automovel-modelo (automovel) ...)
 (defun automovel-portas (automovel) ...)
 (defun muda-automovel-marca! (automovel nova-marca) ...)
 (defun muda-automovel-modelo! (automovel novo-modelo) ...)
 (defun muda-automovel-portas! (automovel novo-portas) ...)
 (defun automovel? (obj) ...)
 'carro)

A definição da macro é, assim, relativamente simples:

(defmacro ficha (nome &rest campos)
  `(progn
    ,(construtor-ficha nome campos)
    ,@(selectores-ficha nome campos)
    ,@(modificadores-ficha nome campos)
    ,(reconhecedor-ficha nome)
    ',nome))

Para definirmos as várias operações vamos necessitar de concatenar símbolos. Para isso, é necessário criar um símbolo, através da função intern, cujo nome seja a concatenação, através da função concatenate, dos nomes dos símbolos, obtidos mapeando a função string nesses símbolos, i.e.:

(defun junta-nomes (&rest nomes)
  (intern (apply #'concatenate 
                 'string 
                 (mapcar #'string nomes))))

Com a ajuda desta função, já podemos definir as restantes funções da macro.

(defun construtor-ficha (nome campos)
  `(defun ,(junta-nomes 'novo- nome) (&key ,@campos)
     (list ',nome ,@campos)))

(defun selectores-ficha (nome campos) (mapcar #'(lambda (campo) `(defun ,(junta-nomes nome '- campo) (,nome) (nth ,(1+ (position campo campos)) ,nome))) campos))

(defun modificadores-ficha (nome campos) (mapcar #'(lambda (campo) `(defun ,(junta-nomes 'muda- nome '- campo '!) (,nome novo) (muda-n-esimo! ,(1+ (position campo campos)) ,nome novo))) campos))

(defun reconhecedor-ficha (nome) `(defun ,(junta-nomes nome '?) (obj) (and (listp obj) (eql (first obj) ',nome))))

Podemos agora experimentar a macro e visualizar os resultados:

> (macroexpand-1 '(ficha automovel marca modelo portas)))
(PROGN
  (DEFUN NOVO-AUTOMOVEL (&KEY MARCA MODELO PORTAS)
    (LIST 'AUTOMOVEL MARCA MODELO PORTAS))
  (DEFUN AUTOMOVEL-MARCA (AUTOMOVEL)
    (NTH 1 AUTOMOVEL))
  (DEFUN AUTOMOVEL-MODELO (AUTOMOVEL)
    (NTH 2 AUTOMOVEL))
  (DEFUN AUTOMOVEL-PORTAS (AUTOMOVEL)
    (NTH 3 AUTOMOVEL))
  (DEFUN MUDA-AUTOMOVEL-MARCA! (AUTOMOVEL NOVO)
    (MUDA-N-ESIMO! 1 AUTOMOVEL NOVO))
  (DEFUN MUDA-AUTOMOVEL-MODELO! (AUTOMOVEL NOVO)
    (MUDA-N-ESIMO! 2 AUTOMOVEL NOVO))
  (DEFUN MUDA-AUTOMOVEL-PORTAS! (AUTOMOVEL NOVO)
    (MUDA-N-ESIMO! 3 AUTOMOVEL NOVO))
  (DEFUN AUTOMOVEL? (OBJ)
    (AND (LISTP OBJ) (EQL (first OBJ) 'AUTOMOVEL)))
  'AUTOMOVEL) 

Exercício 79

Um dos melhoramentos que seria interessante incluir na definição de fichas seria a inclusão de valores por omissão para qualquer campo. Poderiamos indicar que um automóvel possui geralmente quatro portas da seguinte forma:

(ficha automovel
  marca
  modelo
  (portas 4))

Assim, se se criasse um automóvel sem especificar o número de portas, ele seria de 4. Implemente essa alteração.

Resposta


Seguinte: Índice remissivo Acima: Macros Anterior: Iteradores
Índice remissivo

Copyright António Leitão, 1995