Clojure - (функция вызова строки чтения-строки

У меня есть следующее в файле clojure:

(ns helloworld
  (:gen-class
    :main -main))

(defn hello-world-fn []
  (println "Hello World"))

(defn -main [& args]
  (eval (read-string "(hello-world-fn)")))

и я запускаю его с

lein run helloworld

и я получаю следующую ошибку:

Exception in thread "main" java.lang.RuntimeException: Unable to resolve symbol:
 helloworld in this context, compiling:(helloworld.clj:12)

У меня есть ощущение, что мне нужно что-то сделать с ns-resolve или resolve, но я не добился никакого успеха. Я пробовал следующее в основной функции:

(let [call-string  (read-string "(hello-world-fn)")
      func (resolve  (symbol (first call-string)))
      args (rest call-string)]
   (apply func args))

Безуспешно.

Может ли кто-нибудь (а) указать мне правильное направление; и (b) точно объяснить, что происходит в программе чтения Clojure, когда это происходит?


person hawkeye    schedule 06.02.2012    source источник


Ответы (2)


Вы можете очень элегантно решить свою задачу, используя macros. Фактически, вы можете написать макрос, имитирующий eval.

(defmacro my-eval [s] `~(read-string s))
(my-eval "(hello-world-fn)")); "Hello World"

Он работает лучше, чем eval, потому что разрешение символа s происходит в контексте, вызывающем my-eval. Спасибо @Matthias Benkard за разъяснения.

Вы можете прочитать о макросах и их синтаксисе на странице http://clojure.org/reader.

person viebel    schedule 07.02.2012
comment
Почему это автоматически решает проблему с пространством имен, а ns-resolve — нет? - person hawkeye; 07.02.2012
comment
@hawkeye Потому что resolve работает во время выполнения и разрешает символы в текущем пространстве имен, то есть на то, на что в данный момент указывает переменная *ns*, тогда как макрорасширенный код в контексте файла обрабатывается компилятором файлов, который разрешает символы в соответствии с объявлениями пространства имен в файле. - person Matthias Benkard; 07.02.2012
comment
Спасибо - так что, если *ns* уже указывает на helloworld в примере about - почему это не resolve функция? - person hawkeye; 08.02.2012
comment
@hawkeye Вам нужно строго различать время выполнения и время компиляции. Во время компиляции пространство имен, в котором что-то компилируется, известно, но после компиляции код полностью разрешается и квалифицируется пространством имен и, таким образом, не зависит от текущего пространства имен во время выполнения. Код, переданный в eval, разрешается на основе переменной *ns* в этот момент времени, то есть во время выполнения, а не во время компиляции. Переменная *ns* не меняется волшебным образом, когда вы вызываете функцию, скомпилированную в другом пространстве имен (имхо, это было бы довольно странным поведением). Вы должны связать *ns* вручную. - person Matthias Benkard; 08.02.2012
comment
Более элегантный и гибкий. Очень нравятся объяснения от @MatthiasBenkard. - person hawkeye; 11.02.2012

Попробуйте посмотреть, какое реальное пространство имен находится внутри вашего файла -main.

(defn -main [& args]
  (prn *ns*)
  (eval (read-string "(hello-world-fn)")))

Он выводит #<Namespace user> перед вылетом с исключением. Это намекает на то, что выполнение программ с lein run начинается в пространстве имен user, которое, очевидно, не содержит отображения для вашего символа hello-world-fn. Вам нужно будет явно квалифицировать его.

(defn -main [& args]
  (eval (read-string "(helloworld/hello-world-fn)")))
person Daniel Janus    schedule 06.02.2012
comment
При прямом вызове (hello-world-fn) это работает. Почему он ведет себя иначе, чем eval? - person viebel; 07.02.2012
comment
@YehonathanSharvit Поскольку в этом случае символ hello-world-fn будет разрешен во время компиляции, поэтому объявление пространства имен в верхней части исходного файла имеет значение. "(hello-world-fn)", с другой стороны, это просто строка. Компилятор не просматривает строки, чтобы квалифицировать их по пространству имен, потому что это было бы неправильно; поэтому eval, который запускается во время выполнения, должен разрешать ссылки на имена, просматривая переменную *ns* при выполнении. - person Matthias Benkard; 07.02.2012
comment
Очень нравится подход к отладке и непосредственное решение. Я думал, что другой был более нестандартным и более элегантным. Цените @MatthiasBenkard за объяснение этапов компиляции. - person hawkeye; 11.02.2012