1 पॉइंट द्वारा GN⁺ 2025-01-08 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Zig का compile-time (comptime) metaprogramming रनटाइम कोड की तरह पढ़ा जा सकता है, फिर भी यह struct field sum जैसे दोहराव वाले कोड के लिए हाथ से लिखे गए C function जितना efficient executable code बना सकता है
  • अलग generic syntax के बजाय comptime T: type और anytype का उपयोग करके generic programming करना, Zig comptime का एक मुख्य उपयोग है
  • runtime, comptime और build system — तीनों एक ही Zig भाषा साझा करते हैं, इसलिए Fizz Buzz जैसी function को compile के दौरान चलाकर पहले से गणना किया हुआ array बनाया जा सकता है
  • compiler जहाँ संभव हो पहले ही evaluation कर देता है, और जिन हिस्सों में runtime values चाहिए केवल उन्हें बाद में machine code बनने वाले output bytecode के रूप में छोड़ता है
  • Zig comptime text-based code generation जैसे कई काम कर सकता है, लेकिन C macro की तरह keywords को मनमाने ढंग से replace नहीं करता, इसलिए code reading और debugging का बोझ कम होता है

Zig comptime को पढ़ने का नज़रिया

  • Zig का comptime metaprogramming feature है, लेकिन अंतिम behavior को पढ़ते समय compile time और runtime के अंतर को बहुत ज़्यादा सचेत होकर सोचने की ज़रूरत नहीं होती
  • zig zen की तीसरी पंक्ति “Favor reading code over writing code” यहाँ एक महत्वपूर्ण मानदंड बनती है
    • macro या code-generation आधारित metaprogramming मूल code और expanded code की दो परतें बना सकती है
    • इस तरह की अप्रत्यक्ष परतें code पढ़ना, debugging करना और behavior बदलना कठिन बना देती हैं
  • array [3]i64 = .{1,2,3} का sum निकालने वाले runtime code और struct MyStruct के fields a, b, c का sum करने वाले comptime code की तुलना साथ-साथ की गई है
    • inline for (comptime std.meta.fieldNames(MyStruct)) से field names पर iterate किया जाता है
    • @field(my_struct, field_name) से compile time पर ज्ञात field name का उपयोग करके field access किया जाता है
  • struct field summation का उदाहरण runtime reflection जैसा दिख सकता है, लेकिन executable में उस struct type के लिए हाथ से लिखे गए sum function जैसा efficient code ही बचता है
  • उदाहरण में दिखाया गया transformation, Zig implementation की सटीक पुनर्रचना नहीं बल्कि conceptual explanation के अधिक करीब है
  • Zig core team ऐसे debugger पर काम कर रही है जो उदाहरण की तरह comptime और runtime के मिले-जुले code को step-by-step चला सके

Generics को comptime से संभालना

  • Zig में अलग से कोई generic feature नहीं है
  • किसी type को generic बनाना हो तो type लेने वाला function लिखकर उससे type return कराया जाता है
    • उदाहरण: pub fn GenericMyStruct(comptime T: type) type
    • returned struct के fields a, b, c सभी T type का उपयोग करते हैं
  • generic function भी इसी तरह लिखे जा सकते हैं
    • उदाहरण: fn quadratic(comptime T: type, a: T, b: T, c: T, x: T) T
    • quadratic(f32, ...) और quadratic(i64, ...) की तरह type argument स्पष्ट रूप से दिया जा सकता है
  • अगर argument type function signature के दूसरे हिस्सों में महत्वपूर्ण न हो, तो special type anytype से inference किया जा सकता है
  • केवल इसी तरीके से पूरे comptime को समझाया नहीं जा सकता, लेकिन Zig में आम generic tasks को समझने की यह अच्छी शुरुआत है

