2016-10-09 21 views
5

में नेस्टेड मैक्रो के बीच संकलन समय राज्य मैं, ऐसा मैक्रो दोनों एक वैश्विक और नेस्टेड तरह से इस्तेमाल किया जा सकता है लिखने के लिए कोशिश कर रहा हूँ तो जैसे:पासिंग Clojure

;;; global: 
(do-stuff 1) 

;;; nested, within a "with-context" block: 
(with-context {:foo :bar} 
    (do-stuff 2) 
    (do-stuff 3)) 

जब नेस्टेड तरह से इस्तेमाल किया, do-stuffwith-context द्वारा {:foo :bar} तक पहुंच होना चाहिए।

मैं इस तरह इसे लागू कर लिया है:

(def ^:dynamic *ctx* nil) 

(defmacro with-context [ctx & body] 
    `(binding [*ctx* ~ctx] 
    (do [email protected]))) 

(defmacro do-stuff [v] 
    `(if *ctx* 
    (println "within context" *ctx* ":" ~v) 
    (println "no context:" ~v))) 

हालांकि, मैं संकलन-समय पर क्रम से do-stuff भीतर if शिफ्ट करने के लिए कोशिश कर रहा है, क्योंकि है कि क्या do-stuff के भीतर से बुलाया जा रहा है with-context का शरीर या वैश्विक रूप से एक ऐसी जानकारी है जो संकलन-समय पर पहले से ही उपलब्ध है।

दुर्भाग्य से, मैं, एक समाधान खोजने के लिए सक्षम नहीं किया गया है, क्योंकि नेस्टेड मैक्रो एकाधिक "मैक्रो विस्तार रन" में विस्तार करने के लिए लग रहे हैं तो गतिशील (with-context के भीतर निर्धारित रूप में) *ctx* के बंधन अब जब उपलब्ध नहीं है do-stuff विस्तारित हो जाता है। तो यह काम नहीं करता है:

(def ^:dynamic *ctx* nil) 

