Разрешение циклических зависимостей Clojure - PullRequest
15 голосов
/ 21 июня 2010

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

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

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

Ответы [ 5 ]

24 голосов
/ 21 июня 2010

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

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

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

13 голосов
/ 21 июня 2010

У меня была похожая проблема с некоторым графическим кодом, который я в итоге сделал:

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

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

9 голосов
/ 10 апреля 2014

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

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

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

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

1 голос
/ 08 декабря 2011

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

0 голосов
/ 11 сентября 2017

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

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

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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))

Я научился этому трюку из кода Дана Холмсанда в Reagent .

...