वही Zig code compile time पर चलाना

  • Zig runtime, comptime और build system में एक ही भाषा का उपयोग करता है
  • Fizz Buzz के उदाहरण में पहले fizzBuzz(writer) function को सामान्य runtime output function की तरह लिखा जाता है
    • 1 से 100 तक iterate किया जाता है
    • 3 और 5 के multiples पर fizzbuzz, 3 के multiples पर fizz, 5 के multiples पर buzz, और बाकी पर संख्या output होती है
  • उसी fizzBuzz function को comptime block के भीतर चलाने पर पूरा output पहले से compute किया जा सकता है
    • std.io.countingWriter(std.io.null_writer) से पहले ज़रूरी byte count निकाला जाता है
    • computed length से buffer: [cw.bytes_written]u8 array बनाया जाता है
    • std.io.fixedBufferStream(&buffer) में दोबारा output लिखकर full_fizzbuzz value बनाई जाती है
  • अगर केवल मुख्य हिस्से को मापा जाए, तो precomputed version लगभग 9 गुना तेज़ चलता है
    • उदाहरण छोटा होने के कारण कुल execution time पर दूसरे factors का असर ज़्यादा हो सकता है
  • comptime और runtime में उपलब्ध values और features अलग होते हैं
    • comptime_int, comptime_float, type values केवल comptime में उपलब्ध होती हैं
    • कुछ functions केवल comptime arguments लेते हैं, इसलिए वे व्यवहार में comptime-only हो जाते हैं
    • system calls या उन पर आधारित features केवल runtime में उपलब्ध होते हैं
  • ऐसे विशेष features का उपयोग न करने वाला code comptime और runtime दोनों में एक जैसा चलता है

Partial evaluation के रूप में comptime को समझना

  • comptime को compile प्रक्रिया के दौरान होने वाली partial evaluation के रूप में देखा जा सकता है
  • partial evaluation वह तरीका है जिसमें function को कुछ arguments पहले से देकर, ज्ञात values पर निर्भर expressions को पहले substitute और evaluate किया जाता है, और अंत में अज्ञात arguments लेने वाला नया function बनता है
  • array summation का उदाहरण evaluation process को चरणबद्ध ढंग से दिखाता है
    • for loop को हर iteration के अलग-अलग statements में तोड़ा जाता है
    • array[0], array[1], array[2] को क्रमशः 1, 2, 3 से replace किया जाता है
    • sum को 0 → 1 → 3 → 6 की तरह update किया जाता है
    • अंत में यह std.debug.print(..., .{6}) जैसी form तक simplify हो जाता है
  • struct field summation वाले उदाहरण को भी इसी तरह पढ़ा जा सकता है
    • inline for comptime पर execute होता है, इसलिए यह fields "a", "b", "c" के लिए अलग-अलग statements में expand हो जाता है
    • @field(my_struct, "a") बदलकर my_struct.a बन जाता है
    • अंतिम function में सीधे sum += my_struct.a, sum += my_struct.b, sum += my_struct.c जैसी lines होती हैं
  • यह तरीका intent को सीधे code में व्यक्त करते हुए भी, field बदलने पर sum function को manually update करने वाली comment की ज़रूरत कम कर देता है

Compile-time evaluation और runtime code emission

  • Zig comptime को compile-time evaluation और runtime code emission के संयोजन के रूप में भी देखा जा सकता है
  • input code को compiler द्वारा execute किया जाता है
    • जो statements compile time पर ज्ञात हैं, उन्हें तुरंत evaluate कर लिया जाता है
    • जिन statements को runtime values चाहिए, उन्हें output code में जोड़ दिया जाता है
  • यह नज़रिया Zig compiler के वास्तविक processing model के सबसे करीब है
    • Zig पहले syntax parse करता है
    • code को virtual machine के लिए bytecode में बदलता है
    • VM जहाँ तक संभव हो evaluate करती है, और जहाँ runtime processing चाहिए वहाँ नया bytecode emit करती है
    • यह नया bytecode बाद में machine code में बदलता है
  • if जैसे conditionals जिनमें runtime input हो, उनके दोनों paths emit किए जाते हैं
  • dead code का semantic analysis नहीं किया जाता
    • गलत function लिखे जाने पर भी, जब तक उसका वास्तविक उपयोग न हो, compile error नहीं आ सकता
    • यह गुण compilation को अधिक efficient बनाता है और #ifdef के बिना स्वाभाविक conditional compilation संभव करता है
  • Zig में type names भी comptime पर evaluate होने वाले type values के expressions हैं
    • function argument type जैसे सरल दिखने वाले type names भी comptime evaluation का परिणाम होते हैं
    • यही गुण ऊपर दिए गए generic examples को संभव बनाता है
    • ज़रूरत पड़ने पर अधिक जटिल expressions से भी type compute किया जा सकता है
  • Zig का static analysis कई अन्य static type languages की तुलना में अधिक जटिल है
    • सभी types जानने के लिए compiler के बड़े हिस्से को वास्तव में चलाना पड़ सकता है
    • tools के पूरी तरह पकड़ बनाने तक, code completion जैसे editor features हमेशा अच्छी तरह काम नहीं करते

