Динамическое связывание функций во время выполнения с Clojure - PullRequest
4 голосов
/ 01 декабря 2011

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

(defn ^:dynamic state [x]
   (odd x))

(defn even [x]
  (if (= x 0)
    (println "even")
    (binding [state odd] (parity x))))

(defn odd [x]
  (if (= x 0)
    (println "odd")
    (binding [state even](parity x))))

(defn parity [x]
    (state (dec x)))

Это работает нормально, но, поскольку я совершенно новичок в Clojure, я не знаю,
а) чистый функциональный код (поскольку нечетные и даже, кажется, побочные эффекты?)
б) способ изменения функций во время выполнения должен быть сделан

Буду признателен за любые советы по этому поводу! :) -Zakum

Ответы [ 2 ]

4 голосов
/ 01 декабря 2011

Использование динамических привязок - это в основном вопрос вкуса, но есть несколько соображений:

Динамические привязки - это скорее ярлык для явной передачи значений в стек вызовов. Есть только несколько ситуаций, когда это абсолютно очевидная победа; в основном такие вещи, как передача «глобальных» параметров конфигурации / аргументов «через» API, которые их не поддерживают.

API, который опирается на динамические привязки, трудно обернуть во что-то более явное, в то время как наоборот гораздо проще (и обычно это можно сделать полуавтоматически).

Динамические привязки плохо воспроизводятся с ленивыми последовательностями или чем-то еще, что оценивается вне текущего стека вызовов (как и другие потоки).

В целом, я думаю, что "более чистое" функциональное решение состояло бы в том, чтобы передать state в качестве аргумента parity, но аргументы могут быть приведены в любом случае.

2 голосов
/ 01 декабря 2011

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

Подумайте об этом так: ваш код создает символ и две функции, и вы динамически связываете символ с другой функцией:

                                   +---> func1
                                  /
symbol ---- [dynamic binding] ---<
                                  \
                                   +---> func2

Эффект динамического связывания ограничен областью применения binding.

Чего мы хотим добиться, так это, учитывая символ и функцию, обеспечить новую реализацию функции, чтобы весь код, ссылающийся на нее, имел доступ к новой реализации:

(defn func1 [...])

(var func1) ; ---> func1

(defn func1 [...])

(var func1) ; ---> func1*

и такое изменение постоянно влияет на весь код, который использует func1. Это нормальная задача, когда вы разрабатываете часть замыкания: вы, скорее всего, откроете REPL в работающем приложении, и вы будете def и defn несколько раз повторять одни и те же символы снова и снова, переопределяя все движущиеся части вашего приложения на лету.

Если вы используете Emacs и SLIME / Swank, каждый раз, когда вы нажимаете C-c C-k в измененном исходном файле Clojure, вы потенциально можете переопределить все функции в пространстве имен без необходимости перезапуска приложения.

...