Разрешение круговых зависимостей Clojure

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

  • Основная проблема заключается в том, что я получаю ошибку "Нет такого var: namespace/functionname" в одном из файлов
  • Я попытался "объявить" эту функцию, но потом она жалуется: "Нельзя ссылаться на квалифицированный var, который не существует"
  • Я мог бы, конечно, реорганизовать всю кодовую базу, но это кажется непрактичным делать каждый раз, когда у вас есть зависимость для решения..... и может быть очень уродливым для определенных сетей круговых зависимостей.
  • Я мог бы выделить кучу интерфейсов/протоколов/деклараций в отдельный файл и все это ссылается на это.... но похоже, что это закончит тем, что станет беспорядочным и испортит текущую хорошую модульную структуру, которая у меня есть связанные функциональные возможности, сгруппированные вместе.

Любые мысли? Каков наилучший способ обработки такого рода циклической зависимости в Clojure?

Ответы

Ответ 1

Я помню ряд обсуждений по пространствам имен в Clojure - в списке рассылки и в другом месте - и я должен сказать вам, что консенсус (и, AFAICT, текущая ориентация дизайна Clojure) заключается в том, что круговые зависимости - это проектный крик для рефакторинга. Иногда обходные пути могут быть возможны, но уродливые, что может быть проблематично для производительности (если вы делаете вещи бесполезно "динамическими" ), не гарантируется работа навсегда и т.д.

Теперь вы говорите, что круговая структура проекта приятная и модульная. Но почему бы вам назвать это, если все зависит от всего...? Кроме того, "каждый раз, когда у вас есть зависимость для решения", не следует очень часто, если вы планируете древовидную структуру зависимостей раньше времени. И чтобы ответить на ваши идеи по размещению некоторых базовых протоколов и тому подобное в их собственном пространстве имен, я должен сказать, что много раз я хотел, чтобы проекты сделали именно это. Я нахожу это чрезвычайно полезным для моей способности просканировать кодовую базу и получить представление о том, какие абстракции она работает быстро.

Подводя итоги, мой голос переходит к рефакторингу.

Ответ 2

У меня была аналогичная проблема с некоторым gui-кодом, что я закончил делать,

(defn- frame [args]
  ((resolve 'project.gui/frame) args))

Это позволило мне разрешить вызов во время выполнения, это вызвано из элемента меню в фрейме, поэтому я был уверен в 100% фрейме, потому что он вызывался из самого фрейма, помните, что решение может вернуть нуль.

Ответ 3

У меня такая же проблема постоянно. Насколько многие разработчики не хотят этого признавать, это серьезный недостаток дизайна на этом языке. Круговые зависимости - это нормальное состояние реальных объектов. Тело не может выжить без сердца, и сердце не может выжить без тела.

Разрешение во время вызова может быть возможным, но оно не будет оптимальным. Возьмите случай, когда у вас есть API, поскольку часть api - это методы создания отчетов об ошибках, но api создает объект, который имеет свои собственные методы, для этих объектов потребуется отчет об ошибках, и у вас есть круговая зависимость. Функции проверки ошибок и отчетности будут вызываться часто, поэтому разрешение в момент их вызова не является вариантом.

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

Если Aa() зависит от Ba() и Bb() полагается на Ab(), единственным решением является перемещение Ba() в Ca() и/или Ab() в Cb(), хотя C технически не " существуют в реальном мире.

Ответ 4

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

Ответ 5

Хорошо подумать о дизайне. Круговые зависимости могут свидетельствовать нам о том, что мы смущены чем-то важным.

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

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; example/a.cljc

(ns example.a
  (:require [example.b :as b]))

(defn foo []
  (println "foo"))

#?(

   :clj
   (alter-var-root #'b/foo (constantly foo))                ; <- in clojure do this

   :cljs
   (set! b/foo foo)                                         ; <- in clojurescript do this

   )

(defn barfoo []
  (b/bar)
  (foo))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; example/b.cljc

(ns example.b)

;; Avoid circular dependency.  This gets set by example.a
(defonce foo nil)

(defn bar []
  (println "bar"))

(defn foobar []
  (foo)
  (bar))

Я узнал этот трюк от код Dan Holmsand в реагенте.