(defmacro with-context [ctx & body] 
    (binding [*ctx* ctx] 
    `(do [email protected]))) 

(defmacro do-stuff [v] 
    (if *ctx* 
    `(println "within context" ~*ctx* ":" ~v) 
    `(println "no context:" ~v))) 

कोई विचार यह कैसे पूरा किया जाए?

या मेरा दृष्टिकोण पूरी तरह से पागल है और एक मैक्रो से घोंसले तक इस तरह से राज्य को कैसे पारित करने के लिए एक पैटर्न है?

संपादित:

with-context के शरीर न केवल do-stuff (या अन्य संदर्भ जागरूक कार्य/मैक्रो) के साथ, मनमाना भाव के साथ काम करने में सक्षम होना चाहिए। तो कुछ इस तरह भी संभव हो जाना चाहिए:

(with-context {:foo :bar} 
    (do-stuff 2) 
    (some-arbitrary-function) 
    (do-stuff 3)) 

(मुझे पता है some-arbitrary-function दुष्प्रभावों के बारे में है कि कर रहा हूँ, यह उदाहरण के लिए एक डेटाबेस के लिए कुछ लिख सकता है।)

उत्तर

4

जब कोड macroexpanded की जा रही है, Clojure computes एक fixpoint:

(defn macroexpand 
    "Repeatedly calls macroexpand-1 on form until it no longer 
    represents a macro form, then returns it. Note neither 
    macroexpand-1 nor macroexpand expand macros in subforms." 
    {:added "1.0" 
    :static true} 
    [form] 
    (let [ex (macroexpand-1 form)] 
     (if (identical? ex form) 
     form 
     (macroexpand ex)))) 

किसी भी आप किसी मैक्रो के निष्पादन के दौरान स्थापित बाध्यकारी कोई और अधिक जगह में है जब आप अपने मैक्रो से बाहर निकलेंगे (यह macroexpand-1 के अंदर होता है)। जब तक एक आंतरिक मैक्रो का विस्तार किया जा रहा है, संदर्भ लंबे समय से चला गया है।

लेकिन, आप सीधे macroexpand पर कॉल कर सकते हैं, इस स्थिति में बाध्यकारी अभी भी प्रभावी हैं। ध्यान दें कि आपके मामले में, आपको शायद macroexpand-all पर कॉल करने की आवश्यकता है। This answermacroexpand और clojure.walk/macroexpand-all के बीच अंतर बताता है: मूल रूप से, आपको यह सुनिश्चित करना होगा कि सभी आंतरिक रूप मैक्रोएक्सेंडेड हैं। macroexpand-all के लिए स्रोत कोड how it is implemented दिखाता है।

तो, आप अपने मैक्रो इस प्रकार लागू कर सकते हैं:

(defmacro with-context [ctx form] 
    (binding [*ctx* ctx] 
    (clojure.walk/macroexpand-all form))) 

उस मामले में, गतिशील बाइंडिंग भीतरी मैक्रो अंदर से दिखाई देना चाहिए।

+0

बहुत बढ़िया, यह वही है जो मैं खोज रहा था, धन्यवाद! मजेदार तथ्य: मैंने वास्तव में 'मैक्रोएक्सपैंड' और' मैक्रोएक्सपैंड -1 'के साथ यह कोशिश की है लेकिन यह काम नहीं कर सका। मैं बस 'macroexpand-all' के बारे में पता नहीं था। बहुत बहुत धन्यवाद। हालांकि, मैं सोच रहा हूं कि मैक्रोज़ के बीच इस तरह के राज्य को पारित करने का मेरा विचार अवधारणात्मक रूप से उचित है। कोड के भीतर से 'मैक्रोएक्सपैंड' जैसी कुछ कॉल करना हमेशा अजीब लगता है। क्या मुझे यहां एक मुहावरे याद आ रही है जो मेरी समस्या को बेहतर (अधिक मूर्खतापूर्ण, क्लीनर) तरीके से हल करेगी? – Oliver

+0

मुझे इसके बारे में कोई आपत्ति नहीं है: आखिरकार, फ़ंक्शन उपलब्ध कराया गया है ताकि आप इसका उपयोग कर सकें। यह थोड़ा असामान्य है, इसलिए आपको 'संदर्भ के साथ', '* ctx *' के साथ-साथ किसी भी मैक्रो पर एक अच्छा प्रलेखन प्रदान करना होगा जो उस पर निर्भर करता है। यह युग्मन का एक रूप है, लेकिन यदि आपको इसकी आवश्यकता है, तो यह काम करता है। मैं उससे कुछ भी "क्लीनर" के बारे में नहीं जानता। – coredump

2

मैं इसे सरल रखने चाहते हैं। यह समाधान अतिरिक्त *ctx* चर में राज्य से बचाता है। मुझे लगता है कि यह एक और अधिक कार्यात्मक दृष्टिकोण है।

(defmacro do-stuff 
    ([arg1 context] 
    `(do (prn :arg1 ~arg1 :context ~context)) 
     {:a 4 :b 5}) 
    ([arg1] 
    `(prn :arg1 ~arg1 :no-context))) 

(->> {:a 3 :b 4} 
    (do-stuff 1) 
    (do-stuff 2)) 

उत्पादन:

:arg1 1 :context {:a 3, :b 4} 
:arg1 2 :context {:b 5, :a 4} 
+0

धन्यवाद। यह वास्तव में सरल और अधिक कार्यात्मक है, आप सही हैं। हालांकि, यह थ्रेडिंग मैक्रो ब्लॉक के भीतर 'डू-स्टफ' (या अन्य संदर्भ-जागरूक फ़ंक्शंस/मैक्रोज़) का उपयोग करते समय ही काम करता है। क्या होगा यदि मैं उस शरीर में मनमानी कार्यों/मैक्रोज़ का उपयोग करने में सक्षम होना चाहता हूं जो संदर्भ को अंतिम पैरामीटर के रूप में स्वीकार नहीं करता है? जैसे '(- >> {: एक 3: बी 4} (डू-स्टफ 1) (खुले [आर स्ट्रीम] ...))'। कोई विचार? (क्षमा करें, मेरे मूल प्रश्न में वास्तव में यह उल्लेख नहीं किया गया है कि यह एक आवश्यकता है, क्योंकि मैं जितना संभव हो सके उदाहरण बनाने की कोशिश कर रहा था)। – Oliver

1

वहाँ यह करने के लिए एक और संस्करण है, कुछ मैक्रो जादू का उपयोग कर रहा है: इस परिभाषा में

(defmacro with-context [ctx & body] 
    (let [ctx (eval ctx)] 
    `(let [~'&ctx ~ctx] 
     (binding [*ctx* ~ctx] 
     (do [email protected]))))) 

हम एक और letctx के लिए बाध्यकारी परिचय। क्लोजर की मैक्रो सिस्टम इसे &env वैरिएबल में रखेगी, संकलन-समय पर आंतरिक मैक्रोज़ द्वारा पहुंचा जा सकता है। ध्यान दें कि हम bindings भी रखते हैं ताकि आंतरिक कार्य इसका उपयोग कर सकें।

अब हम मैक्रो का &env से संदर्भ मूल्य प्राप्त करने के समारोह परिभाषित करने की जरूरत:

(defn env-ctx [env] 
    (some-> env ('&ctx) .init .eval)) 

और फिर आप आसानी से do-stuff परिभाषित कर सकते हैं:

user> (defn my-fun [] 
     (println "context in fn is: " *ctx*)) 
#'user/my-fun 

user> (defmacro my-macro [] 
     `(do-stuff 100)) 
#'user/my-macro 

user> (with-context {:a 10 :b 20} 
     (do-stuff 1) 
     (my-fun) 
     (my-macro) 
     (do-stuff 2)) 
;;within context {:a 10, :b 20} : 1 
;;context in fn is: {:a 10, :b 20} 
;;within context {:a 10, :b 20} : 100 
;;within context {:a 10, :b 20} : 2 
nil 

user> (do (do-stuff 1) 
      (my-fun) 
      (my-macro) 
      (do-stuff 2)) 
;;no context: 1 
;;context in fn is: nil 
;;no context: 100 
;;no context: 2 
nil 
:

(defmacro do-stuff [v] 
    (if-let [ctx (env-ctx &env)] 
    `(println "within context" ~ctx ":" ~v) 
    `(println "no context:" ~v))) 
repl में

+0

धन्यवाद। यह एक बहुत ही रोचक समाधान है, कुछ हद तक अधिक शामिल है। मैं बस सोच रहा हूं कि 'और env' मानों के कार्यान्वयन पर भरोसा करना एक अच्छा विचार है (मान लीजिए कि' .init' और '.val' जैसी विधियां हैं)। लेकिन फिर भी, यह मैक्रोज़ में '& env' से लाभ कैसे प्राप्त कर सकता है, इस बारे में एक दिलचस्प अंतर्दृष्टि प्रदान करता है। – Oliver