1 पॉइंट द्वारा GN⁺ 2024-09-27 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • 1989 का FM TOWNS के लिए High C Compiler सिर्फ DOS environment compatibility तक सीमित नहीं था, बल्कि उस समय के C compilers में दुर्लभ कई user-oriented language features भी शामिल करता था
  • Phar Lap के DOS extender के साथ जुड़कर यह 16-bit MS-DOS environment में 32-bit 80386 का उपयोग करने वाली development workflow का हिस्सा बना, और FM TOWNS का 1st-party C compiler बन गया
  • numeric literal underscore, labeled arguments, case ranges, nested functions, generators जैसी सुविधाएँ C/C++ standard में बहुत बाद में आईं, या आज भी standard में नहीं हैं
  • nested functions ने function pointer और context pointer को साथ ले जाने वाले non-escaping closure के रूप में “full function value” दिया, जो सामान्य C function pointer से अधिक expressive था
  • generators को nested functions के ऊपर syntactic sugar के रूप में लागू किया गया था, इसलिए caller के for loop body को nested function में बदलकर उसे yield argument के रूप में पास करने वाली सरल संरचना से यह काम करता था

FM TOWNS और High C की जगह

  • FM TOWNS से जुड़ी किताबों के ढेर में मिला 1980 के दशक का यह C compiler manual उम्मीद से कहीं अधिक समृद्ध language extensions रखता था
  • C और उससे जुड़े language family को वास्तविक environments में इस्तेमाल करने के लिए लंबे समय तक vendor extensions की ज़रूरत पड़ती रही
    • आज के GCC, Clang, MSVC-केंद्रित environment में extensions ज़्यादातर platform-specific handling या low-level control पर केंद्रित रहती हैं
    • 1980 के दशक में छोटी और अधिक कंपनियाँ adoption के लिए प्रतिस्पर्धा कर रही थीं, इसलिए extension features भी अधिक विविध थीं
  • Phar Lap ने 16-bit MS-DOS environment में 32-bit 80386 processor का उपयोग संभव बनाने वाले शुरुआती DOS extender में से एक बनाया था
  • MetaWare ने Phar Lap के अनुरोध पर High C Compiler को Phar Lap के DOS extender SDK पर port किया
  • Fujitsu ने 803386-आधारित FM TOWNS platform OS में Phar Lap के DOS extender को integrate किया, और High C इस platform का 1st-party C compiler बन गया
  • FM TOWNS 1989 में लॉन्च हुआ, ठीक उस समय जब पहला ANSI C standard C89 अनुमोदित होने वाला था

standard से आगे की छोटी सुविधाजनक features

  • numeric literals में underscore separators

    • लंबे numeric literals को पढ़ने में आसान बनाने के लिए अंकों के भीतर underscore separators डाले जा सकते थे
    • C++ ने C++14 में 1'000'000 जैसे apostrophe separators जोड़े
    • C ने C23 में जाकर ही ऐसा मिलता-जुलता feature अपनाया
  • labeled arguments

    • जिन functions में parameters बहुत हों, या bool जैसे ऐसे types अधिक हों जिनका अर्थ call site पर स्पष्ट न हो, उनमें argument names लिखे जा सकते थे
    • High C के labeled arguments Python की एक लोकप्रिय feature की तरह काम करते थे
      • argument labels optional थे
      • label होने पर argumentName => value syntax के साथ arguments को किसी भी क्रम में दिया जा सकता था
      • unlabeled और labeled arguments को मिलाया जा सकता था, लेकिन function के सभी parameters के लिए matching arguments होना ज़रूरी था
    • standard C और C++ में यह feature अब भी नहीं है
  • case ranges

    • Pascal के case low..high की तरह एक बार में value range match करने की सुविधा दी जाती थी
    • standard C और C++ ने यह feature नहीं अपनाया

