1 पॉइंट द्वारा GN⁺ 2024-05-10 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Datatype99 pure C99 में algebraic data types, exhaustive pattern matching और compile-time introspection प्रदान करने वाली macro-based library है
  • इसका लक्ष्य गलत type वाले variants, छूटे हुए pattern matching cases और गलत field access को compile time पर पकड़ना है, और इसके लिए केवल standards-compliant C99 compiler चाहिए
  • अंदरूनी तौर पर datatype tagged union और value constructor में, जबकि match switch statement में expand होता है; generate हुआ data layout typed code generation semantics का पालन करता है
  • installation में datatype99.h और dependency Metalang99 को include path में जोड़ना शामिल है, और GCC/Clang में macro expansion error output कम करने के लिए compile options इस्तेमाल करने की सलाह दी जाती है
  • इसे C codebase में #include <datatype99.h> से integrate किया जा सकता है, और यह GCC·Clang·MSVC·TCC पर काम करने के लिए जाना जाता है; C++11 या उससे ऊपर का भी support है

Datatype99 क्या प्रदान करता है

  • Datatype99 C99 में सुरक्षित और सहज algebraic data types प्रदान करता है
  • इसके features में exhaustive pattern matching और compile-time introspection भी शामिल हैं
  • यह external code generation tools के बिना काम करता है, और implementation pure C99 पर आधारित है
  • मुख्य विशेषताएं
    • Type safety: गलत type वाले variants, non-exhaustive pattern matching और गलत field access को compile time पर पकड़ता है
    • Portability: standards-compliant C99 compiler चाहिए; standard library, compiler/platform-specific features या VLA की जरूरत नहीं
    • Predictability: code generation semantics defined हैं, इसलिए generate होने वाला data layout हमेशा समान रहने की guarantee मिलती है
    • समझने योग्य errors: गलत code के लिए library खुद कुछ syntax errors detect करती है
    • वास्तविक उपयोग के उदाहरण: OpenIPC में IP cameras के लिए real-time streaming software development में इस्तेमाल होता है, जिसमें RTSP 1.0 implementation और लगभग 50,000 lines का private code शामिल है

Installation और build settings

  • Datatype99 एक header file datatype99.h और Metalang99 dependency से बना है
  • Project में इस्तेमाल करने के लिए datatype99 और metalang99/include को include directories में जोड़ना होगा
  • GCC में -ftrack-macro-expansion=0, और Clang में -fmacro-backtrace-limit=1 specify करके अनावश्यक macro expansion error output कम करने की सलाह दी जाती है
  • CMake इस्तेमाल करते समय FetchContent की सलाह दी जाती है
    • default datatype99/CMakeLists.txt GitHub Releases से Metalang99 v1.13.5 download करता है
    • इस behavior को पहले FetchContent_Declare call करके override किया जा सकता है
  • Datatype99 पर depend करने वाले headers को precompiled header बनाया जा सकता है, जिससे हर include पर दोबारा compile न होने के कारण compile time कम हो सकता है

इस्तेमाल का तरीका: tagged union को ज्यादा सुरक्षित ढंग से इस्तेमाल करना

  • Datatype99 मूल रूप से tagged union के लिए syntactic sugar है, जो ज्यादा सुरक्षित और concise form देता है
  • सामान्य C में binary tree represent करने के लिए tag enum और union खुद लिखने पड़ते हैं
  • Datatype99 में वही structure इस तरह declare किया जा सकता है
datatype(
    BinaryTree,
    (Leaf, int),
    (Node, BinaryTree *, int, BinaryTree *)
);
  • सामान्य switch approach में case Leaf: के बाद गलती से tree->data.node access करने पर भी compiler warning नहीं दे सकता
  • match और of इस्तेमाल करने पर variant-specific bindings सिर्फ उसी branch में दिखते हैं, और inappropriate access करने पर compilation fail हो जाता है
int sum(const BinaryTree *tree) {
    match(*tree) {
        of(Leaf, x) return *x;
        of(Node, lhs, x, rhs) return sum(*lhs) + * x + sum(*rhs);
    }

    return -1;
}
  • of द्वारा दिए गए bindings x, lhs, rhs जैसे variables होते हैं, और pointer type लेकर value बदली जा सकती है
  • variant creation के लिए internally generated value constructors का इस्तेमाल होता है
BinaryTree leaf5 = Leaf(5);
BinaryTree leaf7 = Leaf(7);
BinaryTree node = Node(&leaf5, 123, &leaf7);

