Выполнение динамически связанной функции в Clojure
Я хотел бы предварительно сохранить кучу вызовов функций в структуре данных, а затем оценить/выполнить их из другой функции.
Это работает как запланировано для функций, определенных на уровне пространства имен с помощью defn
(хотя определение функции происходит после моего создания структуры данных), но не будет работать с функциями, определенными let [name (fn
или letfn
внутри функции.
Вот мой небольшой самостоятельный пример:
(def todoA '(funcA))
(def todoB '(funcB))
(def todoC '(funcC))
(def todoD '(funcD)) ; unused
(defn funcA [] (println "hello funcA!"))
(declare funcB funcC)
(defn runit []
(let [funcB (fn [] (println "hello funcB"))]
(letfn [(funcC [] (println "hello funcC!"))]
(funcA) ; OK
(eval todoA) ; OK
(funcB) ; OK
(eval todoB) ; "Unable to resolve symbol: funcB in this context" at line 2
(funcC) ; OK
(eval todoC) ; "Unable to resolve symbol: funcC in this context" at line 3
)))
Если вы задаетесь вопросом о моей тестовой настройке, чтобы увидеть результат этих 6 операторов, я комментирую/удаляю специфические строки OK/failing, а затем вызываю (runit)
из REPL.
Есть ли какое-то простое исправление, которое я мог бы предпринять для вызова eval
'd quote
d функций для работы для функций, определенных внутри другой функции?
Update:
Это (на основе предложения danlei) действительно работает. Посмотрим, смогу ли я заставить этот метод работать в "реальной жизни!"
(def todoB '(funcB))
(declare funcB)
(defn runit []
(binding [funcB (fn [] (println "hello funcB"))]
(funcB)
(eval todoB) ; "Unable to resolve symbol: funcB in this context" at line 1!
))
Update:
Этот код входит в мое решение для проблемы ограничения ограничений - я хочу узнать которому принадлежит зебра! Я новичок в Clojure и особенно функциональном программировании, и это сделало упражнение довольно сложным. Я попадаю во множество ям, но я в порядке с этим, поскольку это часть учебного опыта.
Я использовал ограничения как кучу простых векторов, например:
[:con-eq :spain :dog]
[:abs-pos :norway 1]
[:con-eq :kools :yellow]
[:next-to :chesterfields :fox]
где первый из каждого вектора будет указывать вид ограничения. Но это привело меня к неудобной реализации механизма отправки для этих правил, поэтому я решил вместо этого вместо них закодировать их как (цитируемые):
'(coloc :japan :parliament) ; 10
'(coloc :coffee :green) ; 12
'(next-to :chesterfield :fox) ; 5
поэтому я могу отправить правило ограничения простым eval
. Это кажется намного более элегантным и "lisp -y". Однако каждой из этих функций необходимо получить доступ к моим данным домена (с именем vars
), и эти данные продолжают меняться по мере запуска программы. Я не хотел испортить свои правила, введя дополнительный аргумент, поэтому я хотел, чтобы vars
был доступен для функций eval
'd через динамическое масштабирование.
Теперь я узнал, что динамическое масштабирование может быть выполнено с помощью binding
, но ему также нужен declare
.
Ответы
Ответ 1
Вы имеете в виду что-то вроде этого?
(def foo '(bar))
(declare bar)
(binding [bar (fn [] (println "hello bar"))]
(eval foo))
Если да, ваша проблема сводится к следующему:
(let [foo 1]
(eval 'foo))
Это не сработает, потому что eval не оценивает в лексической среде. Вы можете обойти это, используя vars:
(declare foo)
(binding [foo 1]
(eval 'foo))
Что касается этого, Clojure, похоже, имеет сходную семантику с CL, см. CLHS:
Вычисляет форму в текущей динамической среде и нулевой лексической среде.
Ответ 2
Я думаю, вы решаете неправильную проблему. В функциональных языках функции являются значениями и могут быть назначены на все, что может хранить любое другое значение, например. карта. Вы не должны пытаться манипулировать пространствами имен или ничего не делать - это не perl.
Попробуйте что-нибудь подобное и используйте ассоциацию для локального изменения карты:
user=> (def fnmap {:funcA (fn [x] (inc x)), :funcB (fn [x] (* x 2))})
#'user/fnmap
user=> ((:funcA fnmap) 10)
11
user=> ((:funcB fnmap) 10)
20