2010-05-30 15 views
5

मैं टेम्पलेट्स और इस तरह के उपयोग के लिए क्लोजर (या एक समान प्रभाव) में गतिशील रूप से स्कॉप्ड चर प्राप्त करने के लिए एक बेवकूफ तरीका ढूंढ रहा हूं।क्लोजर में गतिशील स्कॉइंग?

(def *attr-table* 
    ; Key: [attr-key tag-name] or [boolean-function] 
    ; Value: [attr-key attr-value] (empty array to ignore) 
    ; Context: Variables "tagname", "akey", "aval" 
    '(
     ; translate :LINK attribute in <a> to :href 
    [:LINK "a"] [:href aval] 
     ; translate :LINK attribute in <img> to :src 
    [:LINK "img"] [:src aval] 
     ; throw exception if :LINK attribute in any other tag 
    [:LINK]  (throw (RuntimeException. (str "No match for " tagname))) 
    ; ... more rules 
     ; ignore string keys, used for internal bookkeeping 
    [(string? akey)] [] )) ; ignore 

मैं चाहता हूँ:

यहाँ एक लुकअप तालिका का उपयोग कर टैग HTML, करने के लिए कुछ गैर HTML स्वरूप जहां मेज कहीं और से आपूर्ति की चर का एक सेट के उपयोग की जरूरत से जिम्मेदार बताते अनुवाद करने के लिए एक उदाहरण समस्या है नियमों (बाएं हाथ की ओर) के साथ-साथ नतीजे (दाएं हाथ की ओर) का मूल्यांकन करने में सक्षम हो, और तालिका का मूल्यांकन करने वाले स्थान पर वेरिएबल्स को दायरे में रखने के लिए कुछ तरीका चाहिए।

मैं लुकअप और मूल्यांकन तर्क किसी भी विशेष तालिका या चर के सेट से स्वतंत्र रखना चाहता हूं।

मुझे लगता है कि टेम्पलेट्स (उदाहरण के लिए गतिशील एचटीएमएल के लिए) में समान समस्याएं हैं, जहां आप किसी टेम्पलेट प्रसंस्करण तर्क को फिर से लिखना नहीं चाहते हैं जब कोई टेम्पलेट में कोई नया चर डालता है।

वैश्विक चर और बाइंडिंग का उपयोग करके यहां एक दृष्टिकोण है।

;; Generic code, works with any table on the same format. 
(defn rule-match? [rule-val test-val] 
    "true if a single rule matches a single argument value" 
    (cond 
    (not (coll? rule-val)) (= rule-val test-val) ; plain value 
    (list? rule-val) (eval rule-val) ; function call 
    :else false)) 

