वास्तविक पैटर्न वास्तव में सिर्फ डेटा एक्सेस की तुलना में काफी अधिक सामान्य है। यह एक डोमेन-विशिष्ट भाषा बनाने का एक हल्का तरीका है जो आपको एएसटी देता है, और फिर एएसटी को "निष्पादित" करने के लिए एक या एक से अधिक दुभाषियों का उपयोग करता है।
मुक्त मोनाड भाग एक एएसटी प्राप्त करने का एक आसान तरीका है जिसे आप बहुत सारे कस्टम कोड लिखने के बिना हास्केल के मानक मोनाड सुविधाओं (जैसे-नोटेशन) का उपयोग करके इकट्ठा कर सकते हैं। यह भी सुनिश्चित करता है कि आपका डीएसएल कंपोजिटेबल है : आप इसे भागों में परिभाषित कर सकते हैं और फिर भागों को एक संरचित तरीके से एक साथ रख सकते हैं, जिससे आप हास्केल के सामान्य अमूर्त कार्यों का लाभ उठा सकते हैं।
एक मुफ्त मोनाड का उपयोग करने से आपको एक कंपोज़ेबल डीएसएल की संरचना मिलती है ; आपको बस इतना करना है कि टुकड़ों को निर्दिष्ट करना है। आप बस एक डेटा टाइप लिखते हैं जो आपके डीएसएल में सभी कार्यों को शामिल करता है। ये क्रियाएं कुछ भी कर सकती हैं, न कि केवल डेटा एक्सेस। हालाँकि, यदि आपने अपने सभी डेटा एक्सेस को कार्रवाई के रूप में निर्दिष्ट किया है, तो आपको एक एएसटी मिलेगा जो डेटा स्टोर पर सभी प्रश्नों और कमांड को निर्दिष्ट करता है। आप इसे फिर भी पसंद कर सकते हैं, लेकिन इसे लाइव डेटाबेस के खिलाफ चलाएं, इसे मॉक के खिलाफ चलाएं, बस डिबगिंग के लिए कमांड को लॉग इन करें या प्रश्नों को अनुकूलित करने का प्रयास करें।
कहते हैं, एक प्रमुख मूल्य की दुकान के लिए एक बहुत ही सरल उदाहरण देखें। अभी के लिए, हम कुंजी और मान दोनों को स्ट्रिंग्स के रूप में मानेंगे, लेकिन आप थोड़े प्रयास के साथ प्रकार जोड़ सकते हैं।
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
बजाय चलाना चाह सकते हैं । उस कोड को लिखना एक अच्छा व्यायाम है।
तो यह मुफ्त मोनाड + दुभाषिया पैटर्न है। हम एक डीएसएल बनाते हैं, जो सभी प्लंबिंग करने के लिए मुफ्त मोनाड संरचना का लाभ उठाते हैं। हम अपने डीएसएल के साथ डू-नोटेशन और मानक मोनड कार्यों का उपयोग कर सकते हैं। फिर, वास्तव में इसका उपयोग करने के लिए, हमें किसी तरह इसकी व्याख्या करनी होगी; चूंकि पेड़ अंततः केवल एक डेटा संरचना है, हम इसकी व्याख्या कर सकते हैं, हालांकि हम विभिन्न उद्देश्यों के लिए पसंद करते हैं।
जब हम बाहरी डेटा स्टोर तक पहुंच का प्रबंधन करने के लिए इसका उपयोग करते हैं, तो यह वास्तव में रिपॉजिटरी पैटर्न के समान है। यह हमारे डेटा स्टोर और हमारे कोड के बीच में अंतर करता है, दोनों को अलग करता है। कुछ मायनों में, हालांकि, यह अधिक विशिष्ट है: "रिपॉजिटरी" हमेशा एक स्पष्ट एएसटी के साथ एक डीएसएल होता है जिसे हम तब उपयोग कर सकते हैं जो हमें पसंद है।
हालांकि, पैटर्न खुद ही इससे अधिक सामान्य है। इसका उपयोग बहुत सारी चीजों के लिए किया जा सकता है जो जरूरी नहीं कि बाहरी डेटाबेस या भंडारण में शामिल हों। यह समझ में आता है कि आप जहां भी चाहते हैं प्रभाव को ठीक करते हैं या एक डीएसएल के लिए कई लक्ष्य बनाते हैं।