Две ссылки на карту в качестве элементов друг друга

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

Моя функция выглядит так:

(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

Другие вопросы по тегам