Syntax और generation semantics

  • Datatype99 datatype, record, match, of, otherwise, MATCHES, ifLet जैसी macro syntax प्रदान करता है
  • short macro names और postfixed versions दोनों मौजूद हैं
    • उदाहरण: match99, of99, derive99
    • name conflicts से बचने के लिए datatype99.h include करने से पहले DATATYPE99_NO_ALIASES define किया जा सकता है
    • library headers में postfixed macros इस्तेमाल करने की सलाह दी जाती है
  • datatype ये elements generate करता है
    • forward typedef
    • non-empty variants के लिए structs
    • variant field types के लिए typedef
    • sum type के लिए typedef
    • tag enum और union सहित tagged union
    • हर variant के लिए inline static value constructor
    • derive(...) में specified deriver calls
  • सभी variants empty हों तब भी C standard के अनुसार union में कम से कम एक item होना चाहिए, इसलिए char dummy; डाला जाता है
  • record एक struct है जिसके लिए derivation process defined है, और fields न होने पर भी char dummy; generate करता है
  • match sum type instance को variants से sequentially compare करता है, matching branch के statements execute करता है और फिर next command पर चला जाता है
  • पूर्ण match और ifLet syntax प्रत्येक एक C statement में expand होते हैं
  • MATCHES check करता है कि कोई sum type instance किसी specific variant से संबंधित है या नहीं, true/false में
  • matches deprecated है और MATCHES इस्तेमाल करने की सलाह दी जाती है

derive और helper attributes

  • derive(...) sum type या record के लिए global code generate करने के उद्देश्य से इस्तेमाल होता है
  • sum type का deriver Metalang99-compatible macro form में call होता है, और variants की list tuple list के रूप में pass होती है
  • record का deriver भी Metalang99-compatible macro के रूप में call होता है, और fields की list (<type>, <field-name>) form की tuple list के रूप में pass होती है
  • derive helper attribute वह named argument है जो deriver को pass किया जाता है
  • helper attribute object-like macro form का इस्तेमाल करता है
#define <variant-name>_<namespace>_<attribute-name> attr(/* attribute value */)
  • उपलब्ध attribute manipulation macros
    • DATATYPE99_attrIsPresent / DATATYPE99_ATTR_IS_PRESENT: attribute मौजूद है या नहीं, check करता है
    • DATATYPE99_attrValue / DATATYPE99_ATTR_VALUE: मौजूद attribute की value extract करता है
    • DATATYPE99_assertAttrIsPresent: required attribute न होने पर fatal error generate करता है

ध्यान देने लायक usage patterns

  • of और ifLet को दिए गए statements में top-level break/continue इस्तेमाल नहीं करना चाहिए
    • inner for/while loop के अंदर continue ठीक है
    • top-level control flow को goto label से replace करना चाहिए
  • variant parameter के रूप में array specify करनी हो तो उसे अलग struct में डालना चाहिए
  • of द्वारा introduce किए गए bindings हमेशा mutable होते हैं, इसलिए अगर match को दिया गया value const है तो उसे modify न करने का ध्यान रखें
  • datatype definition को readable बनाए रखने के लिए Clang-Format में // clang-format off और // clang-format on इस्तेमाल किए जा सकते हैं
  • derive helper attribute को namespace pollution से बचने के लिए संबंधित datatype definition के बाद हमेशा #undef करना चाहिए
  • अगर variant parameter का meaning सिर्फ context से clear नहीं है, तो type alias या अलग structure से ज्यादा descriptive name दिया जा सकता है

Errors, IDE और compatibility

  • कुछ syntax errors library खुद detect करती है
    • Bar(int) जैसा non-tuple format
    • missing comma
    • forbidden trailing comma
  • बाकी errors भी सामान्य compiler diagnostics में दिखते हैं
    • non-existent type name
    • non-exhaustive match
    • of में बहुत ज्यादा binders
    • गलत type वाले variant arguments
    • pointer binding को dereference किए बिना return करना
  • अनुभव के अनुसार लगभग 95% errors meaningful दिखाई देते हैं
  • अगर error समझ में न आए तो -E से generated code देखा जा सकता है, और code generation semantics formally defined होने के कारण आम तौर पर unexpected code नहीं निकलता
  • VS Code generated types के लिए suggestions automatically enable करता है, लेकिन macro syntax highlighting support नहीं करता
  • Datatype99 GCC, Clang, MSVC, TCC पर काम करने के लिए जाना जाता है
  • C++11 या उससे ऊपर का भी support है

