Clojure alter update-in возвращает nil, а dosync не разрешает повторение

#clojure #hashmap #apply #stm #update-in

#clojure #hashmap #применить #stm #обновление

Вопрос:

Редактировать:

dosync создает саму функцию, поэтому вызовы recur интерпретируются как вызовы, выполненные для dosync сгенерированной функции.

Это след функции, которую я фактически создал. Я думаю, что это было как можно проще.

 (defn change-to-ref [ref data]
  (dosync
    (if-let [[new-ref new-data] (some-test @ref data)]
      (recur new-ref new-data)
      (alter ref f data))))
  

Исключение:

 CompilerException java.lang.IllegalArgumentException:
Mismatched argument count to recur, expected: 0 args, got: 2
  

ОРИГИНАЛ:

Я пытался обновить hashmap в ссылке следующим образом

 (def example-ref (ref {:some {:nested {:structure}}}))
(defn f [this] [this])  ;; just an example function

(sync (alter example-ref update-in [:some] f)

user=> nil
  

Что было довольно удивительно, поскольку оно должно было вернуться

 user=> {:some [{:nested {:structure}}]}
  

Итак, чем я пытался:

 (update-in @example-ref [:some] f)

user=> {:some [{:nested {:structure}}]}
  

Но чем я читал, что alter это вызывает apply so:

 (apply update-in @example-ref '([:some] f))

user=> {:some nil}
  

Хорошо, итак, давайте сделаем это правильно:

 (apply update-in @example-ref (list [:some] f))

user=> {:some [{:nested {:structure}}]}
  

Ну, это здорово, что я понял это,
но это не объясняет, почему это идет не так, alter
и я все равно не могу это изменить…

 (apply (fn [a b] (update-in a b f)) @example-ref '([:something]))

user=> {:some [{:nested {:structure}}]}
  

Это выглядит ужасно, но, по крайней мере, это работает, и я могу смоделировать это для alter : D

 (sync (alter example-ref (fn [a b] (update-in a b f)) [:some])

user=> nil
  

Хорошо, вы выиграли.

Я взглянул на: clojure.lang.Ref.alter source, но не стал мудрее. (кроме того, что, насколько я понимаю, alter на самом деле не вызывает apply )

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

Комментарии:

1. Что вы имеете в виду при использовании recur внутри dosync ? Вы действительно хотите, чтобы там были вложенные транзакции?

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

3. @OlegTheCat кстати, я согласен с вашим вопросом, если бы я запустил транзакцию, а затем повторил, это было бы не очень хорошей идеей, поскольку транзакция всегда является единственной (в хвостовой позиции) Я думаю, что это нормально, может быть, было бы лучше, если бы я создал отдельную чистую функцию для поиска в пути

Ответ №1:

Проблема в том, что подпись sync выглядит следующим образом:

(флаги синхронизации -игнорируются-пока и тело)

Первый аргумент этого макроса игнорируется. Также документация рекомендует пройти nil для этого:

transaction-flags => TBD, пока передайте nil

Итак, правильный способ использования sync будет:

 > (sync nil (alter example-ref update-in [:some] (partial conj [])))
{:some [{:nested {:structure :foo}}]}
  

Я бы также рекомендовал использовать dosync вместо sync (просто чтобы не связываться с первым параметром; по сути, эти функции одинаковы):

 > (dosync (alter example-ref update-in [:some] (partial conj [])))
{:some [{:nested {:structure :foo}}]}
  

Комментарии:

1. На самом деле я использовал dosync раньше, но это выдавало ошибку о повторном вызове с неправильным количеством аргументов, прочитал, что dosync создает анонимную функцию, вы знаете, как это обойти?

2. Можете ли вы отредактировать свой вопрос и указать MCVE, который вызывает проблему dosync ?