Text-based code generation से संबंध

  • Zig comptime और text-based code generation का रूप अलग है, लेकिन वे समान तरह की समस्याएँ हल कर सकते हैं
  • code-generation function writeSumFn, writer.print से sumFields function का source code output करती है
    • generator में चलने वाला code, Zig comptime वाले हिस्से के बराबर है
    • generator से निकलने वाला code runtime code के बराबर है
  • Zig का comptime version उसी logic को अधिक सीधे ढंग से व्यक्त करता है
    • std.meta.fieldNames(MyStruct) से field names मिलते हैं
    • inline for और @field से field access code बनाया जाता है
  • type information को input के रूप में लेने वाला code generation, Zig में अधिक सरल हो जाता है
    • text generation approach में type name और field name की जानकारी कहाँ से आएगी, यह अलग से हल करना पड़ता है
    • Zig में type और field information को comptime पर सीधे handle किया जा सकता है
  • अगर input बाहरी specification जैसा हो, तो @embedFile से file contents शामिल करके उसे सामान्य तरीके से parse किया जा सकता है
  • generic struct उदाहरण में comptime, text-based code generation की तुलना में type name references को अधिक सीधे तरीके से संभालता है
    • text generation में MyStruct_{s} जैसे नाम strings जोड़कर लगातार सही रखने पड़ते हैं
    • comptime में GenericMyStruct(T) की तरह function को सीधे उपयोग किया जाता है
  • कुछ अपवाद भी हैं
    • ऐसे types बनाए जा सकते हैं जिनके field names comptime पर तय होते हैं
    • इस स्थिति में field definitions की सूची वाली specification के साथ built-in function call करनी होती है
    • ऐसे types में methods जैसी declarations define नहीं की जा सकतीं
    • यह code expressiveness को सीमित नहीं करता, लेकिन दूसरे code के सामने expose किए जा सकने वाले API के रूप को सीमित करता है
  • C जैसे text macros से संभव अधिकांश व्यावहारिक काम comptime से भी किए जा सकते हैं, लेकिन code का रूप लगभग पूरी तरह अलग हो सकता है
  • Zig, text macro की तरह keywords को किसी दूसरे नाम से replace करने की सुविधा नहीं देता

आगे पढ़ने के लिए और standard library के उदाहरण

  • Zig केवल comptime पर निर्भर भाषा नहीं है; अधिक जानकारी official website पर मिल सकती है
  • language reference comptime की ठोस features को समझाती है
  • Zig standard library में comptime उपयोग के उदाहरण मिलते हैं
    • std.debug.print द्वारा उपयोग किया जाने वाला format function एक शक्तिशाली generic function है
      • कई भाषाएँ format string को runtime पर parse करती हैं और early error detection के लिए अलग validator जोड़ सकती हैं
      • Zig format string को comptime पर parse करके efficient output code बनाता है और सारी validation भी compile time पर करता है
    • ArrayList अपेक्षाकृत सरल लेकिन पूर्ण-featured generic container है
    • Zig का main function कई return types रख सकता है, और यह compiler magic नहीं बल्कि सामान्य comptime code से संभाला जाता है

