"मुक्त मोनाद + दुभाषिया" पैटर्न क्या है?


95

मैंने लोगों को इंटरप्रेन्योर के साथ फ्री मोनाड के बारे में बात करते देखा है , विशेष रूप से डेटा-एक्सेस के संदर्भ में। यह कैसा पैटर्न है? मैं इसका उपयोग कब करना चाहता हूं? यह कैसे काम करता है, और मैं इसे कैसे लागू करूंगा?

मैं समझता हूं ( इस तरह के पदों से ) यह डेटा-एक्सेस से मॉडल को अलग करने के बारे में है। यह प्रसिद्ध रिपोजिटरी पैटर्न से कैसे भिन्न है? वे एक ही प्रेरणा के लिए दिखाई देते हैं।

जवाबों:


138

वास्तविक पैटर्न वास्तव में सिर्फ डेटा एक्सेस की तुलना में काफी अधिक सामान्य है। यह एक डोमेन-विशिष्ट भाषा बनाने का एक हल्का तरीका है जो आपको एएसटी देता है, और फिर एएसटी को "निष्पादित" करने के लिए एक या एक से अधिक दुभाषियों का उपयोग करता है।

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

एक मुफ्त मोनाड का उपयोग करने से आपको एक कंपोज़ेबल डीएसएल की संरचना मिलती है ; आपको बस इतना करना है कि टुकड़ों को निर्दिष्ट करना है। आप बस एक डेटा टाइप लिखते हैं जो आपके डीएसएल में सभी कार्यों को शामिल करता है। ये क्रियाएं कुछ भी कर सकती हैं, न कि केवल डेटा एक्सेस। हालाँकि, यदि आपने अपने सभी डेटा एक्सेस को कार्रवाई के रूप में निर्दिष्ट किया है, तो आपको एक एएसटी मिलेगा जो डेटा स्टोर पर सभी प्रश्नों और कमांड को निर्दिष्ट करता है। आप इसे फिर भी पसंद कर सकते हैं, लेकिन इसे लाइव डेटाबेस के खिलाफ चलाएं, इसे मॉक के खिलाफ चलाएं, बस डिबगिंग के लिए कमांड को लॉग इन करें या प्रश्नों को अनुकूलित करने का प्रयास करें।

कहते हैं, एक प्रमुख मूल्य की दुकान के लिए एक बहुत ही सरल उदाहरण देखें। अभी के लिए, हम कुंजी और मान दोनों को स्ट्रिंग्स के रूप में मानेंगे, लेकिन आप थोड़े प्रयास के साथ प्रकार जोड़ सकते हैं।

data DSL next = Get String (String -> next)
              | Set String String next
              | End

nextपैरामीटर हमें कार्यों गठबंधन की सुविधा देता है। हम इसका उपयोग एक प्रोग्राम लिखने के लिए कर सकते हैं जो "फू" हो जाता है और उस मान के साथ "बार" सेट करता है:

p1 = Get "foo" $ \ foo -> Set "bar" foo End

दुर्भाग्य से, यह एक सार्थक डीएसएल के लिए पर्याप्त नहीं है। चूंकि हमने nextरचना के लिए उपयोग किया था , p1हमारे प्रोग्राम के समान लंबाई (यानी 3 कमांड) है:

p1 :: DSL (DSL (DSL next))

इस विशेष उदाहरण में, nextइस तरह का उपयोग करना थोड़ा अजीब लगता है, लेकिन यह महत्वपूर्ण है अगर हम चाहते हैं कि हमारे कार्यों में भिन्न प्रकार के चर हों। हम उदाहरण के लिए एक टाइप getऔर चाहते हैं set

ध्यान दें कि nextप्रत्येक क्रिया के लिए फ़ील्ड अलग कैसे है। यह संकेत देता है कि हम इसका उपयोग DSLफ़न बनाने वाले के लिए कर सकते हैं :

instance Functor DSL where
  fmap f (Get name k)          = Get name (f . k)
  fmap f (Set name value next) = Set name value (f next)
  fmap f End                   = End

वास्तव में, यह एक फ़नकार बनाने का एकमात्र वैध तरीका है, इसलिए हम एक्सटेंशन derivingको सक्षम करके स्वचालित रूप से उदाहरण बनाने के लिए उपयोग कर सकते हैं DeriveFunctor

