हमारे दूसरे थीम वाले महीने, Mechanical March में आपका स्वागत है। इस महीने हम सिस्टम भाषाओं पर ध्यान दे रहे हैं, यानी उन भाषाओं पर जो मशीन कोड में कंपाइल हो जाती हैं।
यह लेख आंशिक रूप से एक पोस्ट है और आंशिक रूप से Mechanical March वीडियो की ट्रांसक्रिप्ट है। मैं आपको इस महीने का एक छोटा-सा परिचय दूँगा। उसके बाद हम सिस्टम भाषाओं को थोड़ा समझेंगे: उनका ऐतिहासिक विकास, मशीन कोड में कंपाइल करने के फायदे और नुकसान, और हर फीचर्ड भाषा पर एक त्वरित नज़र। इस बार भी मेरे साथ Erik हैं, जो दूसरे हिस्से में ज़्यादातर बात करेंगे। लेकिन शुरुआत मैं कुछ व्यावहारिक जानकारी से करता हूँ।
तो सबसे पहले, इस महीने की फीचर्ड भाषाएँ। ये हैं C, C++, D, Go, Nim, Rust, V और Zig. Mechanical March का बैज पाने के लिए आपको इनमें से किसी एक भाषा में पाँच अभ्यास पूरे करने होंगे। Exercism पर हमारे Go ट्रैक का सिलेबस सबसे बेहतर सिलेबस में से एक है, इसलिए मैं ज़रूर कहूँगा कि आप उसे आज़माकर देखिए। Exercism पर हम Nim के भी बड़े प्रशंसक हैं, क्योंकि शुरुआत करने के लिए यह अपेक्षाकृत आसान भाषा है और इसमें लिखना बहुत सरल है, इसलिए इसे भी ज़रूर आज़माइए।
हमारे पास आज़माने के लिए पाँच फीचर्ड अभ्यास भी हैं:
- Linked-list या simple-linked-list (भाषा के अनुसार): मेमोरी आवंटित करना/मुक्त करना, पॉइंटर
- secret-handshake: बिटवाइज़ ऑपरेशन
- pangram: for लूप, स्ट्रिंग और अक्षर
- sieve: ऐरे, for लूप
- binary-search: ऐरे, लूप
एक नया बैज भी है, जिसकी घोषणा मैंने कुछ दिन पहले वाले अपडेट वीडियो में की थी। यह बैज थीम वाली भाषाओं में पाँचों फीचर्ड अभ्यास पूरे करने पर मिलता है। तो यह बैज पाने के लिए आपको साल में कभी भी किसी सिस्टम भाषा में वे सारे अभ्यास पूरे करने होंगे।
हमारे पास और भी बहुत सी मज़ेदार चीज़ें तैयार हो रही हैं: Go Core टीम के कुछ लोगों के साथ इंटरव्यू, और उम्मीद है कि Rust तथा कुछ अन्य भाषाओं के लोगों के साथ भी। पूरे महीने हम बहुत सारी लाइव स्ट्रीमिंग भी करेंगे। और जल्द ही Mechanical March का स्वैग भी आने वाला है!
तो चलिए, तकनीकी पक्ष में थोड़ा और गहरे उतरते हैं।
ये भाषाएँ किस काम आती हैं?
हम इनका इस्तेमाल Exercism में कई जगह करते हैं। हमारा CLI Go में लिखा गया है, ट्रैक मैनेज करने का हमारा आंतरिक टूल configlet Nim में लिखा गया है, और आपके हल में कोड की लाइनें गिनने वाली मुख्य लाइब्रेरी Rust में लिखी गई है। Erik, हमने इन टूल्स के लिए यही भाषाएँ क्यों चुनीं?
CLI:
- मुझे लगता है कि हमने Go इसलिए चुना, क्योंकि Katrina के लिए यही भाषा सबसे ज़्यादा परिचित थी।
- इस तरह के छोटे-मोटे कमांड-लाइन टूल के लिए Go बहुत अच्छा है।
- Go का कोड अपेक्षाकृत सीधा-सरल होता है, जिससे योगदान देना आसान हो जाता है
- Go की बाइनरी आसानी से डिप्लॉय की जा सकती हैं, क्योंकि इसके लिए किसी रनटाइम की ज़रूरत नहीं होती
- Go क्रॉस-कंपाइलेशन अच्छी तरह करता है
Nim
- Nim में Go जैसे ज़्यादातर फायदे हैं
- CLI को बनाए रखने के लिए हमारे पास Go जानने वाले ज़्यादा लोग नहीं थे, इसलिए हम Nim पर चले गए
इन भाषाओं का इस्तेमाल आप और किन जगहों पर देखने की उम्मीद करेंगे?
- हर वह जगह जहाँ परफॉर्मेंस मायने रखती है (ड्राइवर, गेम, ऑपरेटिंग सिस्टम, बिल्ड सिस्टम/कंपाइलर)
- हर वह जगह जहाँ संसाधन सीमित हैं (जैसे एम्बेडेड सॉफ्टवेयर)
- हर वह चीज़ जिसे बहुत पोर्टेबल होना चाहिए, यानी कई अलग-अलग प्लेटफॉर्म पर चले
मशीन कोड क्या है?
तो जैसा मैंने पहले कहा, Mechanical March की ये भाषाएँ इस बात से अलग हैं कि ये मशीन कोड में कंपाइल होती हैं। क्या आप थोड़ा समझा सकते हैं कि मशीन कोड क्या है, और इसके उलट बाइटकोड क्या है?
- मशीन कोड वह कोड है जो सीधे मशीन पर चल सकता है।
- इसके उलट, बाइटकोड को मशीन कोड में बदलने के लिए किसी और कोड की ज़रूरत पड़ती है, जो उसे इंटरप्रेट या कंपाइल करता है। इसलिए बाइटकोड को चलाने से पहले एक बीच का चरण पूरा करना पड़ता है।
बाइटकोड के मुकाबले मशीन कोड के फायदे और नुकसान क्या हैं?
फायदे:
- शुरुआत तेज़ होती है (JIT कंपाइल का चरण नहीं होता)
- मेमोरी की खपत कम होती है (कोई रनटाइम लोड नहीं होता, मेमोरी में बाइटकोड नहीं रहता, एम्बेडेड सिस्टम के लिए बहुत अच्छा)
- जिस मशीन पर चलाना है, उस पर किसी रनटाइम को इंस्टॉल करने की ज़रूरत नहीं होती (Docker कंटेनर छोटे रखने के लिए यह ज़रूरी है)
नुकसान:
- पोर्टेबल नहीं होता। बाइटकोड पोर्टेबल होता है, लेकिन कंपाइल किया गया मशीन कोड किसी एक प्लेटफॉर्म के लिए ही होता है
- प्रोफाइल-गाइडेड ऑप्टिमाइज़ेशन जैसे उन्नत ऑप्टिमाइज़ेशन नहीं किए जा सकते (यानी कोड को कुछ समय तक चलाने के बाद यह तय करना कि उसे कंपाइल करने का सबसे अच्छा तरीका क्या है)
ध्यान दें: हाइब्रिड तरीके भी संभव हैं, जिनमें भाषा पहले बाइटकोड में कंपाइल होती है और फिर कोई दूसरा टूल उस बाइटकोड को मशीन कोड में कंपाइल करता है।
सिस्टम प्रोग्रामिंग का विकास
अब हम इस महीने की कुछ भाषाओं के विकास पर थोड़ा नज़र डालते हैं। शुरुआत C से करते हैं, और यह भी देखते हैं कि उससे C++ कैसे विकसित हुई। इन भाषाओं के बारे में थोड़ा बताइए।
C बहुत लो-लेवल भाषा है। ऐसा लगता है मानो आप मशीन कोड से बस थोड़ा ही ऊपर हैं। इसी वजह से यह बहुत शक्तिशाली है और इसे बहुत बेहतर तरीके से ऑप्टिमाइज़ किया जा सकता है, लेकिन इसमें बग होने की आशंका भी रहती है (जैसे null pointer exceptions और buffer overflows)। मेमोरी का पूरा प्रबंधन खुद करना पड़ता है, यानी इसकी ज़िम्मेदारी प्रोग्रामर की होती है, जिससे बग और मेमोरी लीक हो सकते हैं। C++ C जैसी ही है, लेकिन इसमें ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग का सपोर्ट है। यह अब भी काफी लो-लेवल है और इसमें आपको मेमोरी का प्रबंधन खुद करना पड़ता है। C और C++ दोनों में आप inline assembly (ASM) लिख सकते हैं!
और नई सिस्टम भाषाओं का क्या? वे कैसे विकसित हुई हैं?
आज की सभी आधुनिक सिस्टम प्रोग्रामिंग भाषाएँ मेमोरी का अपने-आप प्रबंधन करती हैं, चाहे रेफरेंस काउंटिंग से, चाहे गार्बेज कलेक्टर से, या किसी और तरीके से।
शुरुआती सिस्टम प्रोग्रामिंग भाषाएँ null pointer का सपोर्ट करती थीं (Tony Hoare ने इसे अपनी "billion-dollar-mistake" कहा था)। ये रनटाइम एरर और कमज़ोरियों की वजह बनने के लिए बदनाम हैं। आज की कई आधुनिक भाषाओं ने null को पूरी तरह हटा दिया है, या कम से कम उसका इस्तेमाल करने के लिए मेहनत लगती है।
एक और बदलाव यह हुआ कि पहले वैल्यू डिफॉल्ट रूप से म्यूटेबल होती थीं, अब डिफॉल्ट रूप से इम्यूटेबल होती हैं। जैसे Rust और Vlang दोनों में वैल्यू डिफॉल्ट रूप से इम्यूटेबल होती हैं, और म्यूटेबिलिटी इस्तेमाल करनी हो तो अलग से चुनना पड़ता है।
सभी नई भाषाओं में C (या C++) के साथ इंटरॉप का सपोर्ट है, क्योंकि इन भाषाओं में बहुत सारा कोड लिखा जा चुका है।
एक और दिलचस्प बात यह है कि कुछ नई भाषाएँ सीधे मशीन कोड में कंपाइल नहीं होतीं, बल्कि इसके लिए दूसरे टूल का इस्तेमाल करती हैं। जैसे Rust और Zig, LLVM का इस्तेमाल करती हैं, जबकि Nim में कई तरह के कंपाइलर चुन सकते हैं। इसे ट्रांसपाइलिंग कहते हैं।
और मैक्रो और मेटाप्रोग्रामिंग जैसी चीज़ों का क्या?
मैक्रो और मेटाप्रोग्रामिंग को लेकर एक दिलचस्प विभाजन दिखता है। C/C++ के मैक्रो शक्तिशाली हैं, लेकिन इनके साथ काम करना मुश्किल होता है, यही इनकी कुछ बदनामी है। Rust, Nim और D तीनों शक्तिशाली मेटाप्रोग्रामिंग देती हैं, पर कहीं बेहतर तरीके से। दूसरी तरफ, VLang और Zig दोनों साफ-साफ कहती हैं कि उनकी भाषा में मैक्रो न होना ही एक फीचर है, और Go का तरीका अलग है, वह go generate का इस्तेमाल करती है।
सिस्टम भाषाओं को काफी लो-लेवल माना जाता है। क्या यह अब भी सही है?
नई भाषाएँ C/C++ के मुकाबले ज़्यादा ऊँचे स्तर के एब्स्ट्रैक्शन पर काम करती हैं। जैसे Rust, D और Nim में कोड लिखने का बहुत फंक्शनल तरीका भी अपनाया जा सकता है। Nim और D में तो प्योर फंक्शन की धारणा भी है।
इस महीने की भाषाओं का परिचय
तो अच्छा होगा कि हम हर भाषा को एक-एक करके देखें। इन सभी भाषाओं में कुछ समानताएँ हैं: ये सब स्ट्रॉन्गली टाइप्ड और स्टैटिकली टाइप्ड हैं। लेकिन चलिए देखते हैं कि ये किन बातों में अलग हैं। शुरुआत C से करें?
C
- Dennis Ritchie ने इसे बनाया
- यह दुनिया की सबसे पुरानी और शायद सबसे ज़्यादा इस्तेमाल होने वाली भाषाओं में से एक है
- बहुत सारा सॉफ्टवेयर C में लिखा गया है, जैसे Unix और Linux
- इसका बहुत गहरा असर है (सोचिए: C जैसी भाषाओं का चलन ही इसी की देन है)
- मेमोरी का प्रबंधन खुद करना पड़ता है
- बेहद तेज़ (हार्डवेयर के बहुत करीब)
- कहीं भी चलती है
- एम्बेडेड सिस्टम के लिए एकदम सही
- काफी छोटी भाषा
C++
- Bjarne Stroustrup ने इसे बनाया
- C की उत्तराधिकारी, पर इसमें ऑब्जेक्ट-ओरिएंटेशन जोड़ा गया (क्लासों वाली C)
- ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग को लोकप्रिय बनाने में मदद की
- C के मुकाबले इसमें ज़्यादा हाई-लेवल भाषा सुविधाएँ हैं
- टेम्पलेट के ज़रिए जेनेरिक प्रोग्रामिंग का सपोर्ट करती है
- नेमस्पेस के ज़रिए मॉड्यूल का सपोर्ट जोड़ती है
- कई गेम (इंजन) C++ में लिखे गए हैं, और Windows के बड़े हिस्से भी
- मेमोरी का प्रबंधन खुद करना पड़ता है
- अब भी विकसित हो रही है और नियमित रूप से नए फीचर जुड़ते रहते हैं (बड़ा स्पेक)
D
- Walter Bright ने इसे बनाया, बाद में Andrei Alexandrescu भी जुड़े
- शुरू में इसे C++ को फिर से डिज़ाइन करने के रूप में देखा गया (उसकी "गलतियों" से सीखते हुए), और इसने कई दूसरी भाषाओं से प्रेरणा ली
- मल्टी-पैराडाइम, यानी इम्पेरेटिव, ऑब्जेक्ट-ओरिएंटेड और फंक्शनल प्रोग्रामिंग तीनों का सपोर्ट
- C/C++ के साथ आसान इंटरॉप
- यूनिफॉर्म फंक्शन कॉल सिंटैक्स
- कंपाइल टाइम फंक्शन इवैल्यूएशन (जैसे कंपाइल के समय Regex स्टेट मशीन बनाना)
- फंक्शनल प्रोग्रामिंग और प्योर फंक्शन का सपोर्ट करती है
- सुरक्षा के कई फीचर
- @safe के ज़रिए मेमोरी सेफ्टी
- कॉन्ट्रैक्ट (प्री/पोस्ट शर्तें, इनवेरिएंट)
- प्योर फंक्शन
- यूनिट टेस्ट पर ज़ोर, और टेस्ट उसी सोर्स कोड के पास रहते हैं जिन्हें वे जाँचते हैं (Exercism वेबसाइट पर इसके लिए एक अपवाद बनाना पड़ा :) )
- यूनिकोड
Rust
- Mozilla Research के कर्मचारी Graydon Hoare ने इसे बनाया, फिर Mozilla ने इसे आधिकारिक रूप से अपनाया, और अब यह Rust Foundation का हिस्सा है
- मल्टी-पैराडाइम: ऑब्जेक्ट-ओरिएंटेड (पर अपनी साफ राय के साथ, जैसे इनहेरिटेंस नहीं), इम्पेरेटिव और फंक्शनल प्रोग्रामिंग (Option/Result टाइप, पैटर्न मैचिंग)
- बहुत से नए टूल Rust में लिखे गए हैं (जैसे SWC, और Gleam भी, साथ ही Linux kernel की दूसरी समर्थित भाषा; कोड की लाइनें गिनने वाला हमारा काउंटर भी इसी पर निर्भर है)
- भरोसेमंदी और परफॉर्मेंस पर ज़ोर
- LLVM पर बनी है
- पिछले 7 साल से StackOverflow सर्वे में सबसे पसंदीदा भाषा
- तेज़, जिसकी एक वजह इसका छोटा कोर और स्टैंडर्ड लाइब्रेरी है
- सुरक्षित: ओनरशिप और लाइफटाइम के ज़रिए मेमोरी और थ्रेड दोनों की सेफ्टी, और डिफॉल्ट रूप से इम्यूटेबल
- शक्तिशाली टाइप सिस्टम, जो कंपाइल के समय ही बहुत से बग पकड़ लेता है (खासकर मेमोरी से जुड़े)। कंपाइलर बहुत काम की एरर दिखाता है
- सब कुछ पहले से शामिल: कंपाइलर, बिल्ड टूल, फॉर्मैटर, पैकेज मैनेजर, IDE इंटिग्रेशन
- बढ़िया डॉक्युमेंटेशन (कंपाइलर कैसे काम करता है, इस पर एक बहुत बड़ा दस्तावेज़ भी है)
- पोर्टेबल: एक ही स्टैटिक बाइनरी में कंपाइल होती है और इंस्टॉल करने के लिए रनटाइम की ज़रूरत नहीं पड़ती
- C कोड के साथ आसान इंटरॉप
- ज़ीरो-कॉस्ट एब्स्ट्रैक्शन
- फियरलेस कॉन्करेंसी
- मैक्रो
Nim
- Andreas Rumpf ने इसे बनाया (पहले इसका नाम Nimrod था)
- इसका सिंटैक्स Python से प्रेरित है
- मल्टी-पैराडाइम
- Exercism में configlet के लिए इस्तेमाल होती है
- बढ़िया परफॉर्मेंस: ज़ीरो ओवरहेड इटरेटर, और वैल्यू-आधारित टाइप को स्टैक पर आवंटित करने को प्राथमिकता
- आधुनिक और अभिव्यक्तिपूर्ण टाइप सिस्टम: टाइप इन्फरेंस, ट्यूपल, जेनेरिक, सम टाइप, async/await
- गार्बेज कलेक्शन, पर साथ में डिटरमिनिस्टिक मेमोरी मैनेजमेंट का सपोर्ट (मेमोरी मैनेज करने के कई विकल्प)
- कंपाइल के समय कोड चलाना
- यूनिफॉर्म कॉल सिंटैक्स
- मैक्रो: भाषा को आसानी से बढ़ाया जा सकता है
- इफेक्ट सिस्टम: साइड इफेक्ट को टाइप सिस्टम में दर्ज करना
Go
- Google में Robert Griesemer, Rob Pike और Ken Thompson ने इसे बनाया
- ज़्यादातर इम्पेरेटिव/प्रोसीजरल, और ऑब्जेक्ट-ओरिएंटेड जैसा तरीका भी अपनाती है (पर इनहेरिटेंस के बिना)
- Docker, Kubernetes जैसे बड़े प्रोजेक्ट में इस्तेमाल होती है। बैकएंड और CLI के लिए भी बहुत अच्छी है (जैसे esbuild)
- इसका लक्ष्य है इतनी आसान होना कि पूरी भाषा दिमाग में समा जाए (सिंटैक्स बहुत कम)
- गार्बेज कलेक्टर के ज़रिए मेमोरी सेफ्टी
- तेज़: तेज़ कंपाइलेशन, तेज़ टेस्टिंग और तेज़ रनटाइम। बेंचमार्क लिखने का सपोर्ट पहले से मौजूद!
- अपनी साफ राय रखती है: Go कोड की स्टाइल तय करने पर खूब मेहनत की गई है: कम सिंटैक्स, go fmt से कोड फॉर्मैट करना, आइडियोमैटिक इस्तेमाल की जाँच करने वाले टूल, दूसरी भाषाओं में जो चेतावनी भर होते हैं उन पर सीधे एरर देना (जैसे बिना इस्तेमाल किए वेरिएबल), और Go के इस्तेमाल करने योग्य आइडियम की सूची देने वाला डॉक्युमेंटेशन
- हल्का टाइप सिस्टम, जो Go को बहुत लचीला बनाता है (टाइप इन्फरेंस का सपोर्ट)
- पोर्टेबल: एक ही स्टैटिक बाइनरी में कंपाइल होती है और इंस्टॉल करने के लिए रनटाइम की ज़रूरत नहीं पड़ती। आसान क्रॉस-कंपाइलेशन। गोरूटीन (हल्के) के ज़रिए कॉन्करेंसी और चैनल के ज़रिए आपसी संवाद
- इंटरफेस के ज़रिए स्ट्रक्चरल टाइपिंग (डक टाइपिंग जैसा, पर स्टैटिकली जाँचा गया)
- एरर हैंडलिंग: भाषा आपको प्रेरित करती है कि एरर जाँचें और उनसे निपटें
VLang
- Alexander Medvednikov और Delyan Angelov ने इसे बनाया
- Go से प्रेरित:
- वही "काम करने का एक ही तरीका" नीति
- वही "कम से कम सिंटैक्स" नीति
- कोरूटीन
- Go से अलग:
- nil/null नहीं, बल्कि result टाइप का इस्तेमाल करती है
- डिफॉल्ट रूप से इम्यूटेबल
- सम टाइप (फंक्शनल)
- स्ट्रिंग इंटरपोलेशन
- छोटा रनटाइम/छोटी बाइनरी
- पैटर्न मैचिंग
- GC को वैकल्पिक बनाने पर काम चल रहा है (autofree)
- ज़ीरो कॉस्ट C इंटरॉप
- कोड से डॉक्युमेंटेशन बनाती है
- तेज़ कंपाइलर, जो कम मेमोरी लेता है
Zig
- Andrew Kelley ने इसे बनाया
- सिंटैक्स काफी कम (PEG ग्रामर की 500 लाइन की फाइल)
- कंपाइल के समय कोड चलाना और रिफ्लेक्शन
- इसका लक्ष्य है "स्पष्ट" होना: कोई छिपा कंट्रोल फ्लो नहीं, न छिपा आवंटन, न मैक्रो/मेटाप्रोग्रामिंग
- मेमोरी का आवंटन खुद करना पड़ता है
- अलग-अलग एलोकेटर का सपोर्ट
- स्टैंडर्ड लाइब्रेरी के जो फंक्शन मेमोरी आवंटित करते हैं, उनमें एलोकेटर पैरामीटर लगता है
- टेस्ट फ्रेमवर्क मेमोरी लीक पकड़ सकता है
- सुरक्षा:
- एरर वैल्यू होती हैं और उन्हें हैंडल करना ज़रूरी है
- null नहीं, optional टाइप का इस्तेमाल
- टेस्ट सोर्स की उसी फाइल में लिखे जा सकते हैं (D की तरह)
- बैकएंड के रूप में LLVM का इस्तेमाल करती है
- C/C++ कोड कंपाइल कर सकती है
- आसान C इंटरॉप
- आसान क्रॉस-कंपाइलेशन
निष्कर्ष
हम यहीं रुकते हैं, क्योंकि मुझे लगता है कि इस समय तक सबका दिमाग काफी भर चुका होगा।
उम्मीद है कि इस महीने की फीचर्ड भाषाओं का यह परिचय काम का और मज़ेदार रहा होगा। आशा है कि इन भाषाओं को खोजते हुए आपको बहुत मज़ा आएगा। Erik और मैं दोनों यह जानना चाहेंगे कि आपने कौन-सी भाषाएँ चुनीं और वे आपको कैसी लगीं, इसलिए कृपया कमेंट में या फोरम पर ज़रूर लिखिए!
देखने के लिए धन्यवाद!