какова практическая цель динамических переменных и привязки clojure?

Я просмотрел ссылки: http://clojure.org/vars#Vars%20and%20the%20Global%20Environment, http://clojuredocs.org/clojure_core/clojure.core/binding

а также clojure и ^: dynamic и Динамическое связывание Clojure

Я до сих пор не понимаю, зачем вообще нужны binding, поскольку каждая программа, которую я написал, была без них, и я могу найти способы написать примеры обычным способом, который я считаю более понятным. Есть ли примеры проектов / парадигм программирования, в которых это используется?

например ... в примере речи животного вы можете получить аналогичный эффект с помощью:

(def dog {:name "Dog" :sound "Woof"})
(def cat {:name "Cat" :sound "Meow"})

(defn speak [animal]
   (str (:name animal) " says " (:sound animal))

(println (speak dog))
(println (speak cat))

без макросов, без динамической привязки ... все еще очень чисто.


person zcaudate    schedule 17.10.2012    source источник
comment
Стюарт Сьерра рассказывает о последствиях динамической области видимости в Clojure в этом сообщении блога: stuartsierra.com/2013/03/29/perils-of-dynamic-scope   -  person Alexandr Kurilin    schedule 22.06.2014


Ответы (2)


В них нет строго необходимости: как вы правильно заметили, вы можете делать все, что захотите, без binding, и действительно, если binding не существует, вы можете относительно легко повторно реализовать его, используя макросы и Java-файлы ThreadLocals.

Однако привязка полезна как способ передачи динамического контекста функции без необходимости явно передавать параметр.

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

Чтобы опираться на ваш пример:

(def ^:dynamic *loud-noises* false)

(defn speak [animal]
     (str (:name animal) " says " 
          (let [sound (:sound animal)]
            (if *loud-noises* (.toUpperCase sound) sound))))

(speak dog)
=> "Dog says Woof"

(binding [*loud-noises* true]
  (speak dog))
=> "Dog says WOOF"

Обратите внимание: мне не нужно было добавлять дополнительный параметр к функции speak, чтобы получить другое поведение. Добавление дополнительного параметра в этом случае было бы тривиальным, но представьте, если бы функция speak была похоронена глубоко внутри сложной функции более высокого порядка ...

Тем не менее, я думаю, что лучший совет в целом - избегать динамического связывания, если оно вам действительно не нужно. Обычно лучше по возможности добавлять явные параметры: прямые параметры упрощают тестирование и рассуждения о функциях.

person mikera    schedule 17.10.2012
comment
Почему вы предпочитаете привязку к добавлению дополнительных параметров к каждой функции в глубоко вложенной функции? Если моя функция вызывает (speak dog) и (close-door), как мне узнать, что *loud-noises* соответствует speak, а не close-door? - person ToBeReplaced; 17.10.2012
comment
@ToBeReplaced Я думаю, вы подчеркиваете опасность динамической привязки и почему дизайн, в котором она используется, следует тщательно продумывать. Динамическое связывание позволяет передавать контекст между слоями, где вы не контролируете середину. Например, функции печати clojure позволяют повторно привязать выходной поток даже при использовании внутри сторонней функции (которая не имеет параметра выходного потока). Это также позволяет настраивать поведение, специфичное для потока, но я подозреваю, что в большинстве API динамическое связывание гораздо чаще приносит вред, чем пользу. - person Alex Stoddard; 17.10.2012

Просто продолжу приведенный выше пример Микеры ... Я понимаю, почему вы должны сделать это на менее выразительном языке, но поскольку clojure настолько выразителен, я бы предпочел его переписать ... Функцию loud-noise можно немного изменить, снова для достижения того же эффект, добавив дополнительные параметры, чтобы говорить ...

(defn speak [animal & opts]
  (let [sound (:sound animal)
        sound (if (some #(= % :louder) opts)
                 (.toUpperCase sound) sound)]
    (str (:name animal) " says " sound)))


> (speak dog)
;;=> "Dog says Woof"
> (speak dog :louder)
;;=> "Dog says WOOF"

Связывание - это просто способ взломать быстрое и грязное решение, если вы не можете изменить исходный код?

person zcaudate    schedule 18.10.2012
comment
у вас не всегда есть возможность добавлять дополнительные параметры - что, если вы, например, передаете функции в код библиотеки? или если функции являются HOF общего назначения, которые вы не хотите загрязнять параметрами, которые используются только в одном частном случае? - person mikera; 18.10.2012