अगला चरण Freeस्वयं प्रकार है। यही कारण है कि हम अपने एएसटी संरचना का प्रतिनिधित्व करने के लिए उपयोग करते हैं , DSLप्रकार के शीर्ष पर निर्माण करते हैं । आप इसे एक सूची की तरह प्रकार के स्तर पर सोच सकते हैं , जहां "विपक्ष" सिर्फ एक फ़नकार की तरह घोंसला बना रहा है DSL:

-- compare the two types:
data Free f a = Free (f (Free f a)) | Return a
data List a   = Cons a (List a)     | Nil

इसलिए हम Free DSL nextविभिन्न आकारों के कार्यक्रमों को एक ही प्रकार देने के लिए उपयोग कर सकते हैं :

p2 = Free (Get "foo" $ \ foo -> Free (Set "bar" foo (Free End)))

जिसके पास बहुत अच्छा प्रकार है:

p2 :: Free DSL a

हालांकि, इसके सभी निर्माणकर्ताओं के साथ वास्तविक अभिव्यक्ति अभी भी उपयोग करने के लिए बहुत अजीब है! यह वह जगह है जहाँ मोनाड भाग आता है। जैसा कि नाम "मुक्त मोनाद" का अर्थ है, Freeएक मोनाद है - जब तक f(इस मामले में DSL) एक फ़नकार है:

instance Functor f => Monad (Free f) where
  return         = Return
  Free a >>= f   = Free (fmap (>>= f) a)
  Return a >>= f = f a

अब हम कहीं जा रहे हैं: हम doअपने डीएसएल एक्सप्रेशन को अच्छा बनाने के लिए नोटेशन का उपयोग कर सकते हैं । एकमात्र सवाल यह है कि इसमें क्या रखा जाए next? ठीक है, विचार Freeसंरचना के लिए संरचना का उपयोग करना है , इसलिए हम बस Returnप्रत्येक अगले क्षेत्र के लिए डालेंगे और सभी प्लंबिंग को डू-नोटेशन करते हैं:

p3 = do foo <- Free (Get "foo" Return)
        Free (Set "bar" foo (Return ()))
        Free End

यह बेहतर है, लेकिन यह अभी भी थोड़ा अजीब है। हमारे पास Freeऔर Returnसभी जगह हैं। खुशी से, एक पैटर्न है जिसका हम शोषण कर सकते हैं: जिस तरह से हम एक डीएसएल कार्रवाई को "लिफ्ट" करते हैं Freeवह हमेशा एक ही होता है - हम इसे लपेटते हैं Freeऔर इसके Returnलिए आवेदन करते हैं next:

liftFree :: Functor f => f a -> Free f a
liftFree action = Free (fmap Return action)

अब, इसका उपयोग करते हुए, हम अपने प्रत्येक कमांड के अच्छे संस्करण लिख सकते हैं और एक पूर्ण डीएसएल है:

get key       = liftFree (Get key id)
set key value = liftFree (Set key value ())
end           = liftFree End

इसका उपयोग करते हुए, यहां बताया गया है कि हम अपना कार्यक्रम कैसे लिख सकते हैं:

p4 :: Free DSL a
p4 = do foo <- get "foo"
        set "bar" foo
        end

साफ-सुथरी चाल यह है कि जबकि p4यह थोड़ा जरूरी कार्यक्रम की तरह दिखता है, यह वास्तव में एक अभिव्यक्ति है जिसका मूल्य है

Free (Get "foo" $ \ foo -> Free (Set "bar" foo (Free End)))

तो, पैटर्न के मुक्त मोनाड भाग ने हमें एक डीएसएल प्राप्त किया है जो अच्छे सिंटैक्स के साथ सिंटैक्स पेड़ों का उत्पादन करता है। हम उपयोग न करके भी कंपोजिट सब-ट्रीज़ लिख सकते हैं End; उदाहरण के लिए, हमारे पास followएक कुंजी हो सकती है , उसका मूल्य मिलता है और फिर वह कुंजी के रूप में उपयोग करता है:

follow :: String -> Free DSL String
follow key = do key' <- get key
                get key'

अब followहमारे कार्यक्रमों में इस्तेमाल किया जा सकता है जैसे getया set:

p5 = do foo <- follow "foo"
        set "bar" foo
        end

तो हम अपने DSL के लिए कुछ अच्छी रचना और अमूर्तता प्राप्त करते हैं।

