Подтвердить что ты не робот

Каково обоснование закрытых записей в Clojure?

У меня есть возможность непосредственного внедрения протокола в тело defrecord вместо использования ext-protocol/extend-type

(defprotocol Fly
  (fly [this]))

(defrecord Bird [name]
  Fly
  (fly [this] (format "%s flies" name)))

=>(fly (Bird. "crow"))
"crow flies"

Если я сейчас попытаюсь переопределить протокол Fly, я получаю сообщение об ошибке

(extend-type Bird
  Fly
  (fly [this] (format "%s flies away (:name this)))

class user.Bird already directly implements interface user.Fly for protocol:#'user/Fly

С другой стороны, если вместо этого я использую начальный тип

(defrecord Dragon [color])

(extend-type Dragon
  Fly
  (fly [this] (format "%s dragon flies" (:color this))))

=>(fly (Dragon. "Red"))
"Red dragon flies"

Затем я могу "переопределить" функцию fly

(extend-type Dragon
  Fly
  (fly [this] (format "%s dragon flies away" (:color this))))

=>(fly (Dragon. "Blue"))
"Blue dragon flies away"

Мой вопрос: почему бы не разрешить расширение в обоих случаях? Является ли это ограничением JVM из-за отношения Record ↔ Class или существует прецедент для неперехвачиваемого протокола?

4b9b3361

Ответ 1

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

На другом уровне, как правило, очень плохой идеей для реализации протокола для типов, предоставляемых кодом, который не владеет ни типом, ни рассматриваемым протоколом. См. Например этот поток в группе Clojure Google для соответствующего обсуждения (в том числе выражение Рика Хики); также имеется соответствующая запись в Clojure Стандарты кодирования библиотек:

Протоколы:

  • Следует распространять протокол только на тип, если он владеет либо тип или протокол.
  • Если кто-то нарушает предыдущее правило, он должен быть готов уйти, если разработчик предоставить определение
  • Если протокол поставляется с Clojure, не допускайте его распространения на типы вы не владеете, особенно, например, java.lang.String и другие ядра Java интерфейсы. Будьте уверены, если протокол должен распространяться на него, он будет, иначе лобби.
    • Мотив, как заявил Рич Хикки, [чтобы предотвратить] "людей расширять протоколы к типам, для которых они не имеют смысла, например. для которого авторы протокола считали, но отклонил реализацию из-за семантическое несоответствие.". будут там (по дизайну), а люди без достаточного понимание/навыки могут заполнить пустота со сломанными идеями ".

Это также много обсуждалось в сообществе Haskell в связи с типами классов (google "orphan instance", есть также хорошие сообщения по теме здесь и на SO).

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

  • проверять вещи на REPL и применять быстрые настройки;

  • модификация реализации метода протокола в рабочем изображении Clojure.

(1) может быть менее проблематичным, если во время разработки использовать extend и Co. и только переключиться на встроенные реализации на некоторой поздней стадии настройки производительности; (2) - это то, что может принести жертву, если требуется максимальная скорость.