nested functions और full function value

  • High C, Pascal की तरह, function के अंदर nested functions declare करने देता था
  • इसका implementation standard Pascal या GCC के nested function extension की तुलना में अधिक complete रूप के काफ़ी करीब था
  • High C सिर्फ nested function declarations ही नहीं, बल्कि full function value type भी declare कर सकता था
    • पारंपरिक C function pointer के विपरीत, यह function pointer के साथ context pointer भी रखता था
    • इससे nested function द्वारा captured context को फिर से पाया जा सकता था
    • यह एक non-escaping closure था, जिसकी lifetime outer function के return होने के बाद तक नहीं बढ़ती थी
  • GCC का nested function extension nested function को सामान्य function pointer की तरह refer करने के लिए call stack पर executable code लिखकर context pointer को thunk करता था
    • इस तरीके ने बड़े security risk पैदा किए, और कई platforms ने इस feature को पूरी तरह disable कर दिया
  • High C के local function references first-class values की तरह इस्तेमाल किए जा सकते थे, लेकिन उनकी lifetime outer function के return के बाद तक extend नहीं होती थी
  • nested functions parent function में goto भी कर सकते थे
    • Smalltalk blocks की तरह non-local exit संभव था, यानी nested function के बाहर निकलना
    • इससे control flow की तरह व्यवहार करने वाले functions बनाए जा सकते थे
  • Objective-C को 2009 में escaping closure के रूप में इस्तेमाल किए जा सकने वाले blocks मिले, और C++ ने 2011 में lambdas पेश किए
  • इन दोनों में non-local exit की क्षमता नहीं थी
  • standard C में अब भी कोई आधिकारिक nested function feature नहीं है

generator coroutines

  • MetaWare ने generators feature पर इतना ज़ोर दिया कि उसके लिए पूरा एक chapter समर्पित किया
  • High C ने 1989 में plain C में Python-style generator coroutines का समर्थन दिया था
  • generator function को void foo(Arg arguments) -> (Yield yields) syntax से declare किया जाता था
    • function के भीतर magic function yield(values...) को कई बार call करके values की sequence बनाई जा सकती थी
    • caller for variable... <- foo(arguments...) do { ... } रूप की नई for loop syntax से generated values को क्रम से iterate करता था
  • यह implementation nested functions के साथ जटिल रूप से combine हो सकता था
    • generator के अंदर nested functions outer generator के yield behavior को capture कर सकते थे
    • nested function खुद को recursively call करके tree या recursive data structures को traverse करते हुए हर step पर yield कर सकता था
  • यह रूप Python या कई mainstream generator coroutine languages की तुलना में ऐसा लगता है जिसे implement करना कठिन होगा

generator implementation और standard languages से अंतर

  • High C के generators किसी advanced runtime के बिना, nested functions के ऊपर syntactic sugar के रूप में काम करते थे
  • void foo(Arg arguments) -> (Yield yields) रूप की generator declaration वास्तव में सामान्य function declaration void foo(void yield(Yield yields)!, Arg arguments) के बराबर थी
    • yield एक implicit parameter था, जिसका type “full function value” होता था
    • generator body के भीतर yield(values) calls वास्तव में इसी implicit function parameter को call करने वाले सामान्य function calls थे
  • caller side पर for loop body को nested function में बदला जाता था
    • यही nested function generator के yield argument के रूप में pass किया जाता था
    • संरचना सरल थी, लेकिन प्रभावी थी
  • क्योंकि nested functions non-local exit को support करते थे, इसलिए for loop body के बाहर जाने वाले break, continue, goto भी उपयुक्त loop-बाहरी स्थान पर goto करके काम करते थे
  • standard C के ऐसे features को integrate करने की संभावना कम लगती है
  • C++20 compile-time coroutine transformation पर आधारित बहुत flexible और complex coroutines feature देता है
    • इसका उपयोग generators implement करने के लिए किया जा सकता है
    • लेकिन उसका परिणाम local functions के साथ इस तरह सहज रूप से combine होता नहीं दिखता