1 टिप्पणियां

 
GN⁺ 2025-01-08
Hacker News की राय
  • compile-time programming में पहले से पहचानी जा चुकी समस्याओं की बिना आलोचना किए तारीफ करने के बजाय, काश इन्हें और गहराई से लिया जाता
    staged programming नई नहीं है, और Lisp परिवार में भी दशकों से इसी तरह का काम होता रहा है, लेकिन उस प्रक्रिया में कई design trade-offs और समस्याएं सामने आई हैं। उदाहरण के लिए, इस तरह के generics parametricity को तोड़ देते हैं, जिससे सिर्फ function के type signature को देखकर अनुमान लगाना मुश्किल हो जाता है। Zig recursive generic types को कैसे संभालता है, यह भी अस्पष्ट है, और type checking तथा compile-time computation कब और किस क्रम में interact करते हैं, यह भी एक अहम design choice है। लेख कहता है कि compile-time code सिर्फ values ही नहीं, code भी generate कर सकता है, लेकिन hygiene पर बात नहीं करता। संबंधित चर्चा के लिए https://typesanitizer.com/blog/zig-generics.html अच्छा है

    • मुझे Zig काफी पसंद है और मैं इसे कई साल से देखता रहा हूं और कभी-कभी इस्तेमाल भी करता रहा हूं, लेकिन comptime खासकर generics, जटिल data structures की compile-time initialization, और target-specific code generation जैसे मामलों में चमकता है
      हालांकि दूसरी स्थितियों में Zig code में comptime देखकर कभी-कभी “अ…” जैसा लगता है। Lisp macros की तरह, इसे ऐसी समस्याओं से बचने के लिए इस्तेमाल करना आसान है जो असल में होती ही नहींं, या code structure बेहतर बनाने पर गायब हो सकती हैं। मूल लेख में struct fields पर iterate करके values जोड़ने वाला उदाहरण असल में लोगों द्वारा comptime इस्तेमाल करने के तरीके का typical case है, लेकिन कई मामलों में std.enums.EnumArray जैसे वास्तव में iterable data structure का उपयोग करना बेहतर होता है
    • मैं जानना चाहता हूं कि parametricity टूटने का मतलब practical programmer के function की behavior समझने के स्तर पर है, या type lambda calculus जैसे type theory system में reasoning करने के स्तर पर
      सैद्धांतिक तौर पर चिंता साफ है, लेकिन practical languages में यह कितना महत्वपूर्ण है, यह अलग सवाल है। C++ templates भी template specialization के जरिए parametricity तोड़ते हैं, लेकिन practice में यह आमतौर पर बड़ी समस्या नहीं बनता और उल्टा optimization को संभव बनाता है। उदाहरण के लिए std::vector<bool> जैसी implementation, जिसमें हर element सचमुच 1 bit में store होता है, ऐसी flexibility होने पर ही संभव है
    • लेखक के तौर पर, यह लेख एक meetup में समझाने की कोशिश ठीक से न कर पाने के बाद उसे व्यवस्थित करने के लिए लिखा गया था, इसलिए इसका focus criticism से ज्यादा explanation पर था
      मुझे लगता है कि Zig-style comptime, ज्यादा statically defined function signatures की तुलना में inferability बनाम expressiveness का सीधा trade-off है। यह केवल उस code को प्रभावित करता है जो types के जरिए ऐसी inference करता है, और जरूरत के हिसाब से चुना जा सकने वाला हिस्सा है। जब type signature काफी नहीं होता था, तब भी source की कुछ दर्जन lines पढ़ने पर usage आमतौर पर समझ आ जाती थी, और लंबे समय तक confusion नहीं रहा। recursive generic type example में declaration के दौरान उसी name का उपयोग करने पर “dependency loop detected” आता है, लेकिन workaround है, और लेख का generic example भी खुद को reference करता है। type checking comptime के दौरान होती है; उदाहरण के लिए पहला @compileLog("Hi") execute होता है, लेकिन उसके बाद const a: u32 = "42"; पर type error आने पर दूसरा log तक नहीं पहुंचता। comptime और runtime के बीच type checking में subtle problems हो सकती हैं, लेकिन वे काफी मुश्किल code में ही दिखती हैं और आसानी से हल हो जाती हैं; core team को भी इनके बारे में पता है, इसलिए उम्मीद है कि 1.0 से पहले इन्हें address किया जाएगा। hygiene से आपका सटीक मतलब क्या है, इसके लिए और explanation चाहिए
    • सिर्फ function declaration देखकर infer कर पाना compile-time execution से अलग बात है
      अगर function का logical purpose साफ हो और उसके name तथा parameter names अच्छे रखे गए हों, तो declaration से ही infer किया जा सकता है। parameter labels, type names वगैरह जैसे हिस्से जिन्हें programmer specify कर सकता है, उन्हें भी name का हिस्सा माना जा सकता है। linked article के title वाले conclusion से मैं सहमत हूं, लेकिन वह meaningful argument से ज्यादा points बटोरने वाली debate-team style writing जैसा लगता है। बेहतर framing flexibility बनाम complexity है। fixed generic system, अगर अच्छी तरह design किया गया हो, तो programmable system से सरल होता है लेकिन कम flexible होता है। Zig की खूबी यह है कि metaprogramming language लगभग सामान्य language जैसी ही है, और वह सामान्य language खुद भी simple है, जिससे extra complexity cost कम होती है। हालांकि compile-time code और runtime code मिल जाते हैं और clues कम होते हैं, इसलिए programmer के लिए यह अलग करना मुश्किल हो सकता है कि क्या कब execute हो रहा है। सोचता हूं कि language server या editor plugin comptime code को अलग background color देने वाला “comptime shader” दे तो कैसा रहेगा
    • यह चर्चा 15-minute demo पर आधारित है और असल trade-offs की समझ कम है, यह बात चुभती है। linked Varun Gandhi का लेख शानदार है
      Rust के अनुभव से, लोग const generics से जो कई चीजें करना चाहते हैं, वे comptime जैसी feature होने पर शायद आसान होंगी। parametricity बनाए रखते हुए const generics पर arithmetic allow करना implement करना कठिन है, और अगर वास्तव में चाहिए बस “output size N वाला hash function trait” जैसा कुछ है, तो उस उद्देश्य के लिए parametricity छोड़कर N से trait को पिछले code generation stage में बदल देना भी ठीक हो सकता है। लेकिन Rust macros उस तरह इस्तेमाल करने के लिए बहुत flexible और झंझट वाले हैं। फिर भी जिस क्षण parametric polymorphism को naive code generation feature से replace करते हैं, मुश्किलों का रास्ता खुल जाता है
  • D में यह 17 साल पहले से था। D की सुविधाएँ लगातार दूसरी languages में जाती रही हैं
    Zig comptime keyword से उन blocks को mark करता है जो compile के दौरान execute होंगे, लेकिन D में execution keyword से नहीं, बल्कि इस बात से तय होता है कि expression constant expression है या नहीं। उदाहरण के लिए int s = sum(3, 4); runtime पर execute होता है और enum e = sum(3, 4); compile time पर execute होता है। अगर non-constant globals, input/output, और malloc() जैसे system function calls से बचें, तो काफी functions बिना बदले compile time पर execute हो सकते हैं। D की automatic memory management की वजह से memory allocation भी संभव है

    • मेरी पसंदीदा uses में से एक static table generation है
      पहले static table बनाने के लिए अलग program लिखना पड़ता था, लेकिन compile-time function execution से अब इसकी जरूरत नहीं रही। array tytab की initial values एक lambda से आती हैं जो array calculate करके return करता है। पूरा example https://github.com/dlang/dmd/blob/master/compiler/src/dmd/ba... पर है। CTFE का एक और common use DSL बनाना है
    • D बनाने के लिए धन्यवाद कहने का मौका नहीं छोड़ना चाहता। हालांकि compile-time execution की limits को लेकर जिज्ञासा है
      अगर compiler जिस machine पर चल रहा है और program जिस machine पर चलेगा वे अलग हैं, तो D compiler correctness कैसे guarantee करता है? उदाहरण के लिए, compiler को कैसे पता कि int s = sum(100000, 1000000) सभी x86 machines पर वही value देगा? CPU generations के बीच subtle differences हो सकते हैं; असल में host पर की गई calculation target machine पर भी वही result देगी, इसकी guarantee कैसे दी जाती है? या फिर मान लिया जाता है कि architecture same हो तो host और target पर्याप्त रूप से similar हैं?
    • D का ImportC भी C code से CTFE कर सकता है
      int sum(int a, int b) { return a + b; } रखकर _Static_assert(sum(3, 4) == 7, "look ma, check at compile time!"); की तरह इस्तेमाल किया जा सकता है। समझ नहीं आता C standard इसे add क्यों नहीं करता, और यह अच्छी तरह काम करता है
    • निष्पक्ष तौर पर कहें तो Zig भी उसी function को runtime context और comptime context, दोनों में call कर सकता है
      square(2) runtime call है और comptime square(3) compile-time call है। अगर कुछ compile-time compatible नहीं है तो comptime call compile error देता है, और मुझे यह एक अहम feature लगता है। क्योंकि जिस code के compile time पर execute होने की उम्मीद थी, वह input arguments बदलने की वजह से गलती से runtime evaluation में चला जाए तो यह warning signal बनता है
  • Zig दिलचस्प लगता है, लेकिन काश इसमें operator overloading होती
    operator overloading के खिलाफ ज्यादातर arguments ज्यादा convincing नहीं लगते। यह दावा कि अंदर असल में क्या हो रहा है पता नहीं चलता, पर्याप्त नहीं है, क्योंकि add नाम का function भी multiplication करने के लिए बनाया जा सकता है। C++ के iostreams या boost::spirit अक्सर abuse cases के रूप में सामने आते हैं, लेकिन operator overloading वाली दूसरी languages में ऐसा ज्यादा नहीं दिखता और यह C++-specific समस्या लगती है

    • OCaml-style solution Zig के use case में अच्छी तरह fit हो सकता है
      OCaml में operators को redefine किया जा सकता है, लेकिन केवल किसी दूसरे module के context के अंदर। उदाहरण के लिए अगर Vec3 module में + को redefine किया गया है, तो Vec3.(a + b + c + d) या let open Vec3 in a + b + c + d की तरह लिखा जा सकता है। source पढ़ते समय “यह + कहाँ से आया?” जैसा सवाल नहीं रहता, और यह a.add(b).add(c).add(d) से कहीं बेहतर है। हालांकि Zig अब काफी stabilize हो रहा है, इसलिए ऐसी समस्या हल करने वाला बड़ा change आना मुश्किल लगता है
    • Python और PHP की __ functions के पीछे कितनी magic चलती है, यह पता नहीं हो सकता
      Zig का approach fresh है, और code follow कर पाने की क्षमता कुछ और characters type करने में लगने वाले कुछ seconds से ज्यादा महत्वपूर्ण है
    • operator overloading नापसंद करने की कई वजहें unexpected runtime performance से जुड़ी लगती हैं
      ideal solution यह होगा कि language ऐसे user-defined operators दे जो साफ दिखाएँ कि वे overloaded हैं। उदाहरण के लिए let c = a |+| b जैसे prefix/suffix form में code देखने वाला जान सकता है कि |+| operation असल में function call है। इसका misuse फिर भी हो सकता है, लेकिन मुख्य चिंताओं में से एक कम हो सकती है
    • C++ में operator overloading को मैंने केवल vector/matrix math के लिए इस्तेमाल किया, और उस case में यह सचमुच useful था
      अगर language shading languages की तरह vector math syntax सीधे provide करे तो अच्छा होगा। Zig में कम से कम Clang के Vector Extension से थोड़ा मिलता-जुलता @Vector() type है, लेकिन दुर्भाग्य से यह Extended Vector Extension नहीं है। https://ziglang.org/documentation/master/#Vector और https://clang.llvm.org/docs/LanguageExtensions.html#vectors-... देखें
    • numeric arrays के लिए basic linear algebra operators शायद overloading के बजाय language में सीधे built-in होने चाहिए
      ऐसी कोई proposal पहले से है या नहीं, मुझे ठीक से नहीं पता
  • fieldNames Nim के fieldPairs से काफी मिलता-जुलता दिखता है, और बेहद convenient construct है
    यह efficient serialization को बहुत आसान बनाता है, और हाल ही में मैंने fieldPairs का इस्तेमाल करके किसी type के लिए thread-safety compile-time check लगभग 20 lines में implement किया। मुझे लगता है ऐसी feature programming languages की standard feature होनी चाहिए। Rust में जिन बातों की सबसे ज्यादा कमी लगती है, उनमें से एक यही है; और आखिरी बार जब मैंने try किया था, तब यह untyped macros तक सीमित था। यह बहुत restrictive है। बस उम्मीद करनी पड़ती है कि crate की struct पर serde implement हो, और समान fields वाली struct को programmatically खुद बना भी नहीं सकते

    • एक समय compile-time reflection पर discussion हुआ था, और शायद इसमें ऐसी feature भी शामिल हो सकती थी, लेकिन लगता है वह related drama के साथ गायब हो गई
      काफी अफसोस की बात है। serde जैसी चीजों को compile-time reflection हो तो implement करना कहीं आसान होता
    • compile-time reflection हो तो ORM या web framework जैसे frameworks बनाए जा सकते हैं
      एकमात्र trade-off यह है कि ऐसी library को source code form में include करना पड़ेगा
  • कुछ हद तक पूरा C parser library लिखने के बाद, मुझे यह दावा ठीक से समझ नहीं आता कि भाषा में ही metaprogramming होना ज़रूरी है
    अगर आप structs, serialization, properties, instrumentation आदि generate करना चाहते हैं, तो बस एक सामान्य C program लिखें जो source file को process करके source file output करे, और build script में उसे पहले चला दें। मुझे जिज्ञासा है कि लोग ऐसे meta programs को debug और test कैसे करते हैं। मेरा वाला तो सामान्य C program है, इसलिए बाकी code जैसे ही debugger और tools इस्तेमाल करता है

    • लोग ठीक यही काम करना नहीं चाहते
    • C# में, और अधिक सही कहें तो Roslyn/dotnet में, यह काफ़ी अच्छी तरह उपलब्ध है
      compiler खुद उसी भाषा में लिखा गया है, इसलिए आप ऐसा plugin लगा सकते हैं जो AST को read-only access करे और C# source emit करे। debugging के लिए अच्छा test framework बनाने में थोड़ा अतिरिक्त काम लगता है, लेकिन उसके बाद आप standard unit test framework के अंदर plugin लगे compiler को चला सकते हैं और interactive debugger में देख सकते हैं
    • यह Ryan Fleury आदि द्वारा समर्थन किए गए approach जैसा है, और काफ़ी अच्छा तरीका है
      “C में arbitrary compile-time execution: cl /nologo /Zi metaprogram.c && metaprogram.exe, cl /nologo /Zi program.c। compile-time code native speed पर चलता है, debug किया जा सकता है, और पूरी तरह procedural व arbitrary होता है। compiler को code चलाने की ज़रूरत नहीं है।” https://x.com/ryanjfleury/status/1875824288487571873
    • Zig के बारे में पक्का नहीं, लेकिन Lisp की ताकत S-expressions में है, यानी AST को manipulate करने में
      C में ऐसा करने के लिए source files process करने वाले C program के लिए पूरा C parser लिखना पड़ेगा
    • पहले Python में numba JIT के साथ कुछ ऐसा ही किया था
      Python code generate करने वाला Python code लिखा, और फिर उसे दोबारा compile कराया। यह नाज़ुक और भयानक गड़बड़झाला था, और यही उन बड़े कारणों में से एक था जिनकी वजह से मुझे Python छोड़ना पड़ा। यह कुछ वैसा ही है जैसे पूछना कि क्या सभी function arguments को string के रूप में pass कर देना काफ़ी नहीं होगा। string-based code लिखने वाले लोग होते हैं, लेकिन यह दुर्लभ होना चाहिए, और भाषा को इससे बचने का तरीका देना चाहिए
  • एक दिलचस्प pattern यह है कि compile time पर structs generate किए जा सकते हैं
    मैंने PyTorch में JSON file बनाकर, उसे Zig के @embedFile से पढ़कर, और किसी खास run method वाले structs generate करके neural network implement करने का प्रयोग किया। सिद्धांततः इससे compiler neural network को सीधे optimize कर सकता है। बड़ा लाभ अभी साबित नहीं कर पाया, लेकिन एक बात यह भी है कि पूरा network stack पर रहता है, dynamic allocation नहीं होता। यह अच्छा है या नहीं, पक्का नहीं

    • यह काम मैंने comptime के बजाय Python में code generator लिखकर किया था
      यकीन नहीं कि comptime Zig खास तौर पर तेज़ होगा, और मैं हर बार structs generate करने वाला JSON parser चलाना भी नहीं चाहता
    • जानना चाहूँगा कि इसका compile time पर क्या असर पड़ता है
  • दिलचस्प तो है, लेकिन ऐसे मामलों में मैं “D ने पहले किया था” वाले comments से सहमत हो जाता हूँ

    • सही है, और यह hygienic तरीके से होता है, preprocessor वाले तरीके से भी नहीं
  • अगर Zig के comptime ने आपको प्रभावित किया है, तो compile-time code evaluation और पूरे AST macro system वाले Nim को भी ज़रूर देखना चाहिए

    • Nim मज़ेदार भाषा है, लेकिन शायद “serious” काम के लिए इस पर विचार नहीं करूँगा
      इसमें दूसरी niche languages जैसी ecosystem समस्याएँ हैं, और इसके साथ ही ध्रुवीकरण करने वाला maintainer, लंबे समय तक न टिकते दिखने वाले core contributors, और cryptocurrency company से मुख्य funding जैसे तत्व भी हैं। 10 साल पहले ये बातें परेशान नहीं करतीं, लेकिन अब स्थिति अलग है
    • Zig का फायदा यह है कि इसमें exceptions नहीं हैं
      लगता है Nim भी exceptions से दूर जाने की कोशिश कर रहा है, लेकिन exceptions functions को दूषित कर देते हैं और आपको उन पर ध्यान देने को मजबूर करते हैं, भले ही आप exception throw करने वाले functions का इस्तेमाल न करें[1]। अदृश्य control flow से निपटने के लिए ज़िंदगी बहुत छोटी है। [1]: https://github.com/status-im/nim-stew
  • Zig कुल मिलाकर काफ़ी अच्छी भाषा है और ज़रूरी काम करती है
    अपने उद्देश्य से न भटकना बहुत ज़रूरी है, और इसी वजह से मुझे यह खास पसंद नहीं कि कुछ languages सिर्फ इसलिए हर जगह इस्तेमाल होने लगती हैं क्योंकि वे इस्तेमाल की जा रही हैं

  • काश Zig की metaprogramming क्षमता और Rust का विशाल ecosystem, community और safety जोड़कर कुछ मिलता
    भाषा design के लिहाज़ से मुझे Rust से कहीं ज़्यादा Zig पसंद है, लेकिन एक अनाड़ी hobby programmer के रूप में, कम से कम अभी, मुझे भरोसा नहीं कि मैं Zig में वास्तव में उपयोगी या भरोसेमंद कुछ लिख पाऊँगा

    • Rust से परिचित होने के नाते मैंने Zig को थोड़े समय के लिए आज़माया और जल्दी छोड़ दिया
      क्योंकि compiler ऐसे issues को compile time पर पकड़ने में मदद नहीं कर रहा था। मुझे पता है Zig ऐसा वादा नहीं करता, लेकिन मेरे लिए यह निर्णायक कमी है, इसलिए लगता है Zig मेरे लिए सही भाषा नहीं है। दूसरी ओर comptime concept मुझे Rust macros से बेहतर लगता है
    • चाहता हूँ Rust community Zig से दूर ही रहे। मज़ाक कर रहा हूँ, लेकिन काफ़ी हद तक सच में भी