প্রকৃত প্যাটার্নটি কেবলমাত্র ডেটা অ্যাক্সেসের চেয়ে উল্লেখযোগ্যভাবে সাধারণ। এটি একটি ডোমেন-নির্দিষ্ট ভাষা তৈরির একটি হালকা উপায় যা আপনাকে একটি এএসটি দেয় এবং তারপরে আপনার পছন্দ মতো এএসটি "চালানো" করার জন্য এক বা একাধিক দোভাষী করে।
বিনামূল্যে মোনাড অংশটি একটি এএসটি পাওয়ার সহজ উপায় যা আপনি প্রচুর কাস্টম কোড না লিখেই হাস্কেলের স্ট্যান্ডার্ড মোনাড সুবিধাগুলি (ডু-নোটেশন) ব্যবহার করে জড়ো করতে পারেন। এটি আপনার ডিএসএলটি কমপোজযোগ্য তাও নিশ্চিত করে : আপনি এটিকে কিছু অংশে সংজ্ঞায়িত করতে পারেন এবং তারপরে অংশগুলি একটি কাঠামোগত উপায়ে রাখতে পারেন, যাতে আপনাকে ফাংশনের মতো হাস্কেলের স্বাভাবিক বিমূর্তিগুলির সুবিধা নিতে দেয়।
একটি নিখরচায় মোনাদ ব্যবহার আপনাকে একটি কম্পোজেবল ডিএসএল এর কাঠামো দেয় ; আপনাকে যা করতে হবে তা হ'ল টুকরোগুলি নির্দিষ্ট করে specify আপনি কেবল একটি ডেটা টাইপ লিখুন যা আপনার ডিএসএল-এর সমস্ত ক্রিয়াকে অন্তর্ভুক্ত করে। এই ক্রিয়াগুলি কেবল ডেটা অ্যাক্সেস নয়, কিছু করতে পারে। তবে, আপনি যদি আপনার সমস্ত ডেটা অ্যাক্সেসগুলি ক্রিয়া হিসাবে নির্দিষ্ট করে থাকেন তবে আপনি একটি এএসটি পাবেন যা ডেটা স্টোরটিতে সমস্ত প্রশ্ন এবং আদেশগুলি নির্দিষ্ট করে। এরপরে আপনি নিজের পছন্দ মতো এটি ব্যাখ্যা করতে পারেন: লাইভ ডাটাবেসের বিরুদ্ধে এটি চালান, একটি উপহাসের বিরুদ্ধে চালান, কেবল ডিবাগিংয়ের জন্য কমান্ডগুলি লগ করুন বা কোয়েরিগুলি অনুকূলিত করার চেষ্টা করুন।
একটি মূল মান স্টোর এর জন্য খুব সাধারণ উদাহরণটি দেখুন। আপাতত, আমরা কেবল কী এবং মান উভয়কেই স্ট্রিং হিসাবে ব্যবহার করব, তবে আপনি কিছুটা চেষ্টা করে প্রকারগুলি যুক্ত করতে পারেন।
data DSL next = Get String (String -> next)
| Set String String next
| End
next
পরামিতি আমাদের কর্ম একত্রিত করতে দেয়। আমরা এটি এমন একটি প্রোগ্রাম লিখতে ব্যবহার করতে পারি যা "foo" পায় এবং সেই মানটির সাথে "বার" সেট করে:
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
যেমন বোঝা যাচ্ছে, ততক্ষণ এটি একটি মোনাড as যতক্ষণ 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
সুতরাং আমরা আমাদের ডিএসএল এর জন্যও কিছু সুন্দর রচনা এবং বিমূর্ততা পেয়েছি।
এখন আমাদের একটি গাছ আছে, আমরা প্যাটার্নটির দ্বিতীয়ার্ধে পৌঁছাই: দোভাষী। আমরা গাছটিকে কেবলমাত্র প্যাটার্ন-ম্যাচ করে পছন্দ করতে পারি। এটি আমাদের আসল ডেটা স্টোরের 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
-যেটা আমরা কেবল সঙ্গে তৈরি করতে পারেন end
। এটি গ্যারান্টি দেয় যে আমাদের কাজ শেষ হয়ে গেলে আমরা সংযোগটি বন্ধ করতে ভুলব না।
safeRunIO :: (forall a. Free DSL a) -> IO ()
safeRunIO = runIO
(আমরা কেবল runIO
এই ধরণেরটি দিয়ে শুরু করতে পারি না কারণ এটি আমাদের পুনরাবৃত্ত কলের জন্য সঠিকভাবে কাজ করবে না However তবে, আমরা সংজ্ঞাটিকে runIO
একটি where
ব্লকের মধ্যে স্থানান্তর করতে safeRunIO
এবং ফাংশনের উভয় সংস্করণ প্রকাশ না করে একই প্রভাব পেতে পারি))
আমাদের কোড চালানো IO
কেবলমাত্র আমরা যা করতে পারি তা নয়। পরীক্ষার জন্য, আমরা এটির State Map
পরিবর্তে খাঁটির বিরুদ্ধে চালাতে চাই । কোডটি লেখা ভাল অনুশীলন।
সুতরাং এটি ফ্রি মোনাড + ইন্টারপ্রেটার প্যাটার্ন। সমস্ত নদীর গভীরতানির্ণয় করার জন্য ফ্রি মনাদ কাঠামোর সুযোগ নিয়ে আমরা একটি ডিএসএল তৈরি করি। আমরা আমাদের ডিএসএলের সাথে ড-নোটেশন এবং স্ট্যান্ডার্ড মোনাড ফাংশন ব্যবহার করতে পারি। তারপরে, আসলে এটি ব্যবহার করতে, আমাদের এটি কোনওভাবে ব্যাখ্যা করতে হবে; যেহেতু গাছটি শেষ পর্যন্ত কেবলমাত্র একটি ডেটা কাঠামো, তাই আমরা এটির ব্যাখ্যা করতে পারি তবে আমরা বিভিন্ন উদ্দেশ্যে পছন্দ করি।
যখন আমরা এটি কোনও বাহ্যিক ডেটা স্টোরের অ্যাক্সেসগুলি পরিচালনা করতে ব্যবহার করি, এটি প্রকৃতপক্ষে সংগ্রহস্থল প্যাটার্নের অনুরূপ। এটি আমাদের ডেটা স্টোর এবং আমাদের কোডের মধ্যে মধ্যস্থতা করে, দুটিকে আলাদা করে। যদিও কিছু উপায়ে এটি আরও সুনির্দিষ্ট: "সংগ্রহশালা" হ'ল একটি স্পষ্টত এএসটি সহ একটি ডিএসএল যা আমরা তারপরে আমাদের পছন্দ মতো ব্যবহার করতে পারি।
তবে, প্যাটার্নটি নিজেই এর চেয়ে বেশি সাধারণ। এটি প্রচুর পরিমাণে ব্যবহার করা যেতে পারে যা বাহ্যিক ডাটাবেস বা স্টোরেজ অগত্যা জড়িত না। আপনি যেখানে কোনও ডিএসএল এর জন্য প্রভাব বা একাধিক লক্ষ্যবস্তুগুলির সূক্ষ্ম নিয়ন্ত্রণ চান সেখানে এটি বোধগম্য হয়।