1 टिप्पणियां

 
GN⁺ 2024-09-27
Hacker News राय
  • 2011 में मैंने iterator-based for के बारे में नोट्स तैयार किए थे। यह तब भी उन features में से एक था जिसे लोग काफी पहले भूल चुके थे, और मैंने यह भी देखा था कि अगर यह उस समय C++ standard में शामिल होता तो कैसा दिखता
    सौभाग्य से मेरे पास High C/C++ Language Reference का एक English edition है
    http://jdebp.uk./FGA/metaware-iterator-driven-for.html
    http://jdebp.uk./Proposals/metaware-iterator-driven-for.html
    • मुझे जिज्ञासा है कि break या return कैसे compile होते होंगे। क्या इसे इस तरह transform किया गया होगा कि yield function एक status code return करे और call site पर उसकी जाँच हो?
    • क्या वह उल्टा smiley face जानबूझकर था?
  • D में, Das BetterC में भी, ऐसे features हैं: numeric literals में underscores, case ranges, named arguments, nested functions, static nested functions, generator जैसी functionality
    उदाहरण के लिए int a = 1_234_567;, case 5 .. case 6:, test(b:3, a:4); जैसे forms संभव हैं
    static nested functions बाहरी function के frame variables को access नहीं कर सकते, इसलिए Error: static function test.foo.plus cannot access variable i in frame of function test.foo जैसी error आती है
    generator जैसी functionality https://dlang.org/spec/statement.html#foreach_over_struct_an... में है
    • यह लेख पढ़ते समय मुझे लगातार D याद आता रहा। लगा कि comments में Walter Bright दिख सकते हैं
    • मुझे लगता है D का garbage collector भी वाकई अच्छा feature है। low-level code में कभी-कभी manual memory management की जरूरत होती है, लेकिन असल में बहुत जगह इससे खास फर्क नहीं पड़ता, और garbage collector काम को काफी आसान बना देता है
      उदाहरण के लिए, अगर आप in-memory cache service बना रहे हों, तो cache items को खुद garbage collector द्वारा track न किया जाना बेहतर है। garbage collector को अक्सर असली access patterns नहीं पता होते, इसलिए वह बाधा बन सकता है। लेकिन उस service के बाकी ज्यादातर components के लिए garbage collector होना ज्यादा उपयुक्त है
    • एक सवाल है। क्या किसी को पता है कि लोग C में nested functions के concept को क्यों नापसंद करते हैं?
      named arguments test(a:4, b:3) के रूप में क्यों हैं, test(.a=4, b.=3); के रूप में क्यों नहीं?
      यह भी जानना चाहता हूँ कि C में first-class types को कैसे handle किया जा सकता है
  • इसी संदर्भ में lcc-win C compiler ने operator overloading, default function arguments और function overloading जोड़े थे। documentation में “generic functions” देखें [1]
    Plan 9 C compiler ने भी कई language extensions introduce किए, जिनमें anonymous structs/unions जैसे कुछ बाद में C standard में शामिल हुए। आज GCC -fplan9-extensions flag accept करता है [2], जिससे function calls और assignments में struct pointers को anonymous fields में automatically convert करने जैसी काफी उपयोगी सुविधाएँ enable की जा सकती हैं
    [1] https://lcc-win32.services.net/C-Tutorial.pdf
    [2] https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html
  • इन features को बनाने वाला genius कौन रहा होगा? लगता है उस company के अंदर कोई बेहद दूरदर्शी व्यक्ति था
    अफसोस है कि ये दुनिया में व्यापक रूप से फैलकर language standards को प्रभावित नहीं कर पाए। इतनी पहले ऐसे features मौजूद थे, यह हैरान करने वाला है
    Hacker News पर भी पहले इस पर चर्चा हुई थी: https://news.ycombinator.com/item?id=38938402
    क्या कहीं इसकी PDF copy होगी?
    • CLU में 1970s के मध्य-उत्तरार्ध में ही iterators, यानी generators और yield वाला for loop मौजूद था [0]. उसी दौर की Icon language में भी मिलती-जुलती generator functionality थी [1], और उसमें yield को suspend कहा जाता था। मेरी जानकारी में Ada (1983) में भी यह feature था
      ये language features पूरी तरह अनजाने नहीं थे
      [0] https://publications.csail.mit.edu/lcs/pubs/pdf/MIT-LCS-TR-2...
      [1] https://dl.acm.org/doi/pdf/10.1145/800055.802034
    • Bitsavers पर HC 1.2 reference manual (1985) की copy है
      इसमें numbers के अंदर underscores, case ranges, named parameters, nested functions, और full function variables तक समझाए गए हैं
      https://bitsavers.org/pdf/metaware/…
      file के अंत से करीब 50 pages पहले Appendix A देखें
    • MetaWare 80s–90s में Santa Cruz की एक prolific compiler company थी। उन्होंने जो बनाया वह मुझे पसंद था, और culture भी काफी रोचक था
      पहले जब मैं code सीखता और इस्तेमाल करता था, तब कुछ थोड़ी shady sites के जरिए उनके बारे में पता चला था
    • यह इतना हैरान करने वाला नहीं है। FORTRAN, Lisp, ALGOL, COBOL के बाद की high-level programming languages के archives में गहराई से जाएँ तो ऐसे बहुत से language ideas दिखते हैं
      systems programming languages का समृद्ध इतिहास भी दिखता है। यह भी दिखता है कि C और Go की design इस मामले में कितनी समान है कि उन्होंने दूसरे ecosystems में हो रहे काम और पुराने अनुभवों को नजरअंदाज किया
    • दुख की बात है कि ये features अधिकांश programming languages द्वारा दिए जाने वाले standard features की list का हिस्सा होने के बजाय नई features जैसे लगते हैं

