В чем разница между скобками и скобками в require?

Одна вещь, которая меня немного смущает, - это различия между скобками и скобками в операторах clojure require. Мне было интересно, может ли кто-нибудь мне это объяснить. Например, они делают то же самое:

(ns sample.core
  (:gen-class)
  (:require clojure.set clojure.string))

и

 (ns sample.core
  (:gen-class)
  (:require [clojure.set] 
            [clojure.string]))

Однако это работает из ответа

(require 'clojure.string 'clojure.test)

Но не работает в файле clj

(ns sample.core
  (:gen-class)
  (:require 'clojure.string 'clojure.test))
...
Exception in thread "main" java.lang.Exception: lib names inside prefix lists must not contain periods
    at clojure.core$load_lib.doInvoke(core.clj:5359)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    ....

В то время как они делают то же самое:

(ns sample.core
  (:gen-class)
  (require clojure.set clojure.string))

(ns sample.core
  (:gen-class)
  (:require clojure.set clojure.string))

В общем я этого не понимаю. Я понимаю использование, импорт и требование. Но я не понимаю ":" и различий между вещами в [] и '() и т. Д. Может ли кто-нибудь осветить эту тему интуитивно понятным образом?


person David Williams    schedule 09.04.2013    source источник
comment
возможный дубликат Почему требуется в форме ns ведут себя иначе, чем функция require   -  person om-nom-nom    schedule 09.04.2013
comment
Хм, на самом деле не спрашивает о [] и различиях между кодом repl и clj.   -  person David Williams    schedule 09.04.2013


Ответы (2)


Проблема здесь тонкая, и, возможно, ее трудно решить, не разбираясь сначала в макросах.

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

(defmacro print-args-m [& args]
  (print "Your args:")
  (prn args))

(defn print-args-f [& args]
  (print "Your args:")
  (prn args))

(print-args-m (+ 1 2) (str "hello" " sir!"))

; Your args: ((+ 1 2) (str "hello" " sir!"))

(print-args-f (+ 1 2) (str "hello" " sir!"))

; Your args: (3 "hello sir!")

Макросы заменяются их возвращаемым значением. Вы можете проверить этот процесс с помощью macroexpand

