Преобразование строки в функцию, которая не находится в пространстве имен в clojure - PullRequest
3 голосов
/ 21 сентября 2011

Вот пример кода, который я хочу получить на работе:

(letfn [(CONC [f] f)
        (CONT [f] (str "\newline" f))]
((voodoo "CONC") "hamster"))

Есть ли какое-то вудо, которое заставит его вызывать функцию CONC с хомяком в качестве параметра?То есть, есть ли какой-нибудь способ преобразовать строку «CONC» в функцию, которая не связана с пространством имен, а скорее с локальной привязкой?

РЕДАКТИРОВАТЬ:

Чтобы быть более понятным, как это будет называться:

(map #((voodoo (:tag %)) (:value %)) 
    [ 
        {:tag "CONC" :value "hamster"} 
        {:tag "CONT" :value "gerbil"}
    ]
)

Ответы [ 4 ]

6 голосов
/ 22 сентября 2011

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

(def voodoo 
  {"CONC" (fn [f] f)
   "CONT" (fn [f] (str "\newline" f))})

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

(map #((voodoo (:tag %)) (:value %)) 
    [ 
        {:tag "CONC" :value "hamster"} 
        {:tag "CONT" :value "gerbil"}
    ]
)

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

4 голосов
/ 22 сентября 2011

Ну, это как бы возможно:

(defmacro voodoo [s]
  (let [env (zipmap (map (partial list 'quote) (keys &env))
                    (keys &env))]
    `(if-let [v# (~env (symbol ~s))]
       v#
       (throw (RuntimeException. "no such local")))))

... и теперь мы можем делать странные вещи вроде этого:

user> (defn example [s]
        (letfn [(foo [x] {:foo x})
                (bar [x] {:bar x})]
          ((voodoo s) :quux)))
#'user/example
user> (example "foo")
{:foo :quux}
user> (example "bar")
{:bar :quux}
user> (example "quux")
; Evaluation aborted.
user> *e
#<RuntimeException java.lang.RuntimeException: no such local>

То, что "Evaluation aborted" означает, что исключение былоБрошенный.

Вы также можете заменить throw ветвь if в voodoo на (resolve (symbol ~s)) для переноса на глобальные переменные, если не найдено локальное:

(defmacro voodoo [s]
  (let [env (zipmap (map (partial list 'quote) (keys &env))
                    (keys &env))]
    `(if-let [v# (~env (symbol ~s))]
       v#
       (resolve (symbol ~s)))))

... и теперь это работает с определением example, как указано выше (хотя учтите, что если вы экспериментируете с REPL, вам потребуется перекомпилировать example после переопределения voodoo):

user> (defn quux [x] {:quux x})
#'user/quux
user> (example "quux")
{:quux :quux}

Так вот, это злоупотребление возможностями Clojure, без которых было бы неплохо попытаться обойтись.Если вы не можете, то, вероятно, следует обратиться к evalive от Michael Fogus;это библиотека, которая предоставляет средство eval-with-localals в виде функции evil и нескольких утилит.Кажется, что функциональность тоже хорошо продумана, например, что-то вроде ~(zipmap ...) выше инкапсулировано как макрос, и evil кажется почти заменой eval (добавьте параметр env, и выхорошо пойти).Я не прочитал источник должным образом, но я, вероятно, сейчас, похоже, весело.: -)

4 голосов
/ 21 сентября 2011

Нет.Eval никогда не имеет доступа к локальной / лексической среде.

Редактировать: Это не очень хороший ответ и не очень точный.Вы можете написать voodoo в виде макроса, и тогда он не требует доступа во время выполнения к лексической среде, только во время компиляции.Однако это означает, что это будет работать, только если во время компиляции вы знаете, что функция, которую вы хотите вызвать, является x, и поэтому она не будет очень полезной - почему бы просто не набрать x вместо (voodoo "x")?

(defmacro voodoo [fname]
  (symbol fname))

(letfn [(x [y] (inc y))]
  ((voodoo "x") 2))
;; 3

(letfn [(x [y] (inc y))]
  (let [f "x"]
    ((voodoo f) 2)))
;; error
2 голосов
/ 21 сентября 2011

Я не совсем понимаю, о чем вы спрашиваете, поэтому я попробую пару ответов:

если у вас есть строка, которая является именем функции, которую вы хотите вызвать:

 (def name "+")
 ((find-var (symbol (str *ns* "/" name))) 1 2 3)

это даст вуду такое определение:

(defn voodoo [name args] (apply (find-var (symbol (str *ns* "/" name))) args))
#'clojure.core/voodoo
clojure.core=> (voodoo "+" [1 2 3])
6

clojure.core => это предполагает, что ваша функция находится в текущем пространстве имен <em>ns</em>.

если вы хотите превратить строку в функцию, вы можете использовать этот шаблон

(let [f (eval (read-string "(fn [] 4)"))] (f))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...