2012-06-08 18 views
19

संपादित में योग प्रकार (या तो बी) का प्रतिनिधित्व करने के लिए बेवकूफ तरीका। मेरा सवाल अब है: सांख्यिकीय रूप से प्रकार की भाषाओं में योग प्रकारों के बजाय आमतौर पर कौन सी मूर्खतापूर्ण क्लोजर संरचनाएं उपयोग की जाती हैं? आम सहमति: अब प्रोटोकॉल का उपयोग करें यदि व्यवहार को एकीकृत किया जा सकता है, टैग किए गए जोड़े/मानचित्रों का उपयोग अन्यथा करें, पूर्व-और बाद की स्थितियों में आवश्यक आवेषण दें।क्लोजर

Clojure उत्पाद प्रकार व्यक्त करने के लिए कई तरीके प्रदान करता है: वैक्टर, नक्शे, रिकॉर्ड ..., लेकिन कैसे आप sum types का प्रतिनिधित्व करते हैं, भी टैग किया यूनियनों और संस्करण रिकॉर्ड के रूप में जाना जाता है? हास्केल में Either a b या स्कैला में Either[+A, +B] की तरह कुछ।

पहली बात जो मेरे दिमाग में आती है वह एक विशेष टैग वाला नक्शा है: {:tag :left :value a}, लेकिन फिर सभी कोड (:tag value) पर सशर्त के साथ प्रदूषित होने जा रहे हैं और यदि यह नहीं है तो विशेष मामलों को संभालना ... मैं क्या हूं ' डी यह सुनिश्चित करना है कि :tag हमेशा वहां होता है, और यह केवल निर्दिष्ट मानों में से एक ले सकता है, और संबंधित मान लगातार उसी प्रकार/व्यवहार का होता है और nil नहीं हो सकता है, और यह देखने का एक आसान तरीका है कि मैंने लिया कोड में सभी मामलों की देखभाल।

मैं defrecord की तर्ज में किसी मैक्रो के बारे में सोच सकते हैं, लेकिन योग प्रकार के लिए:

; it creates a special record type and some helper functions 
(defvariant Either 
    left Foo 
    right :bar) 
; user.Either 

(def x (left (Foo. "foo"))) ;; factory functions for every variant 
; #user.Either{:variant :left :value #user.Foo{:name "foo"}} 
(def y (right (Foo. "bar"))) ;; factory functions check types 
; SomeException... 
(def y (right ^{:type :bar}())) 
; #user.Either{:variant :right :value()} 

(variants x) ;; list of all possible options is intrinsic to the value 
; [:left :right] 

इस तरह की एक बात यह है कि पहले से ही मौजूद है? (उत्तर:)।

+1

LispCast पर एक महान जवाब: http://www.lispcast.com/idiomatic-way-to-represent-either – sastanin

उत्तर

16

आप योग प्रकारों का प्रतिनिधित्व कैसे करते हैं, जिन्हें टैग किए गए यूनियनों और संस्करण रिकॉर्ड के रूप में भी जाना जाता है? हास्केल में Either a b या स्कैला में Either[+A, +B] जैसे कुछ। एक ही प्रकार के अलग अलग टैग के आधार पर अर्थ विज्ञान होना चाहिए कि के दो मानों वापसी के लिए दो प्रकार के का मान प्रदान या करने के लिए:

Either दो उपयोग हैं।

स्थिर उपयोग प्रणाली का उपयोग करते समय पहला उपयोग केवल महत्वपूर्ण है। Either मूल रूप से हास्केल प्रकार प्रणाली की बाधाओं को देखते हुए न्यूनतम समाधान संभव है। गतिशील प्रकार प्रणाली के साथ, आप अपने इच्छित किसी भी प्रकार के मान वापस कर सकते हैं। Either की आवश्यकता नहीं है।

दूसरा उपयोग महत्वपूर्ण है, लेकिन काफी पूरा किया जा सकता बस दो (या अधिक) तरीकों से :

  1. {:tag :left :value 123} {:tag :right :value "hello"}
  2. {:left 123} {:right "hello"}

मुझे क्या करना चाहते हैं यह सुनिश्चित करने के लिए, वह है: टैग हमेशा होता है, और यह निर्दिष्ट मानों में से केवल एक ले सकता है , और संबंधित मान लगातार उसी प्रकार/व्यवहार का है और शून्य नहीं हो सकता है, और वहां यह देखने का एक आसान तरीका है कि मैंने कोड में सभी मामलों का ख्याल रखा।