C को target करने का कारण और सीमाएं

  • pure C में लिखे existing software को Datatype99 के फायदे मिल सकते हैं
  • मौजूदा C codebase में इसे सिर्फ #include <datatype99.h> से integrate किया जा सकता है
  • कुछ environments में embedded devices, Linux और दूसरे operating systems की तरह historical reasons से pure C बनाए रखा जाता है
  • C का stable ABI MetaCall जैसे plugin system projects के लिए महत्वपूर्ण है
  • C को complete specification और कई libraries वाली mature language के रूप में पेश किया गया है
  • अगर ज्यादा modern या high-level language इस्तेमाल की जा सकती है तो पुराने C के बजाय ऐसी language इस्तेमाल करने की सलाह दी जाती है, लेकिन कई लोगों के लिए वह option उपलब्ध नहीं होता या बहुत costly होता है
  • Datatype99 और Metalang99 में फर्क उनके roles में है
    • Metalang99 metaprogramming के लिए functional language है
    • Datatype99 Metalang99 में लिखा गया algebraic data type implementation है

1 टिप्पणियां

 
GN⁺ 2024-05-10
Hacker News की राय
  • जब भी imperative languages इस्तेमाल करता हूँ, algebraic data types की लगभग हमेशा कमी खलती है
    काम में Java इस्तेमाल करनी पड़ती है, और अब मुझे लगता है कि Java उतनी खराब नहीं है जितनी पहले लोग इसकी आलोचना करते थे, लेकिन “काश Java में F# के discriminated union होते” जैसे पल दर्जनों बार आए हैं
    कई तकनीकों से इसकी नकल की जा सकती है और कई मामलों में सिर्फ enum भी काफी होता है, लेकिन ज्यादातर में असली algebraic data types की लचीलापन और संक्षिप्तता नहीं मिलती। सबसे बढ़कर, functional languages में मिलने वाली शानदार pattern matching नहीं होती
    यह C extension मनचाही pattern matching के साथ आता दिखता है, इसलिए काफी अच्छा लग रहा है; देखना होगा कि इसे Arduino project में इस्तेमाल कर सकता हूँ या नहीं
    • जिन लोगों ने algebraic data types और pattern matching इस्तेमाल नहीं किए हैं, वे समझ नहीं पाते कि इसमें खास क्या है; और जो इनके आदी हैं, वे भी तब तक नहीं समझते कि ये कितने खास हैं जब तक उन्हें ऐसी language में काम न करना पड़े जिसमें ये न हों
      जिन्हें अभी-अभी इसका पता चलता है, वे इसे sliced bread के बाद सबसे बड़ा आविष्कार बताते हुए रुकते ही नहीं :)
    • Java 21 के sealed interface से pattern matching संभव है
    • Kotlin JVM के साथ compatible है और उसमें algebraic data types हैं
      Java के लिए https://github.com/functionaljava/functionaljava है; support बंद हो चुका है, लेकिन यह stable है
    • Algebraic data types और pattern matching असल में imperative languages में भी बहुत अच्छी तरह काम करते हैं। उदाहरण के लिए Rust है
  • अगर product को फिर से शुरू से implement करना हो, तो compiler-enforced exhaustive pattern matching वाले discriminated union को अनिवार्य शर्त रखूँगा
    यह इतना powerful है कि इसके बिना काम नहीं चलता
    • Dart में मेरी सबसे बड़ी चाहत union types है, लेकिन अफसोस कि निकट भविष्य में इसके जुड़ने की संभावना नहीं दिखती
      हाल ही में sealed class के ऊपर compiler-enforced pattern matching support आया है, इसलिए लगता है कि आधा रास्ता तय हो गया है
    • अभी ऐसी description से मेल खाने वाली कौन-सी languages हैं? मुझे ठीक-ठीक समझ नहीं आ रहा कि मतलब क्या है, लेकिन support करने वाली language के examples देखूँ तो शायद बेहतर समझ पाऊँ
  • यह निश्चित रूप से बेहतर दिखता है और शायद मेरे पहले बनाए प्रयास से बेहतर काम करेगा [1], लेकिन code 8 गुना ज्यादा है और शानदार मगर थोड़े डरावने Metalang9 macro toolkit पर निर्भर करता है
    अगर देखना हो कि algebraic data types अंदर से कैसे काम करते हैं, तो मुझे लगता है libsum अच्छा शुरुआती resource है
    [1] https://github.com/naasking/libsum
    • उस repository को मैंने star किया हुआ है, लगता है Datatype99 design करते समय इसे देखा था :)
  • यह किसी जादूगर का काम है
    मैं C को लगभग 20 साल से जानता हूँ, लेकिन कभी नहीं सोचा था कि macro system इतनी black magic संभव करने जितना powerful है
    वाकई शानदार
    • लेखक सिर्फ 19 साल का है। अचानक मुझे खुद बेवकूफ जैसा महसूस हो रहा है
    • उसी लेखक का metalang99 भी दिलचस्प हो सकता है
    • x-macro, यानी macros इस्तेमाल करने का यह तरीका, काफी flamboyant है
      पारंपरिक रूप से इसका इस्तेमाल type-safe generics बनाने और access करने, या hardware registers और interrupt definitions में repetitive code घटाने के लिए होता था
      थोड़ा cursed सा लगता है, लेकिन core वास्तव में बहुत simple है, और C-based projects में cognitive complexity और repetitive code घटाने का भरोसेमंद tool है
    • Algebraic data types आम तौर पर generic struct और union पर string substitution के साथ union tag जोड़ना ही होते हैं
      macro usage के लिहाज से यह इतना complex नहीं है
  • Wikipedia पर इससे जुड़ी दिलचस्प बात है। union को “object-oriented programming की class hierarchy” के रूप में implement करने का तरीका
    https://en.wikipedia.org/wiki/Tagged_union#Class_hierarchies...
    इसी विषय पर एक लंबा blog post भी है, लेकिन लगता है लेखक ने अभी Wikipedia का वह section नहीं देखा है
    https://nandakumar.org/blog/2023/12/paradigms-in-disguise.ht...
    datatype99 developer की यह बात सराहनीय है कि उसने README में ऐसे workaround की समस्या सीधे दिखा दी है
  • https://melt.cs.umn.edu/ भी है। इसमें C में templates और algebraic data types जोड़ने वाला extension है
    https://github.com/melt-umn/ableC-template-algebraic-data-ty...
  • “of और ifLet को दिए गए statement के अंदर top-level break/continue का इस्तेमाल न करें, उसकी जगह goto label इस्तेमाल करें” — यह काफी बड़ा foot-gun लगता है
    फिर भी बाकी सब बहुत बढ़िया है
    • goto इस्तेमाल करना अपने-आप में समस्या नहीं है, लेकिन यह पता होना चाहिए कि ऐसे blocks के अंदर break/continue इस्तेमाल नहीं करना है
      पहले मैंने macros से immediate mode UI बनाया था, तो यह बात याद आई। मेरे case में समस्या नहीं बनी, लेकिन कुछ blocks में "break" इस्तेमाल किया जा सकता है
      उदाहरण के लिए win_form block से "break" के जरिए बाहर निकला जा सकता है और "goto" भी काम करता है। दूसरी ओर win_command block "break" को capture नहीं करता, इसलिए win_command के अंदर break या goto इस्तेमाल करने पर आप उस win_command को घेरने वाले बाहरी block से, शायद win_form block से, बाहर निकल जाएंगे। आम तौर पर यह "Cancel" button जैसी स्थिति में इस्तेमाल होता है
    • Rust के macro area की अच्छी बात यह है कि ऐसी condition check करने वाला code लिखना न सिर्फ संभव है, बल्कि सचमुच practical और imaginable भी है, और आसानी से install होने वाली open source library की मदद भी मिल सकती है
      बात सिर्फ कुछ पहलुओं में थोड़ा बेहतर होने की नहीं है; पूरे ecosystem में समय के साथ जमा हुई ढेर सारी छोटी असुविधाओं को smoothly हटाने की है। जैसे CPython ecosystem में अपने lexical types बहुत ज्यादा हैं, या high-performance computing libraries में ऐसी समस्याएँ होती हैं—cumulative effect बड़ा होता है
      उदाहरण के लिए, इस macro में जो हिस्सा ऐसी समस्या पैदा करता है, वह अच्छी तरह लिखे Rust macro में शुरुआत से ही समस्या नहीं बनता। यह smart लोगों द्वारा C की सीमाओं को workaround करने की कोशिश का byproduct है

