Можно ли сделать clojure полностью динамичным?

В clojure 1.1 все вызовы были динамическими, что означает, что вы можете переопределить функцию в REPL, и она будет автоматически включена в запущенную программу. Это также было приятно для таких вещей, как dotrace.

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

Хуже того, я не могу предсказать, где мне это нужно.

Можно ли вернуться к старому стандарту динамической компоновки? Возможно, если вам нужна дополнительная скорость, вы можете переключить ее обратно на производственное приложение, но для разработки я предпочитаю поведение 1.1.

Я надеюсь на какой-то вариант компилятора, например * warn-on-reflection *.

Edit:

Я запутался в том, что происходит. В частности, здесь представлены две функции. Я предпочитаю поведение второго. Как я могу заставить первого вести себя как второй, как я полагаю, он раньше делал в 1.1?

user> (clojure-version)
"1.2.0"

user> (defn factorial[n] (if (< n 2) n (* n (factorial (dec n)))))
#'user/factorial

user> (require 'clojure.contrib.trace)
user> (clojure.contrib.trace/dotrace (factorial) (factorial 10))
TRACE t1670: (factorial 10)
TRACE t1670: => 3628800

user> (defn factorial[n] (if (< n 2) n (* n (#'factorial (dec n)))))
#'user/factorial
user> (clojure.contrib.trace/dotrace (factorial) (factorial 10))
TRACE t1681: (factorial 10)
TRACE t1682: |    (factorial 9)
TRACE t1683: |    |    (factorial 8)
TRACE t1684: |    |    |    (factorial 7)
TRACE t1685: |    |    |    |    (factorial 6)
TRACE t1686: |    |    |    |    |    (factorial 5)
TRACE t1687: |    |    |    |    |    |    (factorial 4)
TRACE t1688: |    |    |    |    |    |    |    (factorial 3)
TRACE t1689: |    |    |    |    |    |    |    |    (factorial 2)
TRACE t1690: |    |    |    |    |    |    |    |    |    (factorial 1)
TRACE t1690: |    |    |    |    |    |    |    |    |    => 1
TRACE t1689: |    |    |    |    |    |    |    |    => 2
TRACE t1688: |    |    |    |    |    |    |    => 6
TRACE t1687: |    |    |    |    |    |    => 24
TRACE t1686: |    |    |    |    |    => 120
TRACE t1685: |    |    |    |    => 720
TRACE t1684: |    |    |    => 5040
TRACE t1683: |    |    => 40320
TRACE t1682: |    => 362880
TRACE t1681: => 3628800
3628800

Изменить (на весь вопрос и сменить заголовок):

Joost указывает ниже, что на самом деле происходит то, что сам вызов факториала оптимизируется. Я не понимаю, почему это было бы сделано, так как вы не можете делать так много рекурсивных "я" без выдувания стека, но это объясняет наблюдаемое поведение. Возможно, это как-то связано с анонимными самообслуживаниями.

Первоначальная причина моего вопроса заключалась в том, что я пытался написать http://www.learningclojure.com/2011/03/hello-web-dynamic-compojure-web.html, и меня раздражало количество мест, которые у меня были чтобы ввести # ', чтобы получить поведение, которое я ожидал. Это и дотчасть заставляли меня думать, что общее динамическое поведение ушло, и что "на лету" переопределение, которое работает в некоторых местах, должно быть сделано с помощью какого-то умного взлома.

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

Ответ 1

Я думаю, вы ошибаетесь. В clojure 1.2 вы, безусловно, можете переопределить функции, а вызывающий код вызовет новые определения. В 1.3 похоже, что это может несколько измениться, но 1.3 еще не исправлено.

Ответ 2

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

В первом примере:

(defn factorial [n] (if (< n 2) n (* n (factorial (dec n)))))

Символ factorial разрешен Var #'user/factorial, который затем вычисляется компилятором для получения его текущего значения, скомпилированной функции. Эта оценка выполняется только один раз, когда функция скомпилирована. factorial в этом первом примере - это значение Var #'user/factorial в момент определения функции.

Во втором примере:

(defn factorial [n] (if (< n 2) n (* n (#'factorial (dec n)))))

Вы явно попросили Var #'user/factorial. Вызов Var имеет такой же эффект, как разыменование Var и вызов его (функции) значения. Этот пример можно было бы написать более явно:

(defn factorial [n] (if (< n 2) n (* n ((deref (var factorial)) (dec n)))))

Макрос clojure.contrib.trace/dotrace (который я написал, давным-давно) использует binding для временной перестройки Var для другого значения. Это не изменяет определения каких-либо функций. Вместо этого он создает новую функцию, которая вызывает исходную функцию и печатает линии трассировки, затем привязывает эту функцию к Var.

В первом примере, поскольку исходная функция была скомпилирована со значением функции factorial, dotrace не влияет. Во втором примере каждый вызов функции factorial просматривает текущее значение #'user/factorial Var, поэтому каждый вызов видит альтернативное связывание, созданное dotrace.

Ответ 3

Чтобы люди не путались по поводу проблем, я объясняю "проблему", связанную с веб-разработкой.

Это ограничение Ring not Clojure (и действительно это ограничение библиотеки Java Jetty). Вы всегда можете переопределять функции в обычном режиме. Однако обработчик, заданный серверному процессу Jetty, не может быть переопределен. Ваши функции обновляются, но сервер Jetty не может видеть эти обновления. Предоставление var в качестве обработчика - это работа в этом случае.

Но обратите внимание, что var не является реальным обработчиком. AbstractHandler должен быть предоставлен серверу Jetty, поэтому Ring использует прокси, чтобы создать тот, который закрывается над вашим обработчиком. Вот почему для того, чтобы обработчик обновлялся динамически, он должен быть var, а не fn.

Ответ 4

В Clojure -1.3 вы также сможете переопределить функции во время выполнения (таким образом изменив привязку корня), это будет работать так же, как 1.2 и 1.1. вам, однако, нужно будет отметить переменные, которые будут динамически отскакивать с помощью binding как динамического. Это нарушение изменяет

  • значительные улучшения скорости.
  • позволяет привязкам работать через pmap
  • полностью стоит того, потому что 99% варов никогда не восстанавливаются.