यदि आप यह स्थिर रूप से सुनिश्चित करना चाहते हैं, तो क्लोजर शायद आपकी भाषा नहीं है। कारण सरल है: अभिव्यक्तियों के पास रनटाइम तक प्रकार नहीं होते हैं - जब तक वे कोई मान वापस नहीं लेते।

मैक्रो काम नहीं करेगा कारण यह है कि मैक्रो विस्तार समय पर, आप रनटाइम मान नहीं हैं - और इसलिए रनटाइम प्रकार हैं। आपके पास संकलन, परमाणुओं, लिंगों आदि जैसे संकलन-समय संरचनाएं हैं। आप eval कर सकते हैं, लेकिन eval का उपयोग कारणों के लिए खराब अभ्यास माना जाता है।

हालांकि, हम रनटाइम पर एक बहुत अच्छी नौकरी कर सकते हैं।

  • टैग हमेशा वहाँ है, और यह केवल निर्दिष्ट में से एक मान ले जा सकते हैं
  • और इसी मूल्य एक ही प्रकार/व्यवहार
  • की लगातार है:

    • मैं यह सुनिश्चित करना चाहते हैं क्या, वह यह है कि
    • और
    • नहीं हो सकता है और यह देखने का एक आसान तरीका है कि मैंने कोड में सभी मामलों का ख्याल रखा।

    मेरी रणनीति सामान्य रूप से स्टैटिक (हास्केल में) रनटाइम में कनवर्ट करने वाली सभी चीज़ों को परिवर्तित करने के लिए होगी। आइए कुछ कोड लिखें।

    ;; let us define a union "type" (static type to runtime value) 
    (def either-string-number {:left java.lang.String :right java.lang.Number}) 
    
    ;; a constructor for a given type 
    (defn mk-value-of-union [union-type tag value] 
        (assert (union-type tag)) ; tag is valid 
        (assert (instance? (union-type tag) value)) ; value is of correct type 
        (assert value) 
        {:tag tag :value value :union-type union-type}) 
    
    ;; "conditional" to ensure that all the cases are handled 
    ;; take a value and a map of tags to functions of one argument 
    ;; if calls the function mapped to the appropriate tag 
    (defn union-case-fn [union-value tag-fn] 
        ;; assert that we handle all cases 
        (assert (= (set (keys tag-fn)) 
          (set (keys (:union-type union-value))))) 
        ((tag-fn (:tag union-value)) (:value union-value))) 
    
    ;; extra points for wrapping this in a macro 
    
    ;; example 
    (def j (mk-value-of-union either-string-number :right 2)) 
    
    (union-case-fn j {:left #(println "left: " %) :right #(println "right: " %)}) 
    => right: 2 
    
    (union-case-fn j {:left #(println "left: " %)}) 
    => AssertionError Assert failed: (= (set (keys tag-fn)) (set (keys (:union-type union-value)))) 
    

    इस कोड का उपयोग करता है निम्नलिखित मुहावरेदार Clojure निर्माण करती है:

    • डेटा पर ही आधारित प्रोग्रामिंग: एक डेटा संरचना है जो "प्रकार" का प्रतिनिधित्व करता है बनाएँ। यह मान अपरिवर्तनीय और प्रथम श्रेणी है और आपके पास इसके साथ तर्क लागू करने के लिए पूरी भाषा उपलब्ध है। यह ऐसा कुछ है जो मुझे विश्वास नहीं है कि हास्केल कर सकता है: रनटाइम पर प्रकारों का उपयोग करें।
    • मूल्यों का प्रतिनिधित्व करने के लिए नक्शे का उपयोग करना।
    • उच्च-आदेश प्रोग्रामिंग: किसी अन्य फ़ंक्शन में Fns का नक्शा पास करना।

    यदि आप पॉलिमॉर्फिज्म के लिए Either का उपयोग कर रहे हैं तो आप वैकल्पिक रूप से प्रोटोकॉल का उपयोग कर सकते हैं। अन्यथा, यदि आप टैग में दिलचस्पी रखते हैं, तो {:tag :left :value 123} फॉर्म में से कुछ सबसे बेवकूफ है।आप अक्सर कुछ इस तरह देखेंगे:

    ;; let's say we have a function that may generate an error or succeed 
    (defn somefunction [] 
        ... 
        (if (some error condition) 
        {:status :error :message "Really bad error occurred."} 
        {:status :success :result [1 2 3]})) 
    
    ;; then you can check the status 
    (let [r (somefunction)] 
        (case (:status r) 
        :error 
        (println "Error: " (:message r)) 
        :success 
        (do-something-else (:result r)) 
        ;; default 
        (println "Don't know what to do!"))) 
    
    +0

    धन्यवाद। यह आम तौर पर योग प्रकारों द्वारा प्रदान की जाने वाली सुविधाओं को शामिल करता है। सवाल यह है, क्या यह _idiomatic_ क्लोजर है? – sastanin

    +1

    उत्तर में उत्तर दिया। –

    2

    नहीं, अभी तक क्लोजर में ऐसी कोई चीज़ नहीं है। यद्यपि आप इसे कार्यान्वित कर सकते हैं लेकिन आईएमओ इस प्रकार की स्थिर रूप से टाइप की गई भाषाओं के लिए अधिक उपयुक्त प्रतीत होता है और आपको क्लोजर जैसे गतिशील वातावरण में अधिक लाभ नहीं देगा।

    4

    कुछ भाषाओं में यह बहुत अच्छा काम करने का कारण यह है कि परिणामस्वरूप आप प्रेषण (आमतौर पर टाइप करके) - यानी आप परिणाम के कुछ संपत्ति (आमतौर पर टाइप) का उपयोग करने के लिए निर्णय लेते हैं कि आगे क्या करना है।

    इसलिए आपको यह देखने की ज़रूरत है कि क्लोजर में प्रेषण कैसे हो सकता है।

    1. नहीं के बराबर विशेष मामला - nil मूल्य है विभिन्न स्थानों में विशेष मामलों और "शायद" की "कोई नहीं" भाग के रूप में इस्तेमाल किया जा सकता। उदाहरण के लिए, if-let बहुत उपयोगी है।

    2. पैटर्न मिलान - बेस क्लोजर के लिए अनुक्रमिक अनुक्रमों के अलावा, इसके लिए बहुत अधिक समर्थन नहीं है, लेकिन विभिन्न पुस्तकालय हैं जो करते हैं। देख Clojure replacement for ADTs and Pattern Matching? [अद्यतन: टिप्पणी में mnicky का कहना है कि पुरानी हो चुकी है और आप का उपयोग करना चाहिए core.match] OO साथ

    3. प्रकार के आधार पर - तरीकों प्रकार के आधार पर चुने गए हैं। इसलिए आप माता-पिता के विभिन्न उप-वर्गों को वापस कर सकते हैं और एक ऐसी विधि को कॉल कर सकते हैं जो आपके इच्छित विभिन्न संचालन करने के लिए ओवरलोड हो। यदि आप एक कार्यात्मक पृष्ठभूमि से आ रहे हैं जो बहुत अजीब/बेकार महसूस करेगा, लेकिन यह एक विकल्प है।

    4. हाथ द्वारा टैग - अंत में, आप स्पष्ट टैग के साथ case या cond उपयोग कर सकते हैं। अधिक उपयोगी रूप से, आप उन्हें किसी प्रकार के मैक्रो में लपेट सकते हैं जो आपके इच्छित तरीके से काम करता है।

    +0

    संभव प्रेषण रणनीतियों की यह सूची जब मैं हाथ से संस्करण रिकॉर्ड को लागू करने के बारे में सोचना बहुत ही दिलचस्प है । प्रकार के अनुसार प्रेषण सबसे दिलचस्प लगता है क्योंकि यह जावा ऑब्जेक्ट्स के लिए भी काम करता है, लेकिन इसे 'लगभग टाइप करें' मेटा के साथ ओवरराइड किया जा सकता है (लगभग) बाकी सब कुछ। – sastanin

    +0

    हू, इस बारे में नहीं पता था: प्रकार। धन्यवाद। –

    +1

    पैटर्न मिलान के बारे में इतना SO प्रश्न दिनांकित है। [Core.match] देखें (https://github.com/clojure/core.match) ... – mnicky

    6

    सामान्य में, गतिशील रूप से टाइप किया भाषाओं में योग प्रकार के रूप में प्रतिनिधित्व कर रहे हैं:

    • में चिह्नित जोड़े (उदाहरण के लिए एक टैग निर्माता का प्रतिनिधित्व करने के साथ एक उत्पाद प्रकार) रनटाइम पर टैग पर
    • मामले विश्लेषण एक प्रेषण करने के लिए

    एक सांख्यिकीय रूप से टाइप की गई भाषा में, अधिकांश मान प्रकारों से अलग होते हैं - जिसका अर्थ है कि आपको रनटाइम टैग विश्लेषण करने की आवश्यकता नहीं है यह जानने के लिए कि आपके पास 012 हैया Maybe - तो आप यह जानने के लिए टैग को देखें कि यह Left या Right है या नहीं।

    गतिशील रूप से टाइप की गई सेटिंग में, आपको पहले रनटाइम प्रकार विश्लेषण करना होगा (यह देखने के लिए कि आपके पास किस प्रकार का मूल्य है), और उसके बाद कन्स्ट्रक्टर का केस विश्लेषण (यह देखने के लिए कि आपके पास कौन सा मूल्य है)।

    एक तरीका हर प्रकार के प्रत्येक निर्माता के लिए एक अद्वितीय टैग असाइन करना है।

    एक तरह से, आप गतिशील टाइपिंग के बारे में सोच सकते हैं जैसे सभी मान एक ही योग प्रकार में, रनटाइम परीक्षणों के लिए सभी प्रकार के विश्लेषण को परिभाषित करते हैं।


    मैं यह सुनिश्चित करना चाहते हैं क्या, कि है: टैग हमेशा वहाँ है, और यह केवल निर्दिष्ट में से एक मान ले जा सकते हैं, और इसी मूल्य एक ही प्रकार/व्यवहार के लगातार है और नहीं किया जा सकता शून्य, और यह देखने का एक आसान तरीका है कि मैंने कोड में सभी मामलों का ख्याल रखा।

    एक तरफ के रूप में, यह एक स्थिर प्रकार प्रणाली क्या करेगा, इसका एक विवरण है।

    4

    एक गतिशील टाइप किया भाषा होने के नाते, सामान्य रूप में प्रकार कुछ हद तक कम प्रासंगिक/Clojure में महत्वपूर्ण की तुलना में वे हास्केल/स्काला में कर रहे हैं। आप को स्पष्ट रूप से उन्हें स्पष्ट रूप से परिभाषित करने की आवश्यकता नहीं है - उदाहरण के लिए आप किसी भी प्रकार के मानों को पहले से ही स्टोर कर सकते हैं या एक चर में टाइप बी टाइप कर सकते हैं।

    तो यह वास्तव में निर्भर करता है कि आप इन योग प्रकारों के साथ क्या करने का प्रयास कर रहे हैं। यह संभव है कि आप वास्तव में प्रकार के आधार पर बहुरूपी व्यवहार में रुचि रखते हैं, ऐसी स्थिति में यह भावना एक प्रोटोकॉल और दो अलग अलग प्रकार के रिकॉर्ड है कि एक साथ एक योग प्रकार के बहुरूपी व्यवहार देना परिभाषित करने के लिए कर सकते हैं कर रहे हैं:

    (defprotocol Fooable 
        (foo [x])) 
    
    (defrecord AType [avalue] 
        Fooable 
        (foo [x] 
         (println (str "A value: " (:avalue x))))) 
    
    (defrecord BType [bvalue] 
        Fooable 
        (foo [x] 
         (println (str "B value: " (:bvalue x))))) 
    
    (foo (AType. "AAAAAA")) 
    
    => A value: AAAAAA 
    

    मुझे लगता है कि यह लगभग सभी लाभ प्रदान करेगा जो आपको योग प्रकारों से प्राप्त होने की संभावना है।

    इस दृष्टिकोण की अन्य अच्छा फायदे:

    • रिकॉर्ड्स और प्रोटोकॉल Clojure में बहुत मुहावरेदार हैं
    • उत्कृष्ट प्रदर्शन (के बाद से प्रोटोकॉल प्रेषण भारी अनुकूलित है)
    • आप शून्य के लिए से निपटने के अपने प्रोटोकॉल में जोड़ सकते हैं (extend-protocol के माध्यम से)
    +0

    धन्यवाद। इससे मदद मिलती है जब मूल्यों में अपरिहार्य व्यवहार होता है, लेकिन जब व्यवहार अलग होता है तो यह मदद नहीं करता है (मान लें कि मान या तो "त्रुटि संदेश" या डबल है)। मेरे काम में मैं प्रोटोकॉल से दूर हो सकता हूं। – sastanin

    +0

    @ सस्तानिन - यह दृष्टिकोण उन स्थितियों के लिए ठीक काम करेगा जहां मूल्य पूरी तरह से अलग प्रकार के होते हैं - आप प्रोटोकॉल को java.lang.String और java.lang पर अलग-अलग बढ़ा सकते हैं। उदाहरण के लिए दोहराएं। एकमात्र स्थिति यह काम नहीं करेगी, जहां आपको टाइप के अलावा किसी अन्य चीज़ पर प्रेषण करने की आवश्यकता है (लेकिन फिर आप ऊपर दिए गए उदाहरण में हमेशा रिकॉर्ड प्रकार में लपेट सकते हैं) – mikera

    4
    कुछ के पूरा होने के बिना

    मन उड़ाने typed clojure की तरह मुझे नहीं लगता कि आप ca एन assrtions की रनटाइम जांच से बचें।

    क्लोजर द्वारा प्रदान की जाने वाली एक कम ज्ञात सुविधा जो निश्चित रूप से रनटाइम चेक के साथ मदद कर सकती है, पूर्व और बाद की स्थितियों के कार्यान्वयन (http://clojure.org/special_forms और a blog post by fogus देखें)। मुझे लगता है कि आप प्रासंगिक कोड पर अपने सभी दावों की जांच के लिए पूर्व और बाद की स्थितियों के साथ एक उच्च आदेश रैपर फ़ंक्शन का भी उपयोग कर सकते हैं। यह रनटाइम चेक "प्रदूषण समस्या" से काफी अच्छी तरह से बचाता है।

    +0

    'pre' और': post' को इंगित करने के लिए धन्यवाद शर्तेँ। – sastanin

    +1

    कुछ साल बाद: टाइप किया गया क्लोजर अब यह सीधा बनाता है। https://github.com/clojure/core.typed –

    2

    टैग के साथ एक वेक्टर का उपयोग वेक्टर में पहले तत्व के रूप में करें और टैग किए गए डेटा को नष्ट करने के लिए core.match का उपयोग करें। इसलिए ऊपर के उदाहरण के लिए, "या तो" डेटा इनकोडिंग किया जाएगा के रूप में:

    [:left 123] 
    [:right "hello"] 
    

    करने के लिए तो destructure आप core.match और उपयोग का उल्लेख करने की आवश्यकता होगी:

    (match either 
        [:left num-val] (do-something-to-num num-val) 
        [:right str-val] (do-something-to-str str-val)) 
    

    यह अन्य की तुलना में अधिक संक्षिप्त है जवाब।

    This youtube talk नक्शा पर एन्कोडिंग वेरिएंट के लिए वेक्टर क्यों वांछनीय हैं, इसकी एक विस्तृत विस्तृत व्याख्या देता है। मेरा सारांश यह है कि वेरिएंट को एन्कोड करने के लिए नक्शे का उपयोग करना समस्याग्रस्त है क्योंकि आपको याद रखना है कि नक्शा एक "टैग किया गया मानचित्र" नियमित नक्शा नहीं है। एक "टैग किए गए मानचित्र" का उपयोग करने के लिए आपको हमेशा दो चरण लुकअप करना होगा: पहला टैग, फिर टैग के आधार पर डेटा। यदि (जब) आप मानचित्र-एन्कोडेड संस्करण में टैग को देखना भूल जाते हैं या टैग या डेटा के लिए मुख्य लुकअप गलत पाते हैं तो आपको एक नल पॉइंटर अपवाद मिलेगा जो ट्रैक करना मुश्किल है।

    • अवैध टैग फँसाने:

      वीडियो भी वेक्टर इनकोडिंग वेरिएंट के इन पहलुओं को शामिल किया गया।

    • Typed Clojure का उपयोग करके स्थिर जांच जोड़ना, वांछित।
    • Datomic में इस डेटा को संग्रहीत करना।