2013-04-09 16 views
15

Una cosa su cui sono stato un po 'confuso è che le differenze tra parentesi e parentesi in clojure richiedono dichiarazioni. Mi chiedevo se qualcuno potesse spiegarmelo. Ad esempio, questi fanno la stessa cosa:Qual è la differenza tra parentesi e parentesi in "richiedere"?

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

e

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

Tuttavia, questo funziona dal repl

(require 'clojure.string 'clojure.test) 

ma non riesce in un file 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) 
    .... 

considerando che questi a fare lo stesso sottile g:

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

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

In generale, non lo capisco. Capisco usare, importare e richiedere. Ma non capisco il ":" e le differenze tra le cose in [] e "() ecc. Qualcuno può illuminare questo argomento in modo intuitivo?

+0

possibile duplicato di [Perché il modulo ns richiede un comportamento diverso dalla funzione di richiesta] (http://stackoverflow.com/questions/3719929/why-does-require-in-the-ns-form-behave- different-from-the-require-function) –

+1

Hm, in realtà non chiede le [] e le differenze tra il codice repl e clj. –

risposta

12

Il problema qui è sottile e possibilmente difficile da trovare senza prima capire un po 'sulle macro.

Le macro manipolano la sintassi nello stesso modo in cui le funzioni gestiscono i valori. In effetti, le macro sono solo funzioni con un hook che le fa valutare al momento della compilazione. Sono passati i dati letterali che vedi nel codice sorgente e sono valutati dall'alto verso il basso. Facciamo una funzione e una macro che hanno lo stesso corpo in modo da poter vedere la differenza:

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

macro vengono sostituite dal loro valore di ritorno. È possibile controllare questo processo con 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})) 

A questo punto, forse dovrei spiegare che ' provoca il seguente modulo per essere quote d. Ciò significa che il compilatore leggerà il modulo, ma non lo eseguirà o tenterà di risolvere i simboli e così via. Ad esempio, 'conj valuta un simbolo, mentre conj valuta una funzione. (eval 'conj) equivale a (eval (quote conj)) equivale a conj.

Con questo in mente, sappi che non puoi risolvere un simbolo come uno spazio dei nomi finché non è stato magicamente importato nel tuo spazio dei nomi in qualche modo. Questo è ciò che fa la funzione require. Prende i simboli e trova gli spazi dei nomi a cui corrispondono, rendendoli disponibili nello spazio dei nomi corrente.

Vediamo cosa la macro ns si espande a:

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

Vedi come citato i simboli clojure.set e clojure.string per noi? Quanto conveniente! Ma qual è il problema quando si utilizza require anziché :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))) 

Sembra che chi ha scritto la macro ns stato abbastanza gentile da farci fare entrambe le cose, dal momento che questo risultato è esattamente lo stesso di prima. Neato!

edit: tvachon ha ragione su solo con :require dal momento che è l'unica forma

ufficialmente supportato Ma qual è il problema con le staffe?

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

scopre che vengono citati anche, proprio come faremmo se stessimo scrivendo chiamate standalone per require.

Si scopre anche che ns non interessa se gli forniamo elenchi (parenti) o vettori (parentesi) con cui lavorare. Vede solo gli argomenti come sequenze di cose. Ad esempio, questo funziona:

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

require, come ha sottolineato amalloy nei commenti, ha una semantica diverse per i vettori e le liste, in modo da non mescolare quelle up!

Infine, perché il seguente lavoro non funziona?

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

Ebbene, dal momento che ns fa il nostro citando per noi, questi simboli ottenere citato due volte, che è semanticamente differente da essere citato solo una volta ed è anche pura pazzia.

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

Spero che questo aiuta, e vi consiglio caldamente imparare a scrivere le macro. Sono super divertenti.

+2

'(: require (clojure.set) (clojure.string))' non funziona affatto. È un no-op, che capita di * sembrare * come funziona perché hai scelto due namespace che sono già richiesti. Provalo su alcuni namespace inesistenti: ci riesce in silenzio; su spazi dei nomi esistenti silenziosamente non fa nulla. L'uso di parens qui indica un elenco di prefissi, come in '(: require (clojure set string))'; la sintassi che hai dato funziona solo con i vettori. – amalloy

+0

Ben individuato. Modificheremo il post per riflettere. –

+0

ottima risposta, e +1 per riferimento TDT, se è quello che era – Hendekagon

4

TL; DR:

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

e

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

sono entrambi bene - la seconda versione è solo un caso speciale della sintassi più flessibile require supporti. Questo potrebbe anche essere scritto come:

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

In generale, questa ultima forma è la migliore procedura per questa particolare esigenza.


(require 'clojure.string 'clojure.test)

funziona anche in un file CLJ - provare questo:

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

La confusione qui è che nel tuo esempio rotta si sta cercando di usare "simboli citati" nel :require della macro ns. Questo non è probabilmente la spiegazione più intuitiva, ma ecco come si rompe:

Ci sono due modi per richiedere altri moduli, require e ns.

require è una funzione che accetta un elenco di moduli citati (il "quoting" è necessario per evitare che il clojure cerchi i simboli passati a require come tutti gli altri simboli).

ns è una macro che supporta un'opzione :require. Prende il valore di questa opzione e, sotto le copertine, lo trasforma in una chiamata alla funzione require. Non è necessario citare il valore dell'opzione :require perché ns è una macro ed è quindi in grado di citare i simboli stessi.

Questo potrebbe non essere ancora chiaro, ma suggerirei di passare alla documentazione di Clojure per chiarire: una volta compreso appieno tutto ciò, avrai una migliore comprensione di Clojure in generale.

Nei file di origine Clojure è necessario utilizzare sempre una clausola ns per richiedere le librerie - require deve essere utilizzato solo in REPL.


Nei tuoi ultimi due esempi che sei corretto che

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

opere, ma questo è un caso - probabilmente a causa del fatto che

 
(name :require) 
=> "require" 

(name 'require) 
=> "require" 

La sintassi è documentata

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

ed è l'unico garantito a non rompere in futuro.

+0

"Nei file di origine Clojure, è necessario utilizzare sempre una clausola ns per richiedere le librerie; è necessario utilizzare require nel REPL." Perché? –

+2

Nessun motivo tecnico - interamente di stile e leggibilità. L'uso di 'ns' garantisce che gli altri programmatori possano facilmente vedere quali spazi dei nomi sono richiesti da un file senza scavare attraverso l'intero file. È anche un po 'più pulito, dal momento che non è necessario scappare manualmente dai moduli. – tvachon

Problemi correlati