В чем причина закрытых записей в Clojure? - PullRequest
19 голосов
/ 28 августа 2010

У меня есть опция прямой реализации протокола в теле defrecord вместо использования extension-protocol / extension-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 или есть вариант использования для не переопределяемого протокола?

1 Ответ

26 голосов
/ 28 августа 2010

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

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

Протоколы:

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

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

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

  1. тестирование в REPL и применение быстрых настроек;

  2. изменение реализаций метода протокола в работающем образе Clojure.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...