19.7 Структуры с явно заданным типом представления

Иногда необходимо явно контролировать представление структуры. Параметр :type позволяет выбрать представление между списком или некоторым видом вектора и указать соотвествие для размещения слотов в выбранном представлении. Структура также может быть «безымянной» или «именованной». Это означает может ли имя структуры быть сохранено в ней самой (и соответственно, прочитано из неё).

19.7.1 Безымянные структуры

Иногда конкретное представление данных навязывается внешними требованиями и, кроме того, формат данных прекрасно ложится в структурный стиль хранения. Например, рассмотрим выражение созданное из чисел, символов и таких операций как + и *. Операция может быть представлена, как в Lisp’е, списком из оператора и двух операндов. Этот факт может быть выражен кратко в терминах defstruct:

(defstruct (binop (:type list))
  (operator ’? :type symbol)
  operand-1
  operand-2)

Результатом выполнения make-binop является 3-ёх элементный список:

(make-binop :operator ’+ :operand-1 ’x :operand-2 5)
    (+ x 5)

(make-binop :operand-2 4 :operator ’*)
    (* nil 4)

Выглядит как функция list за исключением того, что принимает именованные параметры и выполняет инициализацию слотов соответствующую концептуальному типу данных binop. Таким же образом, селекторы binop-operator, binop-operand-1 и binop-operand-2 эквивалентны соответственно car, cadr и caddr. (Они, конечно, не полностью эквивалентны, так как реализация может осуществлять проверки типов элементов, длины массивов при использовании селекторов слотов структур.)

Мы говорим о binop как о «концептуальном» типе данных, потому что binop не принадлежит Common Lisp’овой системе типов. Предикат typep не может использовать binop как спецификатор типа, и type-of будет возвращать list для заданной binop структуры. Несомненно, различий между структурой данных, созданной с помощью make-binop, и простым списком нет.

Невозможно даже получить имя структуры для объекта, созданного с помощью make-binop. Однако имя может быть сохранено и получено, если структура содержит «имя».

19.7.2 Именованные структуры

Структура с «именем» имеет свойство, которое заключается в том, что для любого экземпляра структуры можно получить имя этой структуры. Для структур определённых без указания параметра :type, имя структуры фактически становится частью Common Lisp’овой системы типов. Функция type-of при применении к экземпляру такой структуры будет возвращать имя структуры. Предикат typep будет рассматривать имя структуры, как корректный спецификатор типа.

Для структур определённых с параметром :type, type-of будет возвращать спецификатор типа один из list или (vector t), в зависимости от указанного аргумента параметра :type. Имя структуры не становится корректным спецификатором типа. Однако если также указан параметр :named, тогда первый компонент структуры всегда содержит её имя. Это позволяет получить это имя, имея только экземпляр структуры. Это также позволяет автоматически определить предикат для концептуального типа. Предикат name-p для структуры принимает в качестве первого аргумента объект и истинен, если объект является экземпляром структуры, иначе ложен.

Рассмотрим вышеупомянутый пример binop и модифицируем его, добавив параметр :named:

(defstruct (binop (:type list) :named)
  (operator ’? :type symbol)
  operand-1
  operand-2)

Как и раньше, конструкция определить функцию-конструктор make-binop и три функции-селектора binop-operator, binop-operand-1 и binop-operand-2. Она также определит предикат binop-p.

Результатом make-binop теперь является список с 4-мя элементами:

(make-binop :operator ’+ :operand-1 ’x :operand-2 5)
    (binop + x 5)

(make-binop :operand-2 4 :operator ’*)
    (binop * nil 4)

Структура имеет такую же разметку как и раньше за исключением имени структуры в первом элементе. Функции-селекторы binop-operator, binop-operand-1и binop-operand-2 эквивалентны соответственно cadr, caddr и cadddr. Предикат binop-p примерно соответствует следующему определению.

(defun binop-p (x)
  (and (consp x) (eq (car x) ’binop)))

Имя binop не является корректным спецификатором типа, и не может использоваться в typep. Но с помощью предиката структура теперь отличима от других структур.

19.7.3 Другие аспекты явно определённых типов для представления структур

Параметр :initial-offset позволяет указывать начало размещения слотов в представлении структуры. Например, форма

(defstruct (binop (:type list) (:initial-offset 2))
  (operator ’? :type symbol)
  operand-1
  operand-2)

создаст конструктор make-binop со следующим поведением:

(make-binop :operator ’+ :operand-1 ’x :operand-2 5)
    (nil nil + x 5)

(make-binop :operand-2 4 :operator ’*)
    (nil nil * nil 4)

Селекторы binop-operator, binop-operand-1 и binop-operand-2 будут эквивалентны соответственно caddr, cadddr, и car от cddddr. Таким же образом, форма

(defstruct (binop (:type list) :named (:initial-offset 2))
  (operator ’? :type symbol)
  operand-1
  operand-2)

создаст конструктор make-binop со следующим поведением:

(make-binop :operator ’+ :operand-1 ’x :operand-2 5)
    (nil nil binop + x 5)

(make-binop :operand-2 4 :operator ’*)
    (nil nil binop * nil 4)

Если вместе с параметром :type используется :include, тогда в представлении выделяется столько места, сколько необходимо для родительской структуры, затем пропускается столько места, сколько указано в параметре :initial-offset, и затем начинается расположение элементов определяемой структуры. Например:

(defstruct (binop (:type list) :named (:initial-offset 2))
  (operator ’? :type symbol)
  operand-1
  operand-2)

(defstruct (annotated-binop (:type list)
                            (:initial-offset 3)
                            (:include binop))
  commutative associative identity)

(make-annotated-binop :operator ’*
                      :operand-1 ’x
                      :operand-2 5
                      :commutative t
                      :associative t
                      :identity 1)
    (nil nil binop * x 5 nil nil nil t t 1)

Первые два nil элемента пропущены по причине параметра :initial-offset со значением 2 в определении binop. Следующие четыре элемента содержат имя и три слота структуры binop. Следующие три nil элементы пропущено по причине параметра :initial-offset за значением 3 в определении структуры annotated-binop. Последние три элемента содержат три слота, определённых в структуре annotated-binop.