15.4.2 Составные типы

Когда ни один из простых типов не подходит, вы можете использовать составные типы, которые строят новые типы из других типов или из заданных данных. Указанные типы или данные называются аргументами составного типа. Составной тип обычно выглядит так:

(конструктор аргументы…)

но вы также можете добавлять пары ключ-значение перед аргументами, например:

(конструктор {ключ значение}… аргументы…)

Вот таблица конструкторов и способов их использования для записи составных типов:

(cons тип-car тип-cdr)

Значение должно быть точечной парой, её CAR должен соответствовать тип-car, а её CDR должен соответствовать тип-cdr. Например, (cons string symbol) — это тип настройки, который соответствует значениям вида ("foo" . foo).

В буфере настройки CAR и CDR отображаются и редактируются отдельно, каждый в соответствии со своим указанным типом.

(list типы-элементов…)

Значение должно быть списком с ровно таким количеством элементов, сколько задано типов-элементов; и каждый элемент должен соответствовать соответствующему типу-элемента.

Например, (list integer string function) описывает список из трёх элементов; первый элемент должен быть целым числом, второй — строкой, а третий — функцией.

В буфере настройки каждый элемент отображается и редактируется отдельно в соответствии с указанным для него типом.

(group типы-элементов…)

Работает как list, за исключением форматирования текста в буфере Custom. list помечает каждый элемент его тегом; group этого не делает.

(vector типы-элементов…)

Как list, за исключением того, что значением должен быть вектор, а не список. Элементы работают так же, как в list.

(alist :key-type тип-ключа :value-type тип-значения)

Значение должно быть списком точечных пар, CAR каждой ячейки представляет ключ типа настройки тип-ключа, а CDR той же ячейки представляет значение типа настройки тип-значения. Пользователь может добавлять и удалять пары ключ/значение, а также редактировать как ключ, так и значение каждой пары.

Если опущены, тип-ключа и тип-значения по умолчанию равны sexp.

Пользователь может добавить любой ключ, соответствующий указанному типу ключа, но вы можете дать некоторым ключам особый статус, указав их с помощью :options (см. Определение переменных настройки). Указанные ключи всегда будут показываться в буфере настройки (вместе с подходящим значением) с флажком для включения, исключения или отключения пары ключ/значение из alist. Пользователь не сможет редактировать ключи, указанные с помощью аргумента ключевого слова :options.

Аргумент ключевого слова :options должен быть списком спецификаций для допустимых ключей в alist. Обычно это просто атомы, которые обозначают сами себя. Например:

:options '("foo" "bar" "baz")

указывает, что есть три известных ключа, а именно "foo", "bar" и "baz", которые всегда будут показываться первыми.

Возможно, вы захотите ограничить тип значения для конкретных ключей, например, значение, связанное с ключом "bar", может быть только целым числом. Вы можете указать это, используя список вместо атома в списке. Первый элемент будет задавать ключ, как и раньше, а второй элемент — тип значения. Например:

:options '("foo" ("bar" integer) "baz")

Наконец, вы можете изменить способ отображения ключа. По умолчанию ключ просто показывается как const, поскольку пользователь не может изменить специальные ключи, указанные с помощью ключевого слова :options. Однако вы можете использовать более специализированный тип для отображения ключа, например function-item, если вы знаете, что это символ с привязкой к функции. Это делается с помощью спецификации типа настройки вместо символа для ключа.

:options '("foo"
           ((function-item some-function) integer)
           "baz")

Многие alist используют списки из двух элементов вместо точечных пар. Например,

(defcustom list-alist
  '(("foo" 1) ("bar" 2) ("baz" 3))
  "Каждый элемент является списком вида (KEY VALUE).")

вместо

(defcustom cons-alist
  '(("foo" . 1) ("bar" . 2) ("baz" . 3))
  "Каждый элемент является точечной парой (KEY . VALUE).")

Из-за того, как списки реализованы поверх точечных пар, вы можете рассматривать list-alist в примере выше как alist из точечных пар, где типом значения является список с одним элементом, содержащим реальное значение.