कंपाइलर मैनुअल का लिंक https://winworldpc.com/product/metaware-high-c-cpp/33x पर है
C मैनुअल PDF में 2007 का copyright notice है

  • पिछला submission और comments यहाँ हैं: https://news.ycombinator.com/item?id=38938402
    • आज Joe Groff ने FediVerse पर इसे फिर से उठाया, इसलिए लगता है यह यहाँ भी फिर से आया है
      https://f.duriansoftware.com/@joe/113195961485703110
    • लगता है लेखक ने कल वही सामग्री किसी दूसरे URL पर फिर से पोस्ट की। अजीब है
  • अगर आप सोच रहे हैं कि चित्र में दिए गए उदाहरणों के string literals \n के बजाय ¥n पर क्यों खत्म होते हैं, तो लगता है ये code examples Shift-JIS में लिखे गए थे। Shift-JIS में ASCII के \ वाले स्थान पर ¥ आता है
    • मूल रूप से यह 1969 का जापानी ASCII variant, JIS Roman [0], था। Shift-JIS ने काफी बाद में double-byte character set support जोड़ा
      [0] https://en.wikipedia.org/wiki/JIS_X_0201
    • समस्या यह है कि Shift-JIS में backslash का ASCII code 2-byte character के दूसरे byte के रूप में भी इस्तेमाल होता है। इसलिए C में जापानी string literals कभी-कभी सही से काम नहीं करते
      इस उपयोग के लिए EUC-JP बेहतर है, क्योंकि उसमें यह समस्या नहीं है। Pascal में अगर आप (* *) comments इस्तेमाल करते हैं और { } comments नहीं, तो Shift-JIS के साथ भी यह समस्या नहीं आती
    • लेखक ने यह जानकारी नहीं दी कि यह किताब कब आई थी, और खोजने पर भी जानकारी नहीं मिलती। लेकिन लगता है किताब के समय Shift-JIS standard अभी मौजूद नहीं था
      इसके बजाय संभवतः Shift-JIS का आधार बने JIS X 0201(https://en.m.wikipedia.org.org/wiki/JIS_X_0201) का इस्तेमाल हुआ होगा
    • इसी तरह जापानी DOS prompt में C:\ नहीं बल्कि C:¥ होता था
  • ये extensions Ada features हैं। Ada में Call (Param_A => 1, Param_B => "Foo"); जैसे labels, arbitrary-base numbers में underscores (X : Integer := 1_000;), nested subprograms, और range-based checking होती है
    • लेख में जैसा कहा गया है, Pascal में Ada से पहले से ही ये features थे, और entry points वाले task types को असल में generators माना जा सकता है
      हम अक्सर भूल जाते हैं कि उस समय C कई दूसरी भाषाओं की तुलना में अविश्वसनीय रूप से primitive था
  • सामग्री से अलग, इस किताब की typography दिलचस्प है। एक साथ सुंदर और डरावनी लगती है
    मुझे जापानी notation या kerning rules की पर्याप्त जानकारी नहीं है, लेकिन ऐसा दिखता है जैसे Kanji और Latin characters दोनों वाले variable-width font को fixed-width cells में जबरन fit किया गया हो
    खैर, अच्छा है कि code examples मेरी कई किताबों की तरह 8pt font में नहीं हैं
  • generators देखकर Rust की internal/external iteration समस्या और try_fold() याद आते हैं (https://scribe.rip/@veedrac/rust-is-slow-and-i-am-the-cure-3...)
  • खासकर generators को देखें तो यह अपने समय से बहुत आगे लगता है। शायद Fujitsu लंबी standardization process की परवाह नहीं कर रहा था, इसलिए इसे सीधे implement कर सका
    लेकिन शायद इसी वजह से ये extensions तुलनात्मक रूप से कम जाने गए, और दशकों बाद modern C/C++ में इन्हें फिर से खोजना और reinvent करना पड़ा
    • Fujitsu नहीं, MetaWare था। MetaWare को compilers का काफी अनुभव था, और उसी समय उसके पास काफी मशहूर Pascal compiler भी था। Pascal में पहले से nested functions थे
    • अगर C पर उन लोगों का कब्जा न होता जो इस बात पर अड़े थे कि even two's complement को भी standard में नहीं डालना चाहिए, तो C कहीं बेहतर भाषा हो सकती थी
    • coroutines और generators तब भी अच्छी तरह समझे हुए concepts थे। Icon को देखिए। इसलिए सच में मुख्य वजह शायद यही थी कि standardization burden की चिंता नहीं करनी पड़ती थी