अब जब हमारे पास एक पेड़ है, तो हम पैटर्न के दूसरे भाग तक पहुंचते हैं: दुभाषिया। हम पेड़ की व्याख्या कर सकते हैं, हालांकि हम उस पर पैटर्न-मिलान करके पसंद करते हैं। यह हमें वास्तविक डेटा स्टोर के IOसाथ-साथ अन्य चीजों के खिलाफ कोड लिखने देगा । यहाँ एक काल्पनिक डेटा स्टोर के खिलाफ एक उदाहरण दिया गया है:

runIO :: Free DSL a -> IO ()
runIO (Free (Get key k)) =
  do res <- getKey key
     runIO $ k res
runIO (Free (Set key value next)) =
  do setKey key value
     runIO next
runIO (Free End) = close
runIO (Return _) = return ()

यह खुशी से किसी भी DSLटुकड़े का मूल्यांकन करेगा , यहां तक ​​कि एक के साथ समाप्त नहीं हुआ है end। खुशी से, हम फ़ंक्शन का "सुरक्षित" संस्करण बना सकते हैं जो केवल endइनपुट प्रकार के हस्ताक्षर को सेट करके बंद किए गए कार्यक्रमों को स्वीकार करता है (forall a. Free DSL a) -> IO ()। पुराने हस्ताक्षर एक को स्वीकार करता है, जबकि Free DSL aके लिए किसी भी a (जैसे Free DSL String, Free DSL Intऔर इसी तरह), इस संस्करण केवल एक को स्वीकार करता है Free DSL aकि के लिए काम करता हर संभव a-which हम केवल के साथ बना सकते हैं end। यह गारंटी देता है कि जब हम काम पूरा करेंगे तो हम कनेक्शन बंद करना नहीं भूलेंगे।

safeRunIO :: (forall a. Free DSL a) -> IO ()
safeRunIO = runIO

(हम केवल runIOइस प्रकार को देकर शुरू नहीं कर सकते क्योंकि यह हमारे पुनरावर्ती कॉल के लिए ठीक से काम नहीं करेगा। हालांकि, हम फ़ंक्शन के दोनों संस्करणों को उजागर किए बिना runIOएक whereब्लॉक में परिभाषा को स्थानांतरित कर सकते हैं safeRunIOऔर समान प्रभाव प्राप्त कर सकते हैं।)

हमारे कोड को चलाना IOकेवल वही काम नहीं है जो हम कर सकते हैं। परीक्षण के लिए, हम इसे शुद्ध के State Mapबजाय चलाना चाह सकते हैं । उस कोड को लिखना एक अच्छा व्यायाम है।

तो यह मुफ्त मोनाड + दुभाषिया पैटर्न है। हम एक डीएसएल बनाते हैं, जो सभी प्लंबिंग करने के लिए मुफ्त मोनाड संरचना का लाभ उठाते हैं। हम अपने डीएसएल के साथ डू-नोटेशन और मानक मोनड कार्यों का उपयोग कर सकते हैं। फिर, वास्तव में इसका उपयोग करने के लिए, हमें किसी तरह इसकी व्याख्या करनी होगी; चूंकि पेड़ अंततः केवल एक डेटा संरचना है, हम इसकी व्याख्या कर सकते हैं, हालांकि हम विभिन्न उद्देश्यों के लिए पसंद करते हैं।

जब हम बाहरी डेटा स्टोर तक पहुंच का प्रबंधन करने के लिए इसका उपयोग करते हैं, तो यह वास्तव में रिपॉजिटरी पैटर्न के समान है। यह हमारे डेटा स्टोर और हमारे कोड के बीच में अंतर करता है, दोनों को अलग करता है। कुछ मायनों में, हालांकि, यह अधिक विशिष्ट है: "रिपॉजिटरी" हमेशा एक स्पष्ट एएसटी के साथ एक डीएसएल होता है जिसे हम तब उपयोग कर सकते हैं जो हमें पसंद है।

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


6
इसे 'मुक्त' संन्यासी क्यों कहा जाता है?
बेंजामिन हॉजसन

14
"नि: शुल्क" नाम श्रेणी के सिद्धांत से आता है: ncatlab.org/nlab/show/free+object लेकिन इसका मतलब यह है कि यह "न्यूनतम" मोनड है - कि इस पर केवल वैध संचालन ही मोनड ऑपरेशन हैं, जैसा कि यह है " भूल गया "यह सब अन्य संरचना है।
बॉयड स्टीफन स्मिथ जूनियर

