प्रोग्रामर लगातार इस बात पर बहस कर रहे हैं कि कौन सी भाषा सबसे अच्छी है। एक बार हमने सी और पास्कल की तुलना की, लेकिन समय बीत गया। / और /सी# की लड़ाई पहले से ही हमारे पीछे है। प्रत्येक भाषा के अपने फायदे और नुकसान हैं, यही कारण है कि हम उनकी तुलना करते हैं। आदर्श रूप से, हम अपनी आवश्यकताओं के अनुरूप भाषाओं का विस्तार करना चाहेंगे। प्रोग्रामर्स के पास यह अवसर बहुत लंबे समय से है। हम मेटाप्रोग्रामिंग के विभिन्न तरीकों को जानते हैं, यानी प्रोग्राम बनाने के लिए प्रोग्राम बनाना। यहां तक कि सी में मामूली मैक्रोज़ भी आपको छोटे विवरणों से कोड के बड़े हिस्से उत्पन्न करने की अनुमति देते हैं। हालाँकि, ये मैक्रोज़ अविश्वसनीय, सीमित और बहुत अभिव्यंजक नहीं हैं। आधुनिक भाषाओं में विस्तार के अधिक अभिव्यंजक साधन हैं। इन्हीं भाषाओं में से एक है कोटलिन। पायथन रूबी जावा डोमेन-विशिष्ट भाषा की परिभाषा एक एक ऐसी भाषा है जो जावा, सी#, सी++ और अन्य जैसी सामान्य-उद्देश्य वाली भाषाओं के विपरीत, विशेष रूप से एक विशिष्ट विषय क्षेत्र के लिए विकसित की जाती है। इसका मतलब यह है कि विषय क्षेत्र के कार्यों का वर्णन करना आसान, अधिक सुविधाजनक और अधिक अभिव्यंजक है, लेकिन साथ ही यह रोजमर्रा के कार्यों को हल करने के लिए असुविधाजनक और अव्यवहारिक है, अर्थात यह एक सार्वभौमिक भाषा नहीं है। उदाहरण के तौर पर डीएसएल, आप रेगुलर एक्सप्रेशन भाषा ले सकते हैं। रेगुलर एक्सप्रेशन का विषय क्षेत्र स्ट्रिंग्स का प्रारूप है। डोमेन-विशिष्ट भाषा (डीएसएल) प्रारूप के अनुपालन के लिए स्ट्रिंग की जांच करने के लिए, बस एक लाइब्रेरी का उपयोग करना पर्याप्त है जो नियमित अभिव्यक्तियों के लिए समर्थन लागू करता है: private boolean isIdentifierOrInteger(String s) { return s.matches("^\\s*(\\w+\\d*|\\d+)$"); } यदि आप सार्वभौमिक भाषा, उदाहरण के लिए, जावा में निर्दिष्ट प्रारूप के अनुपालन के लिए स्ट्रिंग की जांच करते हैं, तो आपको निम्नलिखित कोड मिलेगा: private boolean isIdentifierOrInteger(String s) { int index = 0; while (index < s.length() && isSpaceChar(s.charAt(index))) { index++; } if (index == s.length()) { return false; } if (isLetter(s.charAt(index))) { index++; while (index < s.length() && isLetter(s.charAt(index))) index++; while (index < s.length() && isDigit(s.charAt(index))) index++; } else if (Character.isDigit(s.charAt(index))) { while (index < s.length() && isDigit(s.charAt(index))) index++; } return index == s.length(); } उपरोक्त कोड को नियमित अभिव्यक्तियों की तुलना में पढ़ना कठिन है, गलतियाँ करना आसान है और परिवर्तन करना अधिक कठिन है। डीएसएल के अन्य सामान्य उदाहरण HTML, CSS, SQL, UML और BPMN हैं (बाद वाले दो ग्राफिकल नोटेशन का उपयोग करते हैं)। डीएसएल का उपयोग न केवल डेवलपर्स द्वारा बल्कि परीक्षकों और गैर-आईटी विशेषज्ञों द्वारा भी किया जाता है। डीएसएल के प्रकार डीएसएल को दो प्रकारों में विभाजित किया गया है: बाहरी और आंतरिक। भाषाओं का अपना वाक्यविन्यास होता है और वे उस सार्वभौमिक प्रोग्रामिंग भाषा पर निर्भर नहीं होते हैं जिसमें उनका समर्थन लागू किया जाता है। बाहरी डीएसएल बाहरी डीएसएल के फायदे और नुकसान: 🟢 विभिन्न भाषाओं/तैयार पुस्तकालयों में कोड जनरेशन 🟢 अपना सिंटैक्स सेट करने के लिए अधिक विकल्प 🔴 विशेष उपकरणों का उपयोग: एएनटीएलआर, वाईएसीसी, लेक्स 🔴 कभी-कभी व्याकरण का वर्णन करना कठिन होता है 🔴 कोई आईडीई समर्थन नहीं है, आपको अपना प्लगइन लिखना होगा एक विशिष्ट सार्वभौमिक प्रोग्रामिंग भाषा (होस्ट भाषा) पर आधारित होते हैं। अर्थात्, मेजबान भाषा के मानक उपकरणों की सहायता से पुस्तकालय बनाए जाते हैं जो आपको अधिक संक्षिप्त रूप से लिखने की अनुमति देते हैं। उदाहरण के तौर पर, फ़्लुएंट एपीआई दृष्टिकोण पर विचार करें। आंतरिक डीएसएल आंतरिक डीएसएल के फायदे और नुकसान: 🟢 मेजबान भाषा के भावों को आधार के रूप में उपयोग करता है 🟢 होस्ट भाषाओं में कोड में डीएसएल एम्बेड करना आसान है और इसके विपरीत 🟢 कोड जनरेशन की आवश्यकता नहीं है 🟢 होस्ट भाषा में सबरूटीन के रूप में डिबग किया जा सकता है 🔴 वाक्यविन्यास सेटिंग में सीमित संभावनाएँ वास्तविक जीवन का उदाहरण हाल ही में, कंपनी में हमें अपना डीएसएल बनाने की आवश्यकता का सामना करना पड़ा। हमारे उत्पाद ने खरीद स्वीकृति की कार्यक्षमता लागू कर दी है। यह मॉड्यूल BPM (बिजनेस प्रोसेस मैनेजमेंट) का एक मिनी इंजन है। व्यावसायिक प्रक्रियाओं को अक्सर ग्राफ़िक रूप से दर्शाया जाता है। उदाहरण के लिए, नीचे दिया गया बीपीएमएन नोटेशन एक प्रक्रिया दिखाता है जिसमें टास्क 1 को निष्पादित करना और फिर टास्क 2 और टास्क 3 को समानांतर में निष्पादित करना शामिल है। हमारे लिए व्यावसायिक प्रक्रियाओं को प्रोग्रामेटिक रूप से बनाने में सक्षम होना महत्वपूर्ण था, जिसमें गतिशील रूप से एक मार्ग बनाना, अनुमोदन चरणों के लिए कलाकारों को सेट करना, चरण निष्पादन के लिए समय सीमा निर्धारित करना आदि शामिल था। ऐसा करने के लिए, हमने पहले फ़्लुएंट एपीआई का उपयोग करके इस समस्या को हल करने का प्रयास किया। दृष्टिकोण। फिर हमने निष्कर्ष निकाला कि फ़्लुएंट एपीआई का उपयोग करके स्वीकृति मार्ग सेट करना अभी भी बोझिल हो गया है और हमारी टीम ने अपना स्वयं का डीएसएल बनाने के विकल्प पर विचार किया। हमने जांच की कि कोटलिन पर आधारित बाहरी डीएसएल और आंतरिक डीएसएल पर स्वीकृति मार्ग कैसा दिखेगा (क्योंकि हमारा उत्पाद कोड जावा और में लिखा गया है)। कोटलिन बाहरी डीएसएल: acceptance addStep executor: HEAD_OF_DEPARTMENT duration: 7 days protocol should be formed parallel addStep executor: FINANCE_DEPARTMENT or CTO or CEO condition: ${!request.isInternal} duration: 7 work days after start date addStep executor: CTO dueDate: 2022-12-08 08:00 PST can change addStep executor: SECRETARY protocol should be signed आंतरिक डीएसएल: acceptance { addStep { executor = HEAD_OF_DEPARTMENT duration = days(7) protocol shouldBe formed } parallel { addStep { executor = FINANCE_DEPARTMENT or CTO or CEO condition = !request.isInternal duration = startDate() + workDays(7) } addStep { executor = CTO dueDate = "2022-12-08 08:00" timezone PST +canChange } } addStep { executor = SECRETARY protocol shouldBe signed } } घुंघराले कोष्ठक को छोड़कर, दोनों विकल्प लगभग समान हैं। इसलिए, यह निर्णय लिया गया कि बाहरी डीएसएल विकसित करने में समय और प्रयास बर्बाद न किया जाए, बल्कि एक आंतरिक डीएसएल बनाया जाए। डीएसएल की बुनियादी संरचना का कार्यान्वयन आइए एक ऑब्जेक्ट मॉडल विकसित करना शुरू करें interface AcceptanceElement class StepContext : AcceptanceElement { lateinit var executor: ExecutorCondition var duration: Duration? = null var dueDate: ZonedDateTime? = null val protocol = Protocol() var condition = true var canChange = ChangePermission() } class AcceptanceContext : AcceptanceElement { val elements = mutableListOf<AcceptanceElement>() fun addStep(init: StepContext.() -> Unit) { elements += StepContext().apply(init) } fun parallel(init: AcceptanceContext.() -> Unit) { elements += AcceptanceContext().apply(init) } } object acceptance { operator fun invoke(init: AcceptanceContext.() -> Unit): AcceptanceContext { val acceptanceContext = AcceptanceContext() acceptanceContext.init() return acceptanceContext } } lambdas सबसे पहले, आइए क्लास को देखें। इसे मार्ग तत्वों के संग्रह को संग्रहीत करने के लिए डिज़ाइन किया गया है और इसका उपयोग संपूर्ण आरेख के साथ-साथ -ब्लॉकों का प्रतिनिधित्व करने के लिए किया जाता है। AcceptanceContext parallel और विधियाँ एक पैरामीटर के रूप में रिसीवर के साथ एक लैम्ब्डा लेती हैं। addStep parallel एक रिसीवर के साथ एक लैम्ब्डा एक लैम्ब्डा अभिव्यक्ति को परिभाषित करने का एक तरीका है जिसकी एक विशिष्ट रिसीवर ऑब्जेक्ट तक पहुंच होती है। फ़ंक्शन लिटरल के मुख्य भाग के अंदर, किसी कॉल में पास किया गया रिसीवर ऑब्जेक्ट एक अंतर्निहित बन जाता है, ताकि आप बिना किसी अतिरिक्त क्वालीफायर के उस रिसीवर ऑब्जेक्ट के सदस्यों तक पहुंच सकें, या अभिव्यक्ति का उपयोग करके रिसीवर ऑब्जेक्ट तक पहुंच सकें। this this साथ ही, यदि किसी विधि कॉल का अंतिम तर्क लैम्ब्डा है, तो लैम्ब्डा को कोष्ठक के बाहर रखा जा सकता है। इसीलिए हम अपने डीएसएल में निम्नलिखित जैसा एक कोड लिख सकते हैं: parallel { addStep { executor = FINANCE_DEPARTMENT ... } addStep { executor = CTO ... } } यह सिंटैक्टिक शुगर के बिना एक कोड के बराबर है: parallel({ this.addStep({ this.executor = FINANCE_DEPARTMENT ... }) this.addStep({ this.executor = CTO ... }) }) रिसीवर्स के साथ लैम्ब्डा और कोष्ठक के बाहर लैम्ब्डा कोटलिन विशेषताएं हैं जो डीएसएल के साथ काम करते समय विशेष रूप से उपयोगी होती हैं। वस्तु घोषणा अब आइए इकाई पर नजर डालें। एक वस्तु है. कोटलिन में, एक ऑब्जेक्ट घोषणा एक सिंगलटन को परिभाषित करने का एक तरीका है - केवल एक उदाहरण वाला एक वर्ग। तो, ऑब्जेक्ट घोषणा एक ही समय में वर्ग और उसके एकल उदाहरण दोनों को परिभाषित करती है। acceptance acceptance ऑपरेटर ओवरलोडिंग को "आह्वान" करें इसके अलावा, ऑपरेटर वस्तु के लिए अतिभारित है। ऑपरेटर एक विशेष फ़ंक्शन है जिसे आप अपनी कक्षाओं में परिभाषित कर सकते हैं। जब आप किसी क्लास के इंस्टेंस को ऐसे इनवोक करते हैं जैसे कि वह कोई फ़ंक्शन हो, तो ऑपरेटर फ़ंक्शन को कॉल किया जाता है। यह आपको वस्तुओं को फ़ंक्शन के रूप में मानने और उन्हें फ़ंक्शन-जैसी तरीके से कॉल करने की अनुमति देता है। invoke accreditation invoke invoke ध्यान दें कि विधि का पैरामीटर भी एक रिसीवर के साथ एक लैम्ब्डा है। अब हम एक स्वीकृति मार्ग परिभाषित कर सकते हैं... invoke val acceptanceRoute = acceptance { addStep { executor = HEAD_OF_DEPARTMENT ... } parallel { addStep { executor = FINANCE_DEPARTMENT ... } addStep { executor = CTO ... } } addStep { executor = SECRETARY ... } } ...और इसके माध्यम से चलो val headOfDepartmentStep = acceptanceRoute.elements[0] as StepContext val parallelBlock = acceptanceRoute.elements[1] as AcceptanceContext val ctoStep = parallelBlock.elements[1] as StepContext विवरण जोड़ना इन्फ़िक्स फ़ंक्शंस इस कोड पर एक नजर डालें addStep { executor = FINANCE_DEPARTMENT or CTO or CEO ... } हम इसे निम्नलिखित द्वारा कार्यान्वित कर सकते हैं: enum class ExecutorConditionType { EQUALS, OR } data class ExecutorCondition( private val name: String, private val values: Set<ExecutorCondition>, private val type: ExecutorConditionType, ) { infix fun or(another: ExecutorCondition) = ExecutorCondition("or", setOf(this, another), ExecutorConditionType.OR) } val HEAD_OF_DEPARTMENT = ExecutorCondition("HEAD_OF_DEPARTMENT", setOf(), ExecutorConditionType.EQUALS) val FINANCE_DEPARTMENT = ExecutorCondition("FINANCE_DEPARTMENT", setOf(), ExecutorConditionType.EQUALS) val CHIEF = ExecutorCondition("CHIEF", setOf(), ExecutorConditionType.EQUALS) val CTO = ExecutorCondition("CTO", setOf(), ExecutorConditionType.EQUALS) val SECRETARY = ExecutorCondition("SECRETARY", setOf(), ExecutorConditionType.EQUALS) वर्ग हमें कई संभावित कार्य निष्पादकों को सेट करने की अनुमति देता है। में हम infix फ़ंक्शन परिभाषित करते हैं। इन्फिक्स फ़ंक्शन एक विशेष प्रकार का फ़ंक्शन है जो आपको अधिक प्राकृतिक, इन्फिक्स नोटेशन का उपयोग करके इसे कॉल करने की अनुमति देता है। ExecutorCondition ExecutorCondition or भाषा की इस विशेषता का उपयोग किए बिना, हमें इस प्रकार लिखना होगा: addStep { executor = FINANCE_DEPARTMENT.or(CTO).or(CEO) ... } इन्फिक्स फ़ंक्शंस का उपयोग प्रोटोकॉल की आवश्यक स्थिति और समयक्षेत्र के साथ समय निर्धारित करने के लिए भी किया जाता है। enum class ProtocolState { formed, signed } class Protocol { var state: ProtocolState? = null infix fun shouldBe(state: ProtocolState) { this.state = state } } enum class TimeZone { ... PST, ... } infix fun String.timezone(tz: TimeZone): ZonedDateTime { val format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z") return ZonedDateTime.parse("$this $tz", format) } विस्तार कार्य एक एक्सटेंशन फ़ंक्शन है। कोटलिन में, एक्सटेंशन फ़ंक्शन आपको मौजूदा कक्षाओं में उनके स्रोत कोड को संशोधित किए बिना नए फ़ंक्शन जोड़ने की अनुमति देते हैं। यह सुविधा विशेष रूप से तब उपयोगी होती है जब आप उन कक्षाओं की कार्यक्षमता को बढ़ाना चाहते हैं जिन पर आपका नियंत्रण नहीं है, जैसे मानक या बाहरी पुस्तकालयों की कक्षाएं। String.timezone डीएसएल में उपयोग: addStep { ... protocol shouldBe formed dueDate = "2022-12-08 08:00" timezone PST ... } यहां एक रिसीवर ऑब्जेक्ट है, जिस पर एक्सटेंशन फ़ंक्शन कहा जाता है, और पैरामीटर है। कीवर्ड का उपयोग करके रिसीवर ऑब्जेक्ट तक पहुंच प्राप्त की जाती है। "2022-12-08 08:00" timezone PST this ऑपरेटर ओवरलोडिंग कर रहा है अगली कोटलिन सुविधा जो हम अपने डीएसएल में उपयोग करते हैं वह ऑपरेटर ओवरलोडिंग है। हम पहले ही ऑपरेटर के अधिभार पर विचार कर चुके हैं। कोटलिन में, आप अंकगणित सहित अन्य ऑपरेटरों को अधिभारित कर सकते हैं। invoke addStep { ... +canChange } यहां यूनरी ऑपरेटर अतिभारित है। नीचे वह कोड है जो इसे लागू करता है: + class StepContext : AcceptanceElement { ... var canChange = ChangePermission() } data class ChangePermission( var canChange: Boolean = true, ) { operator fun unaryPlus() { canChange = true } operator fun unaryMinus() { canChange = false } } अंतिम रूप देना अब हम अपने डीएसएल पर स्वीकृति मार्गों का वर्णन कर सकते हैं। हालाँकि, DSL उपयोगकर्ताओं को संभावित त्रुटियों से बचाया जाना चाहिए। उदाहरण के लिए, वर्तमान संस्करण में, निम्नलिखित कोड स्वीकार्य है: val acceptanceRoute = acceptance { addStep { executor = HEAD_OF_DEPARTMENT duration = days(7) protocol shouldBe signed addStep { executor = FINANCE_DEPARTMENT } } } के भीतर अजीब लगता है, है ना? आइए जानें कि यह कोड बिना किसी त्रुटि के सफलतापूर्वक संकलित क्यों होता है। जैसा कि ऊपर बताया गया है, और विधियां एक पैरामीटर के रूप में एक रिसीवर के साथ एक लैम्ब्डा लेती हैं, और एक रिसीवर ऑब्जेक्ट को कीवर्ड द्वारा एक्सेस किया जा सकता है। तो हम पिछले कोड को इस तरह फिर से लिख सकते हैं: addStep addStep acceptance#invoke AcceptanceContext#addStep this val acceptanceRoute = acceptance { this@acceptance.addStep { this@addStep.executor = HEAD_OF_DEPARTMENT this@addStep.duration = days(7) this@addStep.protocol shouldBe signed this@acceptance.addStep { executor = FINANCE_DEPARTMENT } } } अब आप देख सकते हैं कि दोनों बार कॉल किया जाता है। विशेष रूप से ऐसे मामलों के लिए, कोटलिन के पास एनोटेशन है। आप कस्टम एनोटेशन परिभाषित करने के लिए उपयोग कर सकते हैं। समान एनोटेशन से चिह्नित रिसीवरों को एक दूसरे के अंदर नहीं पहुँचा जा सकता है। this@acceptance.addStep DslMarker @DslMarker @DslMarker annotation class AcceptanceDslMarker @AcceptanceDslMarker class AcceptanceContext : AcceptanceElement { ... } @AcceptanceDslMarker class StepContext : AcceptanceElement { ... } अब कोड val acceptanceRoute = acceptance { addStep { ... addStep { ... } } } 'fun addStep(init: StepContext.() -> Unit): Unit' can't be called in this context by implicit receiver. Use the explicit one if necessary लिंक इस लेख में जिन भाषा विशेषताओं पर विचार किया गया था, उन पर आधिकारिक कोटलिन दस्तावेज़ के लिंक नीचे दिए गए हैं: रिसीवर के साथ फ़ंक्शन शाब्दिक https://kotlinlang.org/docs/lambdas.html#function-literals-with-receiver अनुगामी लैम्ब्डा को पास करना https://kotlinlang.org/docs/lambdas.html#passing-trailing-lambdas वस्तु घोषणाएँ https://kotlinlang.org/docs/object-declarations.html#object-declarations-overview ऑपरेटर ओवरलोडिंग https://kotlinlang.org/docs/operator-overloading.html इन्फिक्स नोटेशन https://kotlinlang.org/docs/functions.html#infix-notation एक्सटेंशन फ़ंक्शन https://kotlinlang.org/docs/extensions.html#extension-functions दायरा नियंत्रण: @DslMarker https://kotlinlang.org/docs/type-safe-builders.html#scope-control-dslmarker निष्कर्ष डोमेन-विशिष्ट भाषाएँ किसी विशिष्ट डोमेन के भीतर समस्याओं को मॉडल करने और हल करने के लिए एक विशेष और अभिव्यंजक तरीका प्रदान करके उत्पादकता बढ़ाने, त्रुटियों को कम करने और सहयोग में सुधार करने का एक शक्तिशाली साधन प्रदान करती हैं। कोटलिन कई विशेषताओं और वाक्यात्मक शर्करा के साथ एक आधुनिक प्रोग्रामिंग भाषा है, इसलिए यह आंतरिक डीएसएल के लिए मेजबान भाषा के रूप में बहुत अच्छी है।