प्रोग्रामिंग के कुछ क्षेत्रों में, एक डेटा संरचना या एल्गोरिदम लिखना चाहता है जो विभिन्न प्रकार के तत्वों के साथ काम कर सकता है। उदाहरण के लिए, जेनरिक या सॉर्टिंग एल्गोरिथ्म की एक सूची जिसमें केवल तुलनात्मक फ़ंक्शन की आवश्यकता होती है। विभिन्न भाषाओं में, इस समस्या को हल करने के विभिन्न तरीकों की पेशकश की जाती है: केवल प्रोग्रामर को उचित सामान्य कार्यों (C, Go) को इंगित करने से लेकर ऐसे शक्तिशाली जेनरिक सिस्टम तक कि वे ट्यूरिंग पूर्ण (
रस्ट ,
C ++ ) बन जाते हैं। इस लेख में मैं विभिन्न भाषाओं और उनके कार्यान्वयन से सामान्य प्रणालियों के बारे में बात करूंगा। मैं इस तरह की प्रणाली (जैसे सी) के बिना भाषाओं में समस्या को हल करने से शुरू करूंगा, और फिर मैं दिखाऊंगा कि कैसे विस्तार के क्रमिक जोड़ अन्य भाषाओं से सिस्टम की ओर जाते हैं।
मुझे लगता है कि जेनरिक एक दिलचस्प विकल्प है क्योंकि वे सामान्य मेटाप्रोग्रामिंग कार्य का एक सरल विशेष मामला है: ऐसे प्रोग्राम लिखना जो अन्य कार्यक्रमों के वर्ग उत्पन्न कर सकते हैं। प्रमाण के रूप में, मैं दिखाऊंगा कि कैसे तीन अलग-अलग और पूरी तरह से सामान्य मेटाप्रोग्रामिंग विधियों को जेनेरिक प्रणालियों के अंतरिक्ष में बहु-दिशात्मक विस्तार माना जा सकता है: पायथन जैसी गतिशील भाषाएं,
टेम्पलेट हास्केल जैसी प्रक्रियात्मक मैक्रो सिस्टम
, और
जिग और
टेरा जैसे चरण संकलन।
सिंहावलोकन
मैंने लेख में वर्णित सभी प्रणालियों का एक ब्लॉक आरेख खींचा ताकि आप इसकी सामग्री प्रस्तुत कर सकें और ये प्रणालियाँ कैसे परस्पर जुड़ी हैं:

मुख्य विचार
मान लीजिए कि हम सामान्य प्रणालियों के बिना एक भाषा में लिख रहे हैं, और हम एक सामान्य स्टैक डेटा संरचना डेटा संरचना बनाना चाहते हैं जो किसी भी प्रकार के डेटा के साथ काम करती है। समस्या यह है कि प्रत्येक फ़ंक्शन और प्रकार की परिभाषा केवल एक ही आकार के डेटा के साथ काम करेगी और एक तरह से कॉपी की जाएगी, और आम तौर पर इसी तरह काम करेगी।
इसके आसपास पहुंचने के दो तरीके हैं: या तो यह सुनिश्चित करें कि सभी डेटा प्रकार हमारी संरचना में उसी तरह से कार्य करते हैं, या प्रत्येक डेटा प्रकार के साथ सही ढंग से काम करने के लिए मामूली बदलाव के साथ डेटा संरचना की कई प्रतियां बनाते हैं। इन विचारों ने जेनेरिक के साथ समाधान के दो बड़े समूहों का आधार बनाया: मुक्केबाजी और मोनोमोर्फाइजेशन।
पैकेजिंग का मतलब है कि एक पंक्ति में सब कुछ एकीकृत "बक्से" में डालना जो उसी तरह काम करते हैं। यह आमतौर पर इस तरह से किया जाता है: डेटा को एक ढेर में रखा जाता है, और इसके लिए संकेत डेटा संरचना में रखे जाते हैं। आप सभी प्रकार के पॉइंटर्स बना सकते हैं जो एक ही तरह से काम करेंगे, इसलिए समान कोड किसी भी प्रकार के डेटा के साथ काम करेगा! हालाँकि, यह मेमोरी की खपत, डायनेमिक खोज और कैश मिस को बढ़ाता है। C में, इसका मतलब है कि आपकी डेटा संरचना
void*
पॉइंटर्स को स्टोर करती है और
void*
से डेटा को बस कैश करती है (यदि डेटा ढेर पर नहीं है, तो यह उन्हें वहां रखता है)।
मोनोमोर्फिफ़िकेशन का अर्थ है विभिन्न प्रकार के डेटा के लिए बार-बार कोड की प्रतिलिपि बनाना जिसे हम संग्रहीत करना चाहते हैं। फिर प्रत्येक कोड उदाहरण सीधे आकार और डेटा विधियों का उपयोग कर सकता है जो यह बिना गतिशील खोज के साथ काम करता है। इस दृष्टिकोण के साथ, कोड सबसे तेज़ चलता है, लेकिन इसका आकार और संकलन समय बढ़ता है, क्योंकि हम बार-बार एक ही कोड को मामूली परिवर्तनों के साथ संकलित करते हैं। सी में, यह
संपूर्ण डेटा संरचना की
परिभाषा से मेल खाती है, इसके बाद प्रत्येक डेटा प्रकार के लिए इसके आह्वान के बाद।
सामान्य तौर पर, संकलन के दौरान, कोड तेजी से संकलित होता है, लेकिन निष्पादन के दौरान इसका प्रदर्शन बिगड़ सकता है, जबकि मोनोमोर्फाइजेशन के दौरान हम तेजी से कोड उत्पन्न करते हैं, लेकिन कोड के सभी उदाहरणों को संकलित और अनुकूलित करने में अधिक समय लगता है। एक और अंतर यह है कि जब पैकेजिंग एक्सटेंशन आपको निष्पादन योग्य कोड का अधिक गतिशील व्यवहार करने की अनुमति देते हैं, और मोनोमोर्फिफ़िकेशन आपको सामान्य रूप से जेनेरिक कोड के अलग-अलग उदाहरणों को अलग करने की अनुमति देता है। यह भी ध्यान देने योग्य है कि कुछ बड़े कार्यक्रमों में, उत्पन्न कोड से अतिरिक्त निर्देशों के कैश में मिसाइलों द्वारा मोनोमोर्फाइजेशन के लाभों को ऑफसेट किया जा सकता है।
जेनरिक के साथ काम करने के लिए वर्णित योजनाओं में से प्रत्येक को अलग-अलग दिशाओं में विस्तारित किया जा सकता है, अगर आपको अधिक सुविधाओं या सुरक्षा की आवश्यकता है, और विभिन्न भाषाओं के लेखक बहुत दिलचस्प समाधान लेकर आए हैं। उदाहरण के लिए, दोनों दृष्टिकोणों का उपयोग Rust और C # में किया जा सकता है!
पैकिंग
आइए, गो में मूल पैकेजिंग के एक उदाहरण से शुरू करें:
type Stack struct { values []interface{} } func (this *Stack) Push(value interface{}) { this.values = append(this.values, value) } func (this *Stack) Pop() interface{} { x := this.values[len(this.values)-1] this.values = this.values[:len(this.values)-1] return x }
इसके अलावा, पैकेजिंग का उपयोग C (
void*
), गो (
interface{}
), प्री-जेनेरिक जावा (
Object
) और प्री-जेनेरिक ऑब्जेक्टिव-सी (
id
) में किया जाता है।
पैकिंग प्रकार के साथ पैक जेनरिक
मुख्य पैकेजिंग विधि में नुकसान हैं:
- भाषा पर निर्भर करते हुए, हमें अक्सर डेटा संरचना में पढ़ने या लिखने के लिए हर बार सही प्रकार से या उससे मान डालना पड़ता है।
- कुछ भी हमें विभिन्न प्रकार के तत्वों को संरचना में डालने से रोकता है, जो कोड निष्पादन के दौरान क्रैश जैसी दिखने वाली बग को भड़का सकते हैं।
कोड निष्पादन के दौरान पहले की तरह ही मुख्य पैकेजिंग विधि का उपयोग करते हुए, दोनों प्रकार की कार्यक्षमता में जेनेरिक को जोड़कर दोनों समस्याओं को हल किया जा सकता है। इस दृष्टिकोण को अक्सर टाइप इरेज़र कहा जाता है, क्योंकि जेनेरिक सिस्टम में टाइप ओवरराइट हो जाते हैं और हुड के नीचे एक प्रकार बन जाते हैं (जैसे
Object
)।
जावा और ऑब्जेक्टिव-सी ने सामान्य पैकेजिंग के साथ शुरू किया, और बाद में अनुकूलता के लिए टाइपिंग के लिए जेनेरिक के लिए भाषा उपकरण का अधिग्रहण किया, पहले के समान संग्रह प्रकारों का उपयोग करके, लेकिन जेनेरिक प्रकारों के वैकल्पिक मापदंडों के साथ।
जावा में
जेनरिक के बारे में विकिपीडिया लेख के एक उदाहरण पर विचार करें:
List v = new ArrayList(); v.add("test");
यूनिफाइड परफॉरमेंस के साथ पैकेज्ड जेनरिक
OCaml आगे एक एकीकृत दृश्य के विचार को विकसित करता है। ऐसे कोई भी आदिम प्रकार नहीं हैं, जिन्हें अतिरिक्त पैकेजिंग प्लेसमेंट की आवश्यकता हो (जैसा कि जावा में एक
ArrayList
में जाने के लिए
int
को
Integer
में बदलना चाहिए), क्योंकि सबकुछ पहले से ही पैक किया गया है या एक पूर्णांक मूल्य से सूचक के आकार का प्रतिनिधित्व करता है, अर्थात, सब कुछ एक मशीन शब्द में फिट बैठता है। लेकिन जब कचरा संग्रहकर्ता जेनेरिक संरचनाओं में संग्रहीत डेटा को देखता है, तो उसे संख्याओं से बिंदुओं को अलग करने की आवश्यकता होती है, इसलिए संख्याओं को एक बिट के साथ चिह्नित किया जाता है, जहां सही ढंग से संरेखित बिंदुओं में एक बिट नहीं होता है, केवल 31 या 63 बिट्स की रेंज छोड़कर।
OCaml में एक प्रकार का इंफ़ेक्शन सिस्टम भी है, जिससे आप एक फ़ंक्शन लिख सकते हैं और कंपाइलर सबसे उपयुक्त जेनेरिक प्रकार का उत्पादन करेगा यदि आप इसे एनोटेट नहीं करते हैं, और इसलिए फ़ंक्शन ऐसा लगेगा जैसे यह एक गतिशील रूप से टाइप की गई भाषा है:
let first (head :: tail) = head (* inferred type: 'a list -> 'a *)
दिए गए प्रकार को "प्रकार में तत्वों के साथ
'a
प्रकार में
'a
के प्रकार से एक फ़ंक्शन कहा जा सकता है। इसका मतलब यह है कि वापसी प्रकार सूची प्रकार के समान होगा, और यह किसी भी प्रकार का हो सकता है।
इंटरफेस
पारंपरिक पैकेजिंग की एक और सीमा यह है कि पैक किए गए प्रकार
पूरी तरह से अपारदर्शी हैं। यह स्टैक जैसी डेटा संरचनाओं के लिए कोई समस्या नहीं है, लेकिन जेनेरिक फ़ंक्शंस को सॉर्ट करने वाले टूल को अतिरिक्त विशेषताओं की आवश्यकता होती है, जैसे कि टाइप-विशिष्ट तुलना फ़ंक्शंस। रनटाइम में इसे लागू करने और भाषा में प्रतिबिंबित करने के कई तरीके हैं, तकनीकी रूप से ये अलग-अलग दिशाएं हैं, और आप
एक ही भाषा को कई समान दृष्टिकोणों के साथ लागू कर सकते हैं। हालांकि, विभिन्न भाषाओं की विशेषताएं उनके कार्यान्वयन को प्रभावित करती हैं, और उसके बाद ही एक्सटेंशन चयनित कार्यान्वयन की ताकत बढ़ाते हैं। इसका मतलब यह है कि रनटाइम के लिए अलग-अलग दृष्टिकोणों के आधार पर भाषाओं के दो परिवार हैं: आभासी विधि टेबल (vtables) और शब्दकोश स्थानांतरण।
इंटरफ़ेस विधि टेबल्स
यदि हम सब कुछ के साथ एकीकृत कार्य के लिए पैकेजिंग रणनीति का पालन करते हुए, टाइप-विशिष्ट फ़ंक्शन प्रदान करना चाहते हैं, तो समान कार्यों को खोजने के लिए एकीकृत तरीका होना पर्याप्त है जो हमें ऑब्जेक्ट से प्राप्त करने की आवश्यकता है। इस दृष्टिकोण को "वर्चुअल मेथड टेबल" (vtables, वर्चुअल मेथड टेबल) कहा जाता है, हालांकि कोई भी पूर्ण नाम का उपयोग नहीं करता है। इसे निम्नानुसार लागू किया जाता है: प्रत्येक सामान्य संरचना वस्तु में एक शून्य ऑफसेट पर, एक सुसंगत सर्किट के साथ फ़ंक्शन पॉइंटर्स की तालिका के लिए एक संकेतक होता है। इन तालिकाओं में, जेनेरिक कोड निश्चित बिंदुओं पर विशिष्ट बिंदुओं को अनुक्रमित करके टाइपर्स के लिए विशिष्ट कार्यों को इंगित करता है।
यह इस प्रकार है कि
interface
प्रकार को
dyn trait
में लागू किया जाता है और रस्ट में
dyn trait
ऑब्जेक्ट। जब आप एक प्रकार का इंटरफ़ेस टाइप करते हैं, तो यह किस प्रकार लागू होता है, इंटरफ़ेस के लिए एक आवरण बनाया जाता है जिसमें स्रोत ऑब्जेक्ट के लिए एक पॉइंटर होता है और टाइप-स्पेसिफिक कार्यों के लिए एक पॉइंटर होता है। लेकिन इसके लिए सूचक और एक अन्य योजना के अप्रत्यक्ष स्तर के अतिरिक्त स्तर की आवश्यकता होती है। इसलिए, गो में छंटाई
स्वैप विधि के साथ कंटेनर के लिए इंटरफ़ेस का उपयोग करता है, और तुलनात्मक इंटरफ़ेस का टुकड़ा नहीं लेता है, क्योंकि इसके लिए स्मृति में पूरी तरह से नए प्रकार के इंटरफ़ेस रखने की आवश्यकता होगी जो मूल स्लाइस के बजाय हल हो जाएंगे!
ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग
ओओपी एक भाषा संपत्ति है जो आभासी प्रकार की तालिकाओं की क्षमताओं का अच्छा उपयोग करती है। Vtables के साथ अलग-अलग इंटरफ़ेस ऑब्जेक्ट्स के बजाय, ओओपी भाषाएं जैसे कि जावा प्रत्येक ऑब्जेक्ट की शुरुआत में बस एक पॉइंटर को आभासी प्रकारों की एक तालिका में सम्मिलित करता है। जावा जैसी भाषाओं में एक विरासत प्रणाली और इंटरफेस है जो इन आभासी प्रकार ऑब्जेक्ट टेबल का उपयोग करके पूरी तरह से लागू किया जा सकता है।
अतिरिक्त सुविधाओं को प्रदान करने के अलावा, प्रत्येक ऑब्जेक्ट में एम्बेडिंग वाइब्रेट अप्रत्यक्ष पते (अप्रत्यक्ष) के साथ नए इंटरफ़ेस प्रकार के निर्माण की आवश्यकता की समस्या को हल करता है। गो के विपरीत, जावा में
, सॉर्ट फ़ंक्शन उस प्रकार के
Comparable
इंटरफ़ेस को लागू कर सकता है जो इसे लागू करता है।
प्रतिबिंब
यदि आपके पास वर्चुअल प्रकार की तालिकाएँ हैं, तो आपके लिए कंपाइलर को अन्य प्रकार की सूचनाओं की तालिकाएँ बनाने के लिए मजबूर करना मुश्किल नहीं होगा, उदाहरण के लिए, फ़ील्ड्स, प्रकारों और स्थानों के नाम। यह कोड का उपयोग करके इस प्रकार के सभी डेटा तक पहुंच की अनुमति देगा जो किसी अन्य प्रकार के सभी डेटा को देख सकता है। इस व्यवहार का उपयोग भाषा में "प्रतिबिंब" जोड़ने के लिए किया जा सकता है, जो क्रमबद्धता और मनमाने प्रकार के सुंदर प्रदर्शन की अनुमति देता है। पैकेजिंग प्रतिमान के विस्तार के रूप में प्रतिबिंब में एक खामी है: कोड की केवल एक प्रति इसके लिए पर्याप्त है, लेकिन आपको कई गतिशील खोजों को करने की आवश्यकता है, जो क्रमबद्धता की गति को कम करता है।
धारावाहिकीकरण और अन्य कार्यों के लिए प्रतिबिंब का उपयोग करने वाली भाषाएँ: जावा, सी # और गो।
डायनामिक रूप से टाइप की गई भाषाएं
प्रतिबिंब एक बहुत शक्तिशाली उपकरण है जो आपको विभिन्न मेटाप्रोग्रामिंग कार्यों के एक समूह को हल करने की अनुमति देता है। लेकिन यह आपको नए प्रकार बनाने या मौजूदा मूल्यों के प्रकारों के बारे में जानकारी संपादित करने की अनुमति नहीं देता है। यदि हम इस सुविधा को जोड़ते हैं और डेटा एक्सेस और संशोधन के सिंटैक्स बनाते हैं, तो हम डिफ़ॉल्ट रूप से प्रतिबिंब का उपयोग करते हैं, हमें गतिशील रूप से टाइप की गई भाषाएं मिलती हैं! पायथन और रूबी जैसी भाषाओं में मेटाप्रोग्रामिंग का अविश्वसनीय लचीलापन प्रभावी, शक्तिशाली प्रतिबिंब प्रणालियों के लिए धन्यवाद है, जो किसी भी समस्या को हल करने के लिए उपयोग किया जाता है।
आप कह सकते हैं: "लेकिन गतिशील भाषाएं इस तरह से काम नहीं करती हैं, वे बस हैश टेबल का उपयोग करके सब कुछ लागू करते हैं!" हैश टेबल प्रकार की जानकारी के साथ संपादन योग्य टेबल बनाने के लिए सिर्फ एक अच्छा डेटा संरचना है। इसके अलावा, कुछ व्याख्याकार, जैसे कि सीपीथॉन, इस तरह से काम करते हैं। एक उच्च-प्रदर्शन वाले JIT में, V8 का कहना है,
बहुत सारी आभासी प्रकार की टेबल और प्रतिबिंब जानकारी हैं। V8 में, छिपी हुई कक्षाएं (vtables और प्रतिबिंब की जानकारी) और वस्तुओं की संरचना जावा वीएम में आप क्या देख सकते हैं के समान हैं, वस्तुओं को रनटाइम पर नए आभासी प्रकार की तालिकाओं के साथ बदलने की क्षमता के साथ। यह एक संयोग नहीं है, क्योंकि कोई संयोग नहीं हैं:
V8 के
निर्माता उच्च-प्रदर्शन जावा वीएम पर काम करते थे ।
शब्दकोश स्थानांतरण
डायनेमिक इंटरफेस को लागू करने का एक अन्य तरीका यह है कि आवश्यक फ़ंक्शन पॉइंटर्स के साथ एक तालिका को जेनेरिक फ़ंक्शन को स्थानांतरित किया जाए जो उन्हें चाहिए। यह कॉल के स्थान पर गो-आकार की इंटरफ़ेस ऑब्जेक्ट्स के निर्माण के समान है, केवल हमारे मामले में तालिका को एक छिपे हुए तर्क के रूप में पारित किया जाता है, और मौजूदा तर्कों में से एक के रूप में बंडल में पैक नहीं किया जाता है।
इस दृष्टिकोण का उपयोग
हास्केल में
टाइप कक्षाओं में किया जाता है, हालांकि जीएचसी आपको इनलाइनिंग और विशेषज्ञता का उपयोग करके कुछ प्रकार के मोनोमोर्फाइजेशन करने की अनुमति देता है। OCaml
प्रथम श्रेणी के मॉड्यूल के रूप में एक स्पष्ट तर्क के साथ शब्दकोश हस्तांतरण का उपयोग करता है, लेकिन यह पहले से ही
पैरामीटर को निहित बनाने की क्षमता को जोड़ने का प्रस्ताव दिया गया है।
स्विफ्ट में गवाह टेबल
स्विफ्ट लेखकों ने एक दिलचस्प समाधान का उपयोग किया: शब्दकोश को स्थानांतरित करना, साथ ही प्रकार के आकारों पर डेटा डालना और उन्हें तालिका में स्थानांतरित करना, कॉपी करना और जारी करना कैसे संभव है, आपको बिना पैकिंग के किसी भी प्रकार के साथ एकीकृत कार्य के लिए सभी आवश्यक जानकारी प्रदान करने की अनुमति देता है। इस प्रकार, स्विफ्ट सभी संस्थाओं के
एकीकृत प्रतिनिधित्व में स्मृति में विमुद्रीकरण और प्लेसमेंट के बिना जेनरिक
को लागू कर सकता है! हां, आपको गतिशील खोजों के लिए भुगतान करना होगा, जैसा कि पैकेजिंग का उपयोग करने वाले पूरे परिवार की विशेषता है, लेकिन यह मेमोरी में प्लेसमेंट, इसकी खपत और कैश असंगति के लिए संसाधनों को बचाता है।
@Inlinable के रूप में एनोटेट किए गए फ़ंक्शंस का उपयोग करते हुए, स्विफ्ट कंपाइलर एक मॉड्यूल के अंदर या मॉड्यूल के बीच या
इनवॉइस जेनरेट करने में सक्षम है। संभवतः कोड आकार पर प्रभाव के एक अनुमानी मूल्यांकन का उपयोग किया जाता है।
यह भी बताता है कि स्विफ्ट
एबीआई स्थिरता को कैसे
लागू कर सकती है, जबकि अभी भी आपको संरचना में फ़ील्ड जोड़ने और पुनर्वितरित करने की अनुमति देता है, हालांकि लेखक बेहतर प्रदर्शन के लिए गतिशील खोजों से इनकार करने के लिए
@frozen
विशेषता प्रदान करते हैं।
गहन प्रकार विश्लेषण
पैकेज्ड प्रकारों के लिए इंटरफेस को लागू करने का एक और तरीका है। हम वाइट पॉइंटर के उदाहरण के बाद ऑब्जेक्ट के एक निश्चित भाग में टाइप आइडेंटिफायर जोड़ते हैं, और फिर प्रत्येक इंटरफ़ेस मेथड के लिए फंक्शन जेनरेट करते हैं, जिसमें सभी प्रकार के लिए एक बड़ा
switch
एक्सप्रेशन होता है, जो इस मेथड को लागू करता है और इसे सही टाइप-विशिष्ट मेथड में पास करता है।
मैं इस दृष्टिकोण का उपयोग करने वाली भाषाओं के खिलाफ सावधानी नहीं बरतता, लेकिन C ++ कंपाइलर और जावा वर्चुअल मशीन एक समान तरीके से कार्य करते हैं, जब प्रोफाइल के आधार पर अनुकूलन का उपयोग करते हैं तो उन्हें पता चलता है कि कॉलिंग जेनरिक का एक निश्चित स्थान ज्यादातर कुछ प्रकार की वस्तुओं के साथ काम करता है। कंपाइलर और वीएम प्रत्येक साधारण प्रकार के चेक के साथ कॉल पॉइंट्स को प्रतिस्थापित करते हैं, और फिर इन प्रकारों को पारंपरिक डायनामिक चेक का उपयोग करके एक कमबैक के रूप में भेजते हैं। इसलिए, शाखा भविष्यवाणी एल्गोरिथ्म भविष्यवाणी कर सकता है कि कोड किस शाखा पर जाएगा, और स्थिर कॉल का उपयोग करके निर्देश भेजना जारी रखेगा।
Monomorfizatsiya
यह पैकेजिंग का एक विकल्प है। मोनोमोर्फिफ़िकेशन के साथ, हमें प्रत्येक प्रकार के कोड के कई संस्करणों को उत्पन्न करने का एक तरीका खोजने की आवश्यकता है जिसे हम उपयोग करना चाहते हैं। कंपाइलरों में कई प्रेजेंटेशन चरण होते हैं, जो कोड से गुजरता है, और, सैद्धांतिक रूप से, इनमें से किसी भी चरण में कॉपी किया जा सकता है।
स्रोत कोड पीढ़ी
मोनोमोर्फाइज़ करने का सबसे आसान तरीका है कि आप पहले प्रेज़ेंटेशन स्टेज पर कॉपी करें: सोर्स कोड को कॉपी करें! तब कंपाइलर को जेनेरिक का समर्थन भी नहीं करना पड़ता है, और यह कभी-कभी सी और गो भाषाओं के उपयोगकर्ताओं द्वारा किया जाता है, जिनके कंपाइलरों का ऐसा समर्थन नहीं होता है।
सी में, आप एक प्रीप्रोसेसर का उपयोग कर सकते हैं और डेटा संरचना को एक मैक्रो या हेडर में बार-बार अलग-अलग
#define
साथ डालकर
#define
। गो में
जीनी जैसी स्क्रिप्ट हैं जो कोड उत्पन्न करना आसान बनाती हैं।
स्रोत कोड को डुप्लिकेट करने का नुकसान यह है कि, भाषा के आधार पर, कई समस्याओं और किनारे के मामलों से निपटने के लिए आवश्यक हो सकता है, इसके अलावा, कंपाइलर कई बार पार्स करता है और वस्तुतः समान कोड के लिए प्रकारों की जांच करता है। फिर से, भाषा और औजारों के आधार पर, तरीकों के इन जेनेरिकों को लिखना और उपयोग करना मुश्किल हो सकता है, जैसे कि एक सी-मैक्रो के अंदर प्रत्येक पंक्ति एक बैकस्लैश के साथ समाप्त होती है और सभी प्रकार के कार्यों का नाम मैन्युअल रूप से उनके पहचानकर्ताओं में टकराव से बचने के लिए होना चाहिए।
डी में स्ट्रिंग मिश्रण
हालाँकि, कोड जनरेशन के अपने फायदे हैं, जैसे कि यह तथ्य कि आप एक पूर्ण प्रोग्रामिंग भाषा का उपयोग करके कोड उत्पन्न कर सकते हैं, साथ ही उपयोगकर्ताओं के लिए परिचित दृश्य का उपयोग कर सकते हैं।
कुछ भाषाएँ जिनमें जेनरिक को अलग तरह से लागू किया जाता है, आपको अधिक सामान्य मेटाप्रोग्रामिंग मामलों के लिए कोड उत्पन्न करने की अनुमति देती हैं, जो उनके जेनेरिक सिस्टम में नहीं माना जाता है, उदाहरण के लिए, क्रमांकन के लिए। सबसे समझ में आने वाला उदाहरण
डी में स्ट्रिंग मिश्रण है , जो भाषा के सभी विशेषताओं का उपयोग करते हुए संकलन के बीच में स्ट्रिंग मूल्यों के रूप में डी-कोड को संकलित करने की अनुमति देता है।
जंग प्रक्रियात्मक मैक्रोज़
एक समान उदाहरण, केवल एक चरण में कंपाइलर में एक प्रतिनिधित्व के साथ।
जंग प्रक्रियात्मक मैक्रोज़ इन धाराओं को स्ट्रिंग और इसके विपरीत में परिवर्तित करने के लिए उपयोगिताओं प्रदान करते हुए, इनपुट और आउटपुट के रूप में टोकन धाराओं का उपयोग करते हैं। इस दृष्टिकोण का लाभ यह है कि टोकन स्ट्रीम स्रोत कोड से स्थान की जानकारी संग्रहीत कर सकते हैं। उपयोगकर्ता द्वारा लिखित कोड, मैक्रो को इनपुट से सप्ताहांत तक सीधे टोकन के रूप में डाला जा सकता है। और यदि यह कोड मैकोस के आउटपुट में संकलन त्रुटि की ओर जाता है, तो संकलक एक संदेश प्रदर्शित करेगा और कोड के टूटे हुए हिस्से की फ़ाइल, लाइन और कॉलम को इंगित करेगा। लेकिन अगर मैक्रो एक टूटे हुए कोड को उत्पन्न करता है, तो एक त्रुटि संदेश मैक्रो कॉल को इंगित करेगा। उदाहरण के लिए, यदि आप एक
मैक्रो का उपयोग
करते हैं जो लॉगिंग कॉल में एक फ़ंक्शन को लपेटता है और एक लिपटे फ़ंक्शन को लागू करने में गलती करता है, तो त्रुटि संदेश सीधे फ़ाइल में त्रुटि को इंगित करेगा, और मैक्रो द्वारा उत्पन्न कोड को नहीं।
सिंटेक्स ट्री मैक्रोज़
कुछ भाषाएं और भी आगे बढ़ती हैं और मैक्रो (सार सिंटैक्स ट्री, एएसटी) में विभिन्न प्रकार के सार सिंटैक्स पेड़ों का उपयोग करने और बनाने के लिए उपकरण प्रदान करती हैं। उदाहरणों में
टेम्प्लेट हास्केल ,
निम मैक्रोस ,
ओकेम्मेल पीपीएक्स और लगभग सभी
लिस्प शामिल हैं ।
एएसटी मैक्रोज़ की खामी यह है कि आप उपयोगकर्ताओं को एएसटी प्रकारों के साथ-साथ मूल भाषाओं के निर्माण के लिए कार्यों का एक गुच्छा सीखने के लिए बाध्य नहीं करना चाहते हैं। भाषाओं के लिस्प परिवार में, यह मजबूत सरलीकरण और एएसटी की संरचना और संरचना के बीच अधिकतम पत्राचार की मदद से हल किया जाता है, हालांकि, संरचनाएं बनाना हमेशा आसान नहीं होता है।
इस प्रकार, मैंने जिन सभी भाषाओं का उल्लेख किया है, एक रूप में या किसी अन्य में एक "उद्धरण" है जो आप भाषा में कोड का एक टुकड़ा देते हैं, और वह एक वाक्यविन्यास ट्री देता है। ये आदिम वाक्यविन्यास वृक्ष के मूल्यों को स्ट्रिंग प्रक्षेप की समानता का उपयोग करके विलय कर सकते हैं। यहाँ टेम्प्लेट हास्केल पर एक उदाहरण दिया गया है:
, , , . . , PPX OCaml
/ , . Rust ,
parsing quotation , , . Rust
, , !
टेम्पलेट्स
— . ++ D , « ». , , , , .
template <class T> T myMax(T a, T b) { return (a>b?a:b); } template <class T> struct Pair { T values[2]; }; int main() { myMax(5, 6); Pair<int> p { {5,6} };
, , . , , .
D , , : , , . D;
if
(
!
):
C++20 «» , , .
D , (compile time function evaluation)
static if
, , , , - runtime-. , , , ++ , .
, « ». , Zig:
fn Stack(comptime T: type) type { return struct { items: []T, len: usize, const Self = @This(); pub fn push(self: Self, item: T) { // ... } }; }
Zig , ,
comptime
.
Terra , . Terra — Lua, - , Lua API , quoting splicing:
function MakeStack(T) local struct Stack { items : &T;
Terra
- (domain specific) ,
Java Go . Terra runtime , .
Rust
, . , , ++, . , , , , . , . Rust, — Swift Haskell.
Rust « » (trait bounds).
Trait
— , , . Rust , - , , , . - Rust
. , -.
fn my_max<T: PartialOrd>(a: T, b: T) -> T { if a > b { a } else { b } } struct Pair<T> { values: [T; 2], } fn main() { my_max(5,6); let p: Pair<i32> = Pair { values: [5,6] };
, . Rust . Rust 2018 ,
v: &impl SomeTrait
,
v: &dyn SomeTrait
. GHC Swift Haskell , .
— , . , (placeholders) -, , .
memcpy
, ! , . . JIT, , .
, , , , , , ! , , , . , , .