(defcustom list-alist '(("foo" 1) ("bar" 2) ("baz" 3))
  "Каждый элемент является списком вида (KEY VALUE)."
  :type '(alist :value-type (group integer)))

Виджет group используется здесь вместо list только потому, что форматирование лучше подходит для этой цели.

Аналогично вы можете иметь alist с несколькими значениями, связанными с каждым ключом, используя вариации этого приёма:

(defcustom person-data '(("brian"  50 t)
                         ("dorith" 55 nil)
                         ("ken"    52 t))
  "Alist с основной информацией о людях.
Каждый элемент имеет вид (NAME AGE MALE-FLAG)."
  :type '(alist :value-type (group integer boolean)))
(plist :key-type тип-ключа :value-type тип-значения)

Этот тип настройки похож на alist (см. выше), за исключением того, что (i) информация хранится в виде property-списка (см. Property-списки) и (ii) тип-ключа, если опущен, по умолчанию равен symbol, а не sexp.

(choice альтернативные-типы…)

Значение должно соответствовать одному из альтернативных-типов. Например, (choice integer string) допускает либо целое число, либо строку.

В буфере настройки пользователь выбирает альтернативу с помощью меню, а затем может редактировать значение обычным способом для этой альтернативы.

Обычно строки в этом меню определяются автоматически на основе выбора; однако вы можете задать другие строки для меню, включив ключевое слово :tag в альтернативы. Например, если целое число означает количество пробелов, а строка — текст для дословного использования, вы можете записать тип настройки следующим образом:

(choice (integer :tag "Количество пробелов")
        (string :tag "Буквальный текст"))

чтобы в меню предлагались ‘Количество пробелов’ и ‘Буквальный текст’.

В любой альтернативе, для которой nil не является допустимым значением, кроме const, вы должны указать допустимое значение по умолчанию для этой альтернативы с помощью ключевого слова :value. См. Ключевые слова типов.

Если некоторые значения охватываются более чем одной альтернативой, customize выберет первую альтернативу, которой соответствует значение. Это означает, что вы всегда должны перечислять наиболее конкретные типы первыми, а наиболее общие — последними. Вот пример правильного использования:

(choice (const :tag "Выкл" nil)
        symbol (sexp :tag "Другое"))

Таким образом, специальное значение nil не обрабатывается как другие символы, а символы не обрабатываются как другие Lisp-выражения.

(radio типы-элементов…)

Похоже на choice, за исключением того, что варианты отображаются с помощью радиокнопок, а не меню. Это имеет преимущество отображения документации для вариантов, когда это применимо, и поэтому часто является хорошим выбором для выбора между константными функциями (function-item типы настройки).

(const значение)

Значение должно быть значение — ничто другое не допускается.

Основное использование const — внутри choice. Например, (choice integer (const nil)) допускает либо целое число, либо nil.

:tag часто используется с const внутри choice. Например,

(choice (const :tag "Да" t)
        (const :tag "Нет" nil)
        (const :tag "Спросить" foo))

описывает переменную, для которой t означает «да», nil — «нет», а foo — «спросить».

(other значение)

Эта альтернатива может соответствовать любому Lisp-значению, но если пользователь выбирает эту альтернативу, то выбирается значение значение.

Основное использование other — как последний элемент choice. Например,

(choice (const :tag "Да" t)
        (const :tag "Нет" nil)
        (other :tag "Спросить" foo))

описывает переменную, для которой t означает «да», nil — «нет», а всё остальное — «спросить». Если пользователь выбирает ‘Спросить’ из меню альтернатив, это задаёт значение foo; но любое другое значение (не t, nil или foo) отображается как ‘Спросить’, так же как и foo.

(function-item функция)

Как const, но используется для значений, которые являются функциями. Отображает строку документации, а также имя функции. Строка документации — либо та, которую вы указали с помощью :doc, либо собственная строка документации функции.

(variable-item переменная)

Как const, но используется для значений, которые являются именами переменных. Отображает строку документации, а также имя переменной. Строка документации — либо та, которую вы указали с помощью :doc, либо собственная строка документации переменной.

(set типы…)

Значение должно быть списком, и каждый элемент списка должен соответствовать одному из указанных типов.

В буфере настройки это отображается как список флажков, так что каждый из типов может иметь либо один соответствующий элемент, либо ни одного. Невозможно указать два разных элемента, соответствующих одному и тому же из типов. Например, (set integer symbol) допускает одно целое число и/или один символ в списке; он не допускает несколько целых чисел или несколько символов. В результате редко используются неспецифические типы, такие как integer, в set.

Чаще всего типы в set являются типами const, как показано здесь:

(set (const :bold) (const :italic))

Иногда они описывают возможные элементы в alist:

(set (cons :tag "Высота" (const height) integer)
     (cons :tag "Ширина" (const width) integer))

Это позволяет пользователю указать значение высоты по желанию и значение ширины по желанию.

(repeat тип-элемента)

Значение должно быть списком, и каждый элемент списка должен соответствовать типу тип-элемента. В буфере настройки это отображается как список элементов с кнопками ‘[INS]’ и ‘[DEL]’ для добавления новых элементов или удаления элементов.

(restricted-sexp :match-alternatives критерии)

Это самый общий конструктор составных типов. Значением может быть любой Lisp-объект, удовлетворяющий одному из критериев. критерии должны быть списком, и каждый элемент должен быть одной из следующих возможностей:

  • Предикат — то есть функция одного аргумента, возвращающая либо nil, либо не-nil в зависимости от аргумента. Использование предиката в списке означает, что объекты, для которых предикат возвращает не-nil, допустимы.
  • Цитированная константа — то есть 'объект. Такой элемент в списке означает, что сам объект является допустимым значением.

Например,

(restricted-sexp :match-alternatives
                 (integerp 't 'nil))

допускает целые числа, t и nil в качестве допустимых значений.

В буфере настройки все допустимые значения отображаются в их синтаксисе чтения, и пользователь редактирует их текстуально.

Вот таблица ключевых слов, которые вы можете использовать в парах ключ-значение в составном типе:

:tag тег

Используйте тег как имя этой альтернативы для целей взаимодействия с пользователем. Это полезно для типа, который находится внутри choice.

:match-alternatives критерии

Используйте критерии для сопоставления возможных значений. Используется только в restricted-sexp.

:args список-аргументов

Используйте элементы списка-аргументов как аргументы конструктора типа. Например, (const :args (foo)) эквивалентно (const foo). Вам редко нужно явно писать :args, поскольку обычно аргументы распознаются автоматически как всё, что следует за последней парой ключ-значение.