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

data BinTree a b = EmptyBinTree | BinTree a (Maybe b) (BinTree a) (BinTree a)

В Common Lisp я мог бы представить пустое дерево как специальный символ самооценки, например :empty-bin-tree, или как универсальный символ, например nil. Я бы представил общий случай двоичного дерева как

(defun make-bin-tree
    (key-value
     &optional (left-child :empty-bin-tree)
     &optional (right-child :empty-bin-tree))
  "(list key value?) left-child? right-child? -> bin-tree"
  (list key-value left-child right-child))

Без ничего явного, соответствующего конструктору BinTree в коде Haskell.

Есть ли идиоматический или традиционный способ представления эквивалента конструктора нулевых данных в качестве самооценочного символа в текущем пакете вместо повторного использования ключевого слова?

1
Gregory Nisbet 26 Дек 2015 в 23:13

2 ответа

Лучший ответ

Вы можете свернуть свой собственный, как описано в другом ответе. Похоже, вы хотите использовать алгебраические типы данных в стиле ML в Common Lisp. Оказывается, вы можете: эта удивительная библиотека tarballs_are_good: https://bitbucket.org/ tarballs_are_good / cl-algebraic-data-type реализует их.

К сожалению, эта библиотека не поддерживает параметрические рекурсивные типы, потому что их сложно перенастроить на динамический язык только с помощью макрологии. Если эта абстракция не подлежит обсуждению, вы можете посмотреть на Лисп, например Shen, который ее поддерживает. с нуля.

Изменить: мелочи теперь де-факто является стандартом для библиотек сопоставления с образцом. Он совместим с optima, но, как мне кажется, находится в стадии более активной разработки.

Если вам очень нравится сопоставление с образцом, вы можете взглянуть на optima, оптимизатор сопоставления с образцом для общих Лисп. cl-algebraic-data-typse имеет макрос сопоставления с образцом, но в настоящее время сопоставление вложенного образца не реализовано. Я написал плагин для optima, чтобы он мог соответствовать cl-algebraic-data-types, который вы можете найти здесь: https://bitbucket.org/blevyq/adt-optima-bridge. К сожалению, в настоящее время он не проверяет полноту.

8
Riley 13 Окт 2020 в 03:33

Сделать самооценку символа достаточно просто. Вы можете просто использовать defconstant. Затем вы можете создать функцию дерева, как будто вы предлагаете, где левое и правое поддеревья необязательны и по умолчанию используется пустое дерево:

(defconstant empty-tree 'empty-tree)

(defun tree (element &optional
                       (left empty-tree)
                       (right empty-tree))
  (list element left right))
CL-USER> (tree 3)
(3 EMPTY-TREE EMPTY-TREE)

CL-USER> (tree 3 (tree 4))
(3 (4 EMPTY-TREE EMPTY-TREE) EMPTY-TREE)

CL-USER> (tree 3 (tree 4) (tree 5))
(3 (4 EMPTY-TREE EMPTY-TREE) (5 EMPTY-TREE EMPTY-TREE))

Тем не менее, я не знаю, будет ли это сочтено особенно идиоматическим. Теперь у вас есть способ проверить наличие пустых деревьев, но нет определенного способа проверить наличие непустых деревьев. То есть как узнать, дерево это или просто какой-то список?

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

Бинарное дерево - это либо:

  • список формы (элемент левое поддерево правое поддерево)
  • пустое дерево, ноль.

Это достаточно просто реализовать:

(defun tree-element (tree)
  (first tree))

(defun tree-left (tree)
  (second tree))

(defun tree-right (tree)
  (third tree))

(defun treep (object)
  (and (listp object)
       (or (= 3 (length object))  ; doesn't check subtrees, though
           (null object))))

Обратите внимание, что предикат treep не проверяет поддеревья. Это похоже на listp , который проверяет, является ли что-то нулевым или минусом , но не идет дальше вниз. Также вопрос вкуса, действительно ли длина дерева должна быть три или нет. В конце концов, вы все равно можете сделать, например, (tree-right '(1 (2))) и получить обратно nil .

Вы также можете сделать что-то более явное, с информацией о типе, фактически встроенной в объекты, чтобы вы могли проверять аргументы и т. Д. Я думаю, что это менее распространенный подход, но вы можете получить типизированные объекты, используя CLOS или структуры. Структуры по умолчанию также предоставляют вам множество таких же аксессуаров. Подход со структурами может выглядеть так (обязательно прочтите комментарии в коде):

(defstruct (binary-tree
             ;; By default, DEFSTRUCT would define a BINARY-TREE-P
             ;; predicate that would be true only of the structure
             ;; objects.  The structure objects are just the internal
             ;; nodes; NIL is the leaf, so instead we define
             ;; INTERNAL-BINARY-TREE-P, and define BINARY-TREE-P
             ;; later.
             (:predicate internal-binary-tree-p))
  element   ; defines binary-tree-element
  left      ;    ''   binary-tree-left
  right)    ;    ''   binary-tree-right

(defun binary-tree-p (object)
  (or (null object)
      (internal-binary-tree-p object)))

Или вы также можете создать иерархию типов со структурами. Проблема с иерархиями типов, конечно, в том, что у вас нет возможности заблокировать их. Например, вы можете определить класс двоичного дерева без слотов, а затем создать подклассы пустое двоичное дерево и внутреннее двоичное дерево, но если кто-то приходит и определяет другой подкласс, это тоже двоичное дерево, даже если вы хотел алгебраическое поведение.

Common Lisp - достаточно гибкий язык, на котором вы можете разрабатывать алгебраические типы данных, если хотите, и достаточно прощающий, чтобы вы могли применять столько, сколько захотите. Я думаю, что наиболее распространенный подход и, безусловно, самый простой подход - это просто обрабатывать ваши данные таким образом. Компилятор не будет требовать, чтобы вы не передавали не-дерево функции ожидания дерева, но вы все равно можете получить большую часть преимуществ.

6
Joshua Taylor 27 Дек 2015 в 16:14