Какая разница между парсерами и скобками в "требовании"?

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

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

и

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

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

(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))

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

Ответы

Ответ 1

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

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

(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 [email protected]))) ; 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}))

В этот момент я должен, вероятно, объяснить, что ' вызывает следующую форму: quote d. Это означает, что компилятор прочитает форму, но не выполнит ее или не попытается разрешить символы и т.д. т.е. '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 не заботит, будем ли мы давать ему списки (parens) или векторы (скобки) для работы. Он просто рассматривает аргументы как последовательности вещей. Например, это работает:

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

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

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

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

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

conj    ; => #<core$conj [email protected]>
'conj   ; => conj 
''conj  ; => (quote conj)
'''conj ; => (quote (quote conj))

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

Ответ 2

TL; ДР:

(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))

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