(defmacro defmap [sym & args]
  `(def ~sym (hash-map ~@args))) ; I won't explain these crazy symbols here.
                                 ; There are plenty of good tutorials around

(macroexpand
  '(defmap people
     "Steve" {:age 53, :gender :male}
     "Agnes" {:age 7,  :gender :female}))

;  (def people
;    (clojure.core/hash-map
;      "Steve" {:age 53, :gender :male}
;      "Agnes" {:age 7, :gender :female}))

Здесь я, вероятно, должен объяснить, что ' приводит к тому, что следующая форма будет quoted. Это означает, что компилятор прочитает форму, но не выполнит ее, не попытается разрешить символы и так далее. т.е. 'conj оценивается как символ, а conj оценивается как функция. (eval 'conj) эквивалентно (eval (quote conj)) эквивалентно conj.

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

Посмотрим, что расширяется до макроса ns:

(macroexpand
  '(ns sample.core
    (:require clojure.set clojure.string)))

;  (do
;    (clojure.core/in-ns 'sample.core)
;    (clojure.core/with-loading-context
;      (clojure.core/refer 'clojure.core)
;      (clojure.core/require 'clojure.set 'clojure.string)))

Видите, как он цитирует нам символы clojure.set и clojure.string? Как удобно! Но в чем дело, когда вы используете require вместо :require?

(macroexpand
 '(ns sample.core
   (require clojure.set clojure.string)))

;  (do
;    (clojure.core/in-ns 'sample.core)
;    (clojure.core/with-loading-context
;      (clojure.core/refer 'clojure.core)
;      (clojure.core/require 'clojure.set 'clojure.string)))

Кажется, что тот, кто написал макрос ns, был достаточно хорош, чтобы позволить нам делать это обоими способами, поскольку этот результат точно такой же, как и раньше. Neato!

edit: tvachon прав, используя только :require, поскольку это единственная официально поддерживаемая форма

Но в чем дело со скобками?

(macroexpand
  '(ns sample.core
    (:require [clojure.set] 
              [clojure.string])))

; (do
;  (clojure.core/in-ns 'sample.core)
;  (clojure.core/with-loading-context
;   (clojure.core/refer 'clojure.core)
;   (clojure.core/require '[clojure.set] '[clojure.string])))

Оказывается, они тоже цитируются, как и мы, если бы писали автономные вызовы require.

Также оказывается, что ns не заботится, даем ли мы ему для работы списки (скобки) или векторы (скобки). Он просто рассматривает аргументы как последовательность вещей. Например, это работает:

(ns sample.core
  [:gen-class]
  [:require [clojure.set]
            [clojure.string]])

require, как указал amalloy в комментариях, имеет разную семантику для векторов и списков, так что не путайте их!

Наконец, почему следующее не работает?

(ns sample.core
  (:require 'clojure.string 'clojure.test))

Ну, поскольку ns цитирует за нас, эти символы цитируются дважды, что семантически отличается от цитирования только один раз, а также является чистым безумием.

conj    ; => #<core$conj clojure.core$conj@d62a05c>
'conj   ; => conj 
''conj  ; => (quote conj)
'''conj ; => (quote (quote conj))

Надеюсь, это поможет, и я определенно рекомендую научиться писать макросы. Они супер веселые.

person d.j.sheldrick    schedule 09.04.2013
comment
(:require (clojure.set) (clojure.string)) вообще не работает. Это бесполезная операция, которая выглядит так, как будто она работает, потому что вы выбрали два пространства имен, которые уже требуются. Попробуйте применить его к некоторым несуществующим пространствам имен: он завершится незаметно; в существующих пространствах имен он ничего не делает. Использование скобок здесь указывает на список префиксов, как в (:require (clojure set string)); указанный вами синтаксис работает только с векторами. - person amalloy; 09.04.2013
comment
отличный ответ, и +1 за ссылку TDT, если это было то, что было - person Hendekagon; 10.04.2013
comment
DJ, почему это работает: (ns image-test.core (:gen-class) (:require (png-extract) [clojure.string :as string])) но не работает: (ns image-test.core (:gen-class) (:require [png-extract] [clojure.string :as string])) - person David Williams; 20.04.2013

TL;DR:

(ns sample.core
  (:gen-class)
  (:require clojure.set clojure.string))

и

 (ns sample.core
  (:gen-class)
  (:require [clojure.set] 
            [clojure.string]))

оба хороши - вторая версия - это просто частный случай наиболее гибкого синтаксиса, поддерживаемого require. Это также можно было бы записать как:

 (ns sample.core
  (:gen-class)
  (:require [clojure set string]))

В общем, эта последняя форма лучше всего подходит для этого конкретного требования.


(require 'clojure.string 'clojure.test)

Также работает в файле clj - попробуйте это:

(ns sample.core
  (:gen-class))
(require 'clojure.string 'clojure.test)

Путаница здесь заключается в том, что в вашем сломанном примере вы пытаетесь использовать «символы в кавычках» в предложении :require макроса ns. Вероятно, это не самое интуитивное объяснение, но вот как оно выходит из строя:

Есть два способа потребовать другие модули: require и ns.

require - это функция, которая принимает список цитируемых форм («цитирование» необходимо для того, чтобы clojure не выполнял поиск символов, которые вы передаете в require, как это делает все другие символы).

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

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

В исходных файлах Clojure вы всегда должны использовать предложение ns, чтобы требовать библиотеки - require следует использовать только в REPL.


В ваших последних двух примерах вы правы, что

(ns sample.core
  (:gen-class)
  (require clojure.set clojure.string))

работает, но это случайность - вероятно, результат того, что

(name :require)
=> "require"

(name 'require)
=> "require"

Документированный синтаксис:

(ns sample.core
  (:gen-class)
  (:require clojure.set clojure.string))

и это единственный, который гарантированно не сломается в будущем.

person tvachon    schedule 09.04.2013
comment
В исходных файлах Clojure вы всегда должны использовать предложение ns, чтобы требовать библиотеки - require следует использовать только в REPL. Почему? - person Daniel Kaplan; 09.04.2013
comment
Никакой технической причины - все дело в стиле и удобочитаемости. Последовательное использование ns гарантирует, что другие программисты могут легко увидеть, какие пространства имен требуются для файла, не копаясь во всем файле. Это также немного чище, поскольку вам не нужно экранировать формы вручную. - person tvachon; 09.04.2013