पैटर्न मिलान को समझने के लिए तीन भागों की व्याख्या की आवश्यकता होती है:
- बीजीय डेटा प्रकार।
- क्या पैटर्न मिलान है
- क्यों इसका कमाल
संक्षेप में बीजीय डेटा प्रकार
एमएल-जैसी कार्यात्मक भाषाएँ आपको सरल डेटा प्रकारों को परिभाषित करने की अनुमति देती हैं जिन्हें "असमान संघ" या "बीजीय डेटा प्रकार" कहा जाता है। ये डेटा संरचनाएं सरल कंटेनर हैं, और इसे पुनरावर्ती रूप से परिभाषित किया जा सकता है। उदाहरण के लिए:
type 'a list =
| Nil
| Cons of 'a * 'a list
एक स्टैक जैसी डेटा संरचना को परिभाषित करता है। इसे इस C # के बराबर समझें:
public abstract class List<T>
{
public class Nil : List<T> { }
public class Cons : List<T>
{
public readonly T Item1;
public readonly List<T> Item2;
public Cons(T item1, List<T> item2)
{
this.Item1 = item1;
this.Item2 = item2;
}
}
}
इसलिए, Cons
और Nil
पहचानकर्ता सरल को एक साधारण वर्ग of x * y * z * ...
परिभाषित करते हैं, जहां एक निर्माता और कुछ डेटा प्रकारों को परिभाषित करता है। निर्माता के पैरामीटर अनाम हैं, वे स्थिति और डेटा प्रकार से पहचाने जाते हैं।
आप अपनी a list
कक्षा के उदाहरण इस प्रकार बनाएँ :
let x = Cons(1, Cons(2, Cons(3, Cons(4, Nil))))
जो निम्नानुसार है:
Stack<int> x = new Cons(1, new Cons(2, new Cons(3, new Cons(4, new Nil()))));
संक्षेप में पैटर्न का मिलान
पैटर्न मिलान एक प्रकार का परीक्षण है। तो मान लें कि हमने ऊपर की तरह एक स्टैक ऑब्जेक्ट बनाया है, हम स्टैक को स्लाइड करने और पॉप को निम्न प्रकार से लागू कर सकते हैं:
let peek s =
match s with
| Cons(hd, tl) -> hd
| Nil -> failwith "Empty stack"
let pop s =
match s with
| Cons(hd, tl) -> tl
| Nil -> failwith "Empty stack"
ऊपर दिए गए तरीके समतुल्य हैं (हालाँकि ऐसा लागू नहीं किया गया है) निम्नलिखित C #:
public static T Peek<T>(Stack<T> s)
{
if (s is Stack<T>.Cons)
{
T hd = ((Stack<T>.Cons)s).Item1;
Stack<T> tl = ((Stack<T>.Cons)s).Item2;
return hd;
}
else if (s is Stack<T>.Nil)
throw new Exception("Empty stack");
else
throw new MatchFailureException();
}
public static Stack<T> Pop<T>(Stack<T> s)
{
if (s is Stack<T>.Cons)
{
T hd = ((Stack<T>.Cons)s).Item1;
Stack<T> tl = ((Stack<T>.Cons)s).Item2;
return tl;
}
else if (s is Stack<T>.Nil)
throw new Exception("Empty stack");
else
throw new MatchFailureException();
}
(लगभग हमेशा, एमएल भाषाएँ रन-टाइम टाइप-टेस्ट या कास्ट के बिना पैटर्न के मेल को लागू करती हैं, इसलिए C # कोड कुछ हद तक भ्रामक है। आइए कार्यान्वयन के विवरणों को कुछ हाथ से लहराते हुए कृपया ब्रश करें :)
संक्षेप में डेटा संरचना का अपघटन
ठीक है, चलिए वापस चलते हैं:
let peek s =
match s with
| Cons(hd, tl) -> hd
| Nil -> failwith "Empty stack"
चाल समझ रही है कि hd
और tl
पहचानकर्ता चर हैं (ग़लती से ... चूंकि वे अपरिवर्तनीय हैं, वे वास्तव में "चर" नहीं हैं, लेकिन "मूल्य";))। यदि s
प्रकार है Cons
, तो हम इसके मानों को कंस्ट्रक्टर से बाहर निकालने जा रहे हैं और उन्हें नामांकित चर में बाँधेंगे hd
और tl
।
पैटर्न मिलान उपयोगी है क्योंकि यह हमें इसकी सामग्री के बजाय इसके आकार द्वारा डेटा संरचना को विघटित करने देता है । तो कल्पना करें कि क्या हम एक बाइनरी ट्री को निम्नानुसार परिभाषित करते हैं:
type 'a tree =
| Node of 'a tree * 'a * 'a tree
| Nil
हम कुछ पेड़ों के घुमाव को इस प्रकार परिभाषित कर सकते हैं :
let rotateLeft = function
| Node(a, p, Node(b, q, c)) -> Node(Node(a, p, b), q, c)
| x -> x
let rotateRight = function
| Node(Node(a, p, b), q, c) -> Node(a, p, Node(b, q, c))
| x -> x
( let rotateRight = function
कंस्ट्रक्टर के लिए सिंटैक्स चीनी है let rotateRight s = match s with ...
।)
इसलिए डेटा संरचना को चरों से बांधने के अलावा, हम इसमें ड्रिल भी कर सकते हैं। मान लीजिए कि हमारे पास एक नोड है let x = Node(Nil, 1, Nil)
। यदि हम कहते हैं rotateLeft x
, तो हम x
पहले पैटर्न के खिलाफ परीक्षण करते हैं, जो कि मेल करने में विफल रहता है क्योंकि सही बच्चे के Nil
बजाय टाइप होता है Node
। यह अगले पैटर्न में चला x -> x
जाएगा, जो किसी भी इनपुट से मेल खाएगा और इसे बिना लाइसेंस वापस कर देगा।
तुलना के लिए, हम ऊपर दिए गए तरीकों को C # में लिखेंगे:
public abstract class Tree<T>
{
public abstract U Match<U>(Func<U> nilFunc, Func<Tree<T>, T, Tree<T>, U> nodeFunc);
public class Nil : Tree<T>
{
public override U Match<U>(Func<U> nilFunc, Func<Tree<T>, T, Tree<T>, U> nodeFunc)
{
return nilFunc();
}
}
public class Node : Tree<T>
{
readonly Tree<T> Left;
readonly T Value;
readonly Tree<T> Right;
public Node(Tree<T> left, T value, Tree<T> right)
{
this.Left = left;
this.Value = value;
this.Right = right;
}
public override U Match<U>(Func<U> nilFunc, Func<Tree<T>, T, Tree<T>, U> nodeFunc)
{
return nodeFunc(Left, Value, Right);
}
}
public static Tree<T> RotateLeft(Tree<T> t)
{
return t.Match(
() => t,
(l, x, r) => r.Match(
() => t,
(rl, rx, rr) => new Node(new Node(l, x, rl), rx, rr))));
}
public static Tree<T> RotateRight(Tree<T> t)
{
return t.Match(
() => t,
(l, x, r) => l.Match(
() => t,
(ll, lx, lr) => new Node(ll, lx, new Node(lr, x, r))));
}
}
गंभीरता के लिए।
पैटर्न का मेल कमाल का है
आप विज़िटर पैटर्न का उपयोग करके C # में मेल खाने वाले पैटर्न के समान कुछ लागू कर सकते हैं , लेकिन यह लगभग उतना लचीला नहीं है क्योंकि आप जटिल डेटा संरचनाओं को प्रभावी ढंग से विघटित नहीं कर सकते हैं। इसके अलावा, यदि आप पैटर्न मिलान का उपयोग कर रहे हैं, तो संकलक आपको बताएगा कि आपने कोई केस छोड़ा है या नहीं । कितना भयानक है?
इस बारे में सोचें कि आप पैटर्न मिलान के बिना C # या भाषाओं में समान कार्यक्षमता कैसे लागू करेंगे। इस बात पर विचार करें कि रनटाइम के दौरान आप इसे बिना टेस्ट-टेस्ट और कास्ट के कैसे करेंगे। यह निश्चित रूप से कठिन नहीं है , बस बोझिल और भारी है। और आपके पास यह सुनिश्चित करने के लिए संकलक जाँच नहीं है कि आपने हर मामले को कवर किया है।
इसलिए पैटर्न मिलान आपको डेटा संरचनाओं को बहुत सुविधाजनक, कॉम्पैक्ट सिंटैक्स में विघटित और नेविगेट करने में मदद करता है, यह कंपाइलर को आपके कोड के तर्क की जांच करने में सक्षम बनाता है , कम से कम थोड़ा सा। यह वास्तव में एक हत्यारा सुविधा है।