(defn rule-lookup [test-val rule-table] 
    "looks up rule match for test-val. Returns result or nil." 
    (loop [rules (partition 2 rule-table)] 
    (when-not (empty? rules) 
     (let [[select result] (first rules)] 
     (if (every? #(boolean %) (map rule-match? select test-val)) 
      (eval result) ; evaluate and return result 
      (recur (rest rules))))))) 

;; Code specific to *attr-table* 
(def tagname) ; need these globals for the binding in html-attr 
(def akey) 
(def aval) 

(defn html-attr [tagname h-attr] 
    "converts to html attributes" 
    (apply hash-map 
    (flatten 
     (map (fn [[k v :as kv]] 
      (binding [tagname tagname akey k aval v] 
       (or (rule-lookup [k tagname] *attr-table*) kv))) 
     h-attr)))) 

;; Testing 
(defn test-attr [] 
    "test conversion" 
    (prn "a" (html-attr "a" {:LINK "www.google.com" 
          "internal" 42 
          :title "A link" })) 
    (prn "img" (html-attr "img" {:LINK "logo.png" }))) 

user=> (test-attr) 
"a" {:href "www.google.com", :title "A link"} 
"img" {:src "logo.png"} 

यह देखने तर्क तालिका से स्वतंत्र है कि में अच्छा है, तो यह अन्य तालिकाओं और विभिन्न चर के साथ पुन: उपयोग किया जा सकता है: मैं तालिका देखने के लिए कुछ तर्क शामिल किया है। (इसके अलावा सामान्य तालिका दृष्टिकोण कोड के आकार के लगभग एक चौथाई हिस्से में था जब मैंने एक विशाल कंड में "हाथ से" अनुवाद किया था।

यह इतना अच्छा नहीं है कि मुझे इसकी आवश्यकता है काम करने के लिए बाध्यकारी के लिए हर चर को वैश्विक के रूप में घोषित करें।

(defn attr-table [tagname akey aval] 
    `(
    [:LINK "a"] [:href ~aval] 
    [:LINK "img"] [:src ~aval] 
    [:LINK]  (throw (RuntimeException. (str "No match for " ~tagname))) 
    ; ... more rules  
    [(string? ~akey)]  []))) 

केवल परिवर्तन की एक जोड़ी आराम करने के लिए की जरूरत है:

यहाँ एक "अर्द्ध मैक्रो", एक वाक्य रचना उद्धृत वापसी मान वाला समारोह, कि वैश्विक जरूरत नहीं है का उपयोग करते हुए एक और दृष्टिकोण है कोड:

In rule-match? The syntax-quoted function call is no longer a list: 
- (list? rule-val) (eval rule-val) 
+ (seq? rule-val) (eval rule-val) 

In html-attr: 
- (binding [tagname tagname akey k aval v] 
- (or (rule-lookup [k tagname] *attr-table*) kv))) 
+ (or (rule-lookup [k tagname] (attr-table tagname k v)) kv))) 

और हमें ग्लोबल्स के बिना एक ही परिणाम मिलता है। (और गतिशील स्कोपिंग के बिना।)

क्लोजर के binding द्वारा आवश्यक ग्लोबल्स के बिना कहीं और घोषित परिवर्तनीय बाइंडिंग के सेट के साथ पास करने के अन्य विकल्प हैं?

क्या ऐसा करने का एक बेवकूफ तरीका है, जैसे Ruby's binding या Javascript's function.apply(context)?

(defn attr-table [akey aval] 
    (list 
    [:LINK "a"] [:href aval] 
    [:LINK "img"] [:src aval] 
    [:LINK]  [:error "No match"] 
    [(string? akey)] [])) 

(defn match [rule test-key] 
    ; returns rule if test-key matches rule key, nil otherwise. 
    (when (every? #(boolean %) 
      (map #(or (true? %1) (= %1 %2)) 
      (first rule) test-key)) 
    rule)) 

(defn lookup [key table] 
    (let [[hkey hval] (some #(match % key) 
         (partition 2 table)) ] 
    (if (= (first hval) :error) 
     (let [msg (str (last hval) " at " (pr-str hkey) " for " (pr-str key))] 
     (throw (RuntimeException. msg))) 
     hval))) 

(defn html-attr [tagname h-attr] 
    (apply hash-map 
    (flatten 
     (map (fn [[k v :as kv]] 
      (or 
       (lookup [k tagname] (attr-table k v)) 
       kv)) 
     h-attr)))) 

: कोई वैश्विक, कोई evals और कोई गतिशील scoping -

अद्यतन

शायद मैं इसे बहुत ज्यादा जटिल है, यहाँ कर रहा था कि मैं क्या मान ऊपर का एक और अधिक कार्यात्मक कार्यान्वयन है यह संस्करण छोटा, सरल है और बेहतर पढ़ता है। तो मुझे लगता है कि मुझे गतिशील स्कॉइंग की आवश्यकता नहीं है, कम से कम अभी तक नहीं।

पोस्टस्क्रिप्ट

निकला ऊपर मेरी अद्यतन में "हर बार मूल्यांकन everyting" दृष्टिकोण समस्याग्रस्त किया जा करने के लिए, और मैं समझ नहीं सकता है कि कैसे एक multimethod प्रेषण के रूप में सभी सशर्त परीक्षण लागू करने के लिए (हालांकि मैं लगता है कि यह संभव होना चाहिए)।

तो मैं एक मैक्रो के साथ समाप्त हुआ जो तालिका को एक समारोह और एक cond में फैलाता है। यह मूल eval कार्यान्वयन के लचीलेपन को बरकरार रखे हुए है, लेकिन और अधिक कुशल है, कम कोडिंग लेता है और गतिशील scoping की जरूरत नहीं है:

(deftable html-attr [[akey tagname] aval] 
    [:LINK ["a" "link"]] [:href aval] 
    [:LINK "img"]  [:src aval] 
    [:LINK]    [:ERROR "No match"] 
    (string? akey)  [])))) 

फैलता

(defn html-attr [[akey tagname] aval] 
    (cond 
    (and 
     (= :LINK akey) 
     (in? ["a" "link"] tagname)) [:href aval] 
    (and 
     (= :LINK akey) 
     (= "img" tagname))   [:src aval] 
    (= :LINK akey) (let [msg__3235__auto__ (str "No match for " 
              (pr-str [akey tagname]) 
              " at [:LINK]")] 
        (throw (RuntimeException. msg__3235__auto__))) 
    (string? akey) [])) 

में मैं नहीं जानता कि यह है कि क्या विशेष रूप से कार्यात्मक, लेकिन यह निश्चित रूप से डीएसएलिश है (दोहराव वाले कार्यों को सरल बनाने के लिए एक माइक्रोलांगेज बनाएं) और लिस्पी (डेटा के रूप में कोड, कोड के रूप में डेटा), जिनमें से दोनों ऑर्थोगोनल कार्यात्मक होने के लिए हैं।

मूल प्रश्न पर - क्लोजर में गतिशील स्कॉइंग कैसे करें - मुझे लगता है कि उत्तर बन जाता है कि मूर्खतापूर्ण क्लोजर तरीका उस सुधार को ढूंढना है जिसकी आवश्यकता नहीं है।

उत्तर

6

समस्या का आपका दृष्टिकोण बहुत कार्यात्मक प्रतीत नहीं होता है, और आप अक्सर eval का उपयोग कर रहे हैं; यह खराब डिजाइन की तरह गंध करता है।

eval पर कोड के स्निपेट का उपयोग करने के बजाय, उचित फ़ंक्शन का उपयोग क्यों न करें? यदि आवश्यक चर सभी पैटर्न के लिए तय किए गए हैं, तो आप उन्हें तर्क के रूप में सीधे पास कर सकते हैं; यदि वे नहीं हैं, तो आप एक मानचित्र के रूप में बाइंडिंग में गुजर सकते हैं।

+0

मुझे लगता है कि आपके पास एक बिंदु है। मैंने एक अद्यतन संस्करण जोड़ा, बेहतर? –

+0

मैं आपका जवाब स्वीकार कर रहा हूं क्योंकि इससे मुझे यह सोचने में मदद मिली कि मैं बिना किसी eval के इसे कैसे कर सकता हूं। मैं कार्यात्मक शैली में नया हूं, मेरी आंत प्रतिक्रिया यह है कि यह आपके लिए बिल्कुल कुछ भी मूल्यांकन करने के लिए अविश्वसनीय रूप से अपर्याप्त है, लेकिन शायद यह गैर-कार्यात्मक सोच है? –

6

आपका कोड ऐसा लगता है कि आप इसे जितना कठिन बना रहे हैं उससे कठिन बना रहे हैं। मुझे लगता है कि आप वास्तव में क्या चाहते हैं क्लोजर बहु-तरीकों है। आप उन्हें एटआर-टेबल में बनाए गए प्रेषण तालिका को बेहतर सारणी के लिए उपयोग कर सकते हैं और इसे काम करने के लिए आपको गतिशील स्कॉइंग या ग्लोबल्स की आवश्यकता नहीं है।

; helper macro for our dispatcher function 
(defmulti html-attr (fn [& args] (take (dec (count args)) args))) 

(defmethod html-attr [:LINK "a"] 
    [attr tagname aval] {:href aval}) 

(defmethod html-attr [:LINK "img"] 
    [attr tagname aval] {:src aval}) 

ग्लोबल्स या यहां तक ​​कि एक अटैच-टेबल की आवश्यकता के बिना सभी बहुत संक्षिप्त और कार्यात्मक।

उपयोगकर्ता => (एचटीएमएल-attr: लिंक "एक" "http://foo.com") {: href "http://foo.com}

यह ऐसा नहीं करता है अपने करती है, लेकिन एक छोटे संशोधन और यह होगा

वास्तव में क्या।
+0

टिप के लिए धन्यवाद। पहली नज़र में ऐसा लगता है तालिका संस्करण की तुलना में अधिक टाइपिंग, "defmethod html-attr" की पुनरावृत्ति के साथ-साथ तर्क सूची वास्तविक नियमों की तुलना में अधिक वर्ण हैं, लेकिन मुझे इसमें एक नज़र डालेंगी। किसी भी मामले में हम इस बात से सहमत हैं कि मैं इसे जितना कठिन बनाना चाहता हूं :) –

+0

जे-जी-फस्टस: clojure.template पर एक नज़र डालें; ठीक से इस्तेमाल किया, यह verbosity को हटा देगा। – Brian

+0

ब्रायन: धन्यवाद, मैं इसे देख लूंगा। –

संबंधित मुद्दे