Две ссылки на карту в качестве элементов друг друга
У меня есть две карты в ссылках, и я хочу связать их друг с другом в одной транзакции.
Моя функция выглядит так:
(defn assoc-two
[one two]
(let [newone (assoc @one :two two)
newtwo (assoc @two :one one)]
(ref-set one newone)
(ref-set two newtwo)))
Теперь я звоню assoc-two
как это:
(dosync (assoc-two (ref {}) (ref {})))
Я получаю и StackruError в этой точке.
Я также попробовал это:
(defn alter-two
[one two]
(alter one assoc :two two)
(alter two assoc :one one))
Есть ли, чтобы сделать это таким образом, чтобы one
имеет ссылку на запись two
и наоборот и до сих пор в одной транзакции?
2 ответа
Переполнение стека не происходит, пока вы не попытаетесь распечатать одну из циклических ссылок, например, в REPL.
so.core => (def a (ref {})) # 'So.core/ а so.core=> (def b (ref {})) #'So.core/ б so.core=> (do (dosync (alter-two a b)): успех): успех so.core=> (= b (: два @a)) правда so.core=> (= a (:one @b)) правда
Очевидно, что печать объекта с круговой ссылкой будет проблематичной, но посмотрите на этот недавний вопрос и ответ об отключении печати по умолчанию содержимого ссылочных типов.
(remove-method print-method clojure.lang.IDeref)
(dosync (alter-two (ref {}) (ref {})))
;=> {:one #<Ref clojure.lang.Ref@7f1f91ac>}
; (prints the second ref without attempting to print its circular contents)
Ответ можно найти несколькими постами вниз, это просто REPL, пытающийся напечатать вашу рекурсивную структуру. Вы должны удалить метод печати:
(remove-method print-method clojure.lang.IDeref)
Или добавьте метод печати, который обрабатывает ваш конкретный случай, этот метод должен быть более конкретным, чем общий clojure.lang.IDeref