Реализация модели данных для предотвращения распространенных ошибок - PullRequest
7 голосов
/ 26 октября 2011

Существует несколько способов реализации моделей данных в Clojure:

  • обычные встроенные типы данных (карты / списки / наборы / векторы)
  • встроенные типы данных + метаданные - например: (type ^{:type ::mytype} {:fieldname 1})
  • встроенные типы данных + специальные функции доступа (например, get при отбрасывании несуществующего ключа из карты выдается исключение, вместо тихого возврата nil)
  • deftype
  • defstruct
  • defrecord
  • defprotocol

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

У нас есть три основные цели:

  • написать идиоматический код Clojure
  • избегайте тратить много времени на поиск глупых ошибок типа
  • уверены, что мы можем изменить / реорганизовать код, не прерывая ничего

Как мы можем использовать силу Clojure, чтобы помочь нам?

Ответы [ 2 ]

4 голосов
/ 27 октября 2011

Clojure культура настоятельно поддерживает необработанные типы данных.Оправданно так.Но явные типы могут быть полезны.Когда ваши простые типы данных становятся достаточно сложными и конкретными, у вас, по сути, есть неявный тип данных без спецификации.

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

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

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

user> (= (with-meta [1 2 3] {:type :A})  (with-meta [1 2 3] {:type :B}))
true

Вопрос о том, приемлемо это или нет, зависит от вас, но я бы побеспокоился об этом, представив новые тонкие ошибки.


Другие параметры типа данных:

  • deftype предназначен только для низкоуровневой работы по созданию новых структур данных базового или специального назначения.В отличие от defrecord, он не несет в себе все хорошее качество clojure.Для большинства работ это не нужно или не рекомендуется.
  • defstruct не рекомендуется.Когда Рич Хики представил типы и протоколы, он, по сути, сказал, что defrecord следует отдавать предпочтение во все времена.

Протоколы очень полезны, даже если они кажутся чем-то вроде отступления от (functions +данные) парадигма.Если вы обнаружите, что создаете записи, вам следует подумать и об определении протоколов.

РЕДАКТИРОВАТЬ : я обнаружил еще одно преимущество для простых типов данных, которое ранее не было мне очевидно: если вы занимаетесь веб-программированием, простые типы данных можно эффективно преобразовывать в и из JSONи легко.(Библиотеки для этого включают clojure.data.json, clj-json и мой любимый, cheshire).С записями и типами данных задача значительно раздражает.

1 голос
/ 26 октября 2011

Действительно удобно иметь возможность создавать функции, которые работают с картами и списками, и было бы стыдно потерять это, переключившись на классы и протоколы. ведь лучше иметь сто функций на один тип. Переход на протоколы или записи будет немного тяжелым. Это предотвратит вас от (debug (map :rows (get-state)) при отладке, например.

метаданные - это отличный способ добавить «достаточно типа», чтобы сделать ваши данные более безопасными в тех местах, где это необходимо, без потери преимуществ в остальной части вашего кода. Я бы рекомендовал идти с вариантом 2

  • 'встроенные типы данных + метаданные ((type ^ {: type :: mytype} {: fieldname 1}))'
...