3
@BenjaminHodgson: बॉयड पूरी तरह से सही है। मैं इसके बारे में बहुत ज्यादा चिंता नहीं करूंगा जब तक कि आप सिर्फ उत्सुक न हों। डैन पिपोनी ने बेहाक में "मुक्त" का क्या अर्थ है, इस बारे में एक शानदार बात की , जो देखने लायक है। उसकी स्लाइड के साथ अनुसरण करने का प्रयास करें क्योंकि वीडियो में दृश्य पूरी तरह से बेकार है।
तिकोन जेल्विस

3
ए नाइटपिक: "मुक्त मोनाड भाग सिर्फ [मेरा जोर] एएसटी प्राप्त करने का एक आसान तरीका है जिसे आप कस्टम कोड के बहुत सारे लिखने के बिना हास्केल के मानक मोनड सुविधाओं (जैसे-नोटेशन) का उपयोग करके इकट्ठा कर सकते हैं।" यह "सिर्फ" से अधिक है (जैसा कि मैं निश्चित हूं कि आप जानते हैं)। नि: शुल्क मठ भी एक सामान्यीकृत कार्यक्रम प्रतिनिधित्व है जो दुभाषिया के लिए उन कार्यक्रमों के बीच अंतर करना असंभव बनाता है जिनके do-नोटेशन अलग है लेकिन वास्तव में "एक ही मतलब है।"
sacundim

5
@sacundim: क्या आप अपनी टिप्पणी पर विस्तार से बता सकते हैं? विशेष रूप से वाक्य 'फ्री मोनैड्स भी एक सामान्यीकृत प्रोग्राम प्रतिनिधित्व है जो दुभाषिया के लिए उन प्रोग्रामों के बीच अंतर करना असंभव बनाता है, जिनका डू-नोटेशन अलग है लेकिन वास्तव में "एक ही मतलब है।"'।
जियोर्जियो

15

एक मुक्त मोनाड मूल रूप से एक ऐसा सन्यासी है जो किसी भी चीज़ को अधिक जटिल करने के बजाय गणना के रूप में उसी "आकार" में एक डेटा संरचना बनाता है। ( ऑनलाइन पाए जाने वाले उदाहरण हैं। ) यह डेटा संरचना तब कोड के एक टुकड़े को पारित कर दी जाती है, जो इसका उपभोग करता है और संचालन को पूरा करता है। * मैं रिपॉजिटरी पैटर्न से पूरी तरह परिचित नहीं हूं, लेकिन जो मैंने पढ़ा है, वह इससे प्रकट होता है। एक उच्च स्तरीय वास्तुकला और इसे लागू करने के लिए एक मुफ्त मोनाड + दुभाषिया का उपयोग किया जा सकता है। दूसरी ओर, मुफ्त मोनाड + दुभाषिया का उपयोग पूरी तरह से अलग चीजों को लागू करने के लिए भी किया जा सकता है, जैसे कि पार्सर।

* यह ध्यान देने योग्य है कि यह पैटर्न मोनाड्स के लिए अनन्य नहीं है, और वास्तव में मुफ्त ऐप्लिकेशंस, या फ्री एरो के साथ अधिक कुशल कोड का उत्पादन कर सकता है । ( पर्सर्स इसका एक और उदाहरण हैं। )


क्षमा याचना, मैं रिपोजिटरी के बारे में स्पष्ट होना चाहिए था। (मैं भूल गया कि हर किसी के पास एक व्यापार प्रणाली / OO / DDD पृष्ठभूमि नहीं है!) एक रिपॉजिटरी मूल रूप से डेटा एक्सेस को एन्क्रिप्ट करती है और आपके लिए डोमेन ऑब्जेक्ट्स को रिहाइड्रेट करती है। यह अक्सर डिपेंडेंसी इनवर्जन के साथ उपयोग किया जाता है - आप रेपो (परीक्षण के लिए उपयोगी, या यदि आपको डेटाबेस या ओआरएम को स्विच करने की आवश्यकता है) के विभिन्न कार्यान्वयनों में 'प्लग इन' कर सकते हैं। डोमेन कोड सिर्फ यह repository.Get()बताता है कि उसे डोमेन ऑब्जेक्ट कहां से मिल रहा है।
बेंजामिन हॉजसन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.