Можно ли сделать clojure полностью динамичным? - PullRequest
10 голосов
/ 23 марта 2011

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

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

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

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

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

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

Я не понимаю, что происходит.Более конкретно, здесь есть две функции.Я предпочитаю поведение второго.Как я могу заставить первый вести себя как второй, как я полагаю, что это было в 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

Правка (ко всему вопросу и смена заголовка):

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

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

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

Ответы [ 4 ]

11 голосов
/ 24 марта 2011

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

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

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

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

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

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

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

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

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

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

9 голосов
/ 24 марта 2011

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

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

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

8 голосов
/ 23 марта 2011

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...