हालांकि यह macro भी मूल रूप से Rust के native feature का port ही है, इसलिए Rust में इसे शुरू से लिखने की ज़रूरत ही नहीं पड़ती, और इसी वजह से यह community software में स्वाभाविक रूप से इस्तेमाल होता है

  • goto सिर्फ़ तब अपने पैर पर कुल्हाड़ी मारने जैसा होता है जब उसे एक function से दूसरे function में जाने के लिए इस्तेमाल किया जाए, और असल में “goto considered harmful” भी उसी practice को निशाना बनाता था
    वह practice खत्म हो चुकी है, और आज function के अंदर इस्तेमाल होने वाला goto काफ़ी harmless है; व्यवहार में यह break/continue जैसा ही है
  • मान लें कि आपको C program लिखना ही है, और union tag पर पूर्ण pattern matching वाकई चाहिए। Datatype99 जो देता है, वह भी “सरल शब्दों में tagged union के ऊपर syntax sugar” है
    और मान लें कि आपको यह भी पहले से पता है कि Rust मौजूद है, लेकिन C program लिखने वाले जिन कारणों से Rust नहीं अपनाते, उन्हीं कारणों से आप Rust नहीं इस्तेमाल करेंगे
    फिर भी कम-से-कम Zig पर विचार करने लायक है। दो दिन पहले मैंने Zig में ऐसा code लिखा था
    comptime के inline else का इस्तेमाल करके tagged union पर switch statement की सभी branches generate कीं, और जिन union members में "l" field था उनमें offset जोड़ा। अगर type information compile time पर पता हो, तो branch का स्वभाव कई तरह से बदला जा सकता है, और ऐसी information काफ़ी होती है। सारी conditions compile time पर तय हो जाती हैं, इसलिए switch की हर branch में सिर्फ़ उसी variant को handle करने के लिए ज़रूरी logic होता है
    “लेकिन मेरा program पहले से C में है और मुझे यह सिर्फ़ एक file में चाहिए” तो भी Zig आज़माकर देखना अच्छा होगा। हो सकता है आपको पसंद आ जाए
    • दिए गए example को देखकर Zig पर विचार किया। अब आंखें दुख रही हैं। लगता है Lisp के लिए मेरी कद्र और बढ़ गई है
    • Nim भी काम आ सकता है, और यह C में compile होता है
      https://gist.github.com/unclechu/eb37cc81e80afbbb5e74990b62e...
  • struct + union + enum से algebraic data types के फायदे ज़्यादातर नहीं मिल सकते क्या? मैंने कई types की union रखकर, कौन-सा चुनना है यह बताने वाला enum जोड़ने का pattern इस्तेमाल किया है
    std::variant भी sum type की तरह कुछ हद तक काम करता लगता है
    इकलौती समस्या यह है कि field की किसी खास value के हिसाब से साफ़-सुथरा switch statement नहीं बनाया जा सकता, लेकिन nested switch statements भी इतने गंदे नहीं होते
    • सही। convention और discipline से भी object-oriented programming के कई फायदे मिल सकते हैं, लेकिन ऐसा करने के लिए, उदाहरण के लिए, vtable को खुद handle करना पड़ता है, इसलिए अक्सर implementation details तक नीचे उतरना पड़ता है
      इस approach की समस्या यह है कि सारी details का पूरा ध्यान रखने का मानसिक बोझ बहुत बड़ा होता है। थकान के कारण आप नई class बनाने के बजाय मौजूदा class में feature को जबरन ठूंसना शुरू कर देते हैं
      अंत में abstraction महंगा महसूस होने लगता है, और नतीजतन जहां problem को ज़्यादा elegant तरीके से हल किया जा सकता था, वहां भी abstraction कम इस्तेमाल करते हैं
      संक्षेप में, किसी जटिल विचार का reference देते समय हर बार लंबी कहानी दोहराने के बजाय “Darmok and Jalad at Tanagra” कह पाने की क्षमता परिवर्तनकारी है
    • modeling वाला पहलू नकल किया जा सकता है। लेकिन वह algebraic data types के फायदे का आधा भी नहीं है
      pattern matching बड़ा usability फायदा है
    • sum type value को handle करते समय यह सुनिश्चित करना बेहद ज़रूरी है कि आपने सभी alternatives को जांचा है या नहीं
      macros से करना मुश्किल है, लेकिन असंभव नहीं। मूलतः एक ऐसा macro चाहिए जो matching context शुरू करे, फिर एक variable introduce करना होगा जो जांचे गए सभी alternatives को track करे, और matching context के अंत में यह जांचने की व्यवस्था करनी होगी कि सभी alternatives cover हुए या नहीं
    • जब सच में ज़रूरत हो तो ज़्यादातर code में C++ इस्तेमाल करना आम तौर पर ठीक है, लेकिन std::variant मुझे खास पसंद नहीं
      std::visit से सभी cases को exhaustively match करने का तरीका hacky लगता है। अगर यह सच में first-class language feature बन जाए, तो बड़ा फायदा होगा
      coroutines जैसी जिन दूसरी features पर अब तक काम हुआ है, उनसे भी ज़्यादा इसका रोज़मर्रा के code पर असर हो सकता है
  • इसे implement करना वाकई कमाल है। नमन