Compiler लागू करने के लिए भी TypeScript अप्रत्याशित रूप से ठीक-ठाक है
(matklad.github.io)- compiler-टाइप tools की implementation language पारंपरिक रूप से OCaml और C++ में बंटी रही है, लेकिन छोटे language experiments में TypeScript, ML-family languages की तरह हल्के-फुल्के तरीके से इस्तेमाल होने वाला विकल्प बन सकती है
- Rust, ML और C++ की खूबियों को जोड़ता है और safe multithreading तक देता है, लेकिन data के physical layout को model करना पड़ता है, इसलिए छोटे prototypes के लिए बोझ बढ़ सकता है
- Deno single binary, built-in linting और formatting, compile step न होना, task runner और watch mode की वजह से TypeScript language experiments जल्दी शुरू करने देता है
- example typechecker generic AST, tagged union, visitor, bottom-up transform को मिलाकर
Expr<void>कोExpr<Type>में बदलता है, औरTypeErrorको type value के रूप में डालकर cascading errors कम करता है - TypeScript autocomplete, flexible type system, और ज़रूरत पड़ने पर dynamic तरीके में उतर सकने वाली runtime प्रकृति की वजह से छोटे language hacking tools के लिए productive हो सकती है
implementation language चुनने की पुरानी तस्वीर
- compiler जैसे tools बनाते समय implementation language चुनने में broadly दो धाराएँ रही हैं
- formal specification या hobby toy language जैसे language-centric कामों के लिए OCaml अच्छी तरह फिट बैठती है
- उदाहरण के तौर पर plzoo और WebAssembly reference interpreter हैं
- implementation-centric और production-ready होने की ज़रूरत वाले कामों में अक्सर C++ चुनी जाती है
- LLVM, clang, v8, HotSpot सभी C++ based हैं
- Rust पर ML और C++ का सीधा प्रभाव रहा है, यह दोनों languages की खूबियों को जोड़ती है और safe multithreading जैसी अपनी ताकतें भी देती है
- हालांकि spectrum पर यह production readiness की तरफ ज्यादा झुकी हुई है
- “बस चल जाने वाला” build system prototyping में भी मदद करता है, लेकिन data के physical layout को model करने की अतिरिक्त complexity साथ आती है
Rust के index approach के फायदे और छोटे code का बोझ
- Rust में compiler बनाते समय आम सलाह pointer से बचकर index इस्तेमाल करने की होती है
- index बड़े codebase में कई फायदे देते हैं
- side tables को संबंधित module के अंदर रखा जा सकता है, जिससे coupling घटती है
- index
u32होता है और struct-of-arrays layout को बढ़ावा देता है, इसलिए performance के लिए अच्छा है - serialization या incremental compilation framework से जोड़ना आसान होता है, जिससे computation strategy flexible होती है
- लेकिन छोटे स्तर की programming में index approach खुद ही झंझट बन जाती है, और hobby experiments में यह बोझ घातक हो सकता है
- OCaml में पुराना-सा feel बचा हुआ है, और इसी context में देखा गया कि TypeScript को ML के equivalent alternative की तरह इस्तेमाल किया जा सकता है या नहीं
Deno और TypeScript से मिलने वाला तेज experiment environment
- starting environment के रूप में deno इस्तेमाल किया गया
- यह TypeScript को सीधे इस्तेमाल करने का out-of-the-box experience देता है
- OCaml में यही हिस्सा pain point है, और Rust, OCaml या C++ से बेहतर है, लेकिन Deno Rust से भी ज्यादा simple experience देता है
- Deno का development experience छोटे PLT hacking के लिए अच्छी तरह फिट बैठता है
- यह single binary है
- linting और formatting built-in हैं
- अलग compile step नहीं है
- built-in task runner और watch mode हैं
- TypeScript खुद पर्याप्त flexible होते हुए भी syntax burden हल्का रखने वाला type system देता है
छोटे typechecker example से दिखने वाले TypeScript patterns
- AST file location जानकारी वाली expression से शुरू होता है
Locationमेंfile,line,columnहोते हैं- TypeScript में string बस
stringऔर number बसnumberहोता है, इसलिएusizeऔरu32जैसे फर्क की चिंता नहीं करनी पड़ती
- expression में location और kind को अलग किया जाता है, और बाद में
Expr<T>रूप में associated data को generic बनाया जाता है- parsing के तुरंत बाद expression में
voiddata होता है - typechecker द्वारा process की गई expression में
Typedata होता है - type inference function
ast.Expr<void>लेता है औरast.Expr<Type>लौटाता है
- parsing के तुरंत बाद expression में
- TypeScript runtime behavior अपने-आप add नहीं करता, इसलिए union type match करने के लिए runtime type information खुद डालनी पड़ती है
tag: "binary",tag: "if"जैसे fields यही भूमिका निभाते हैंtag: "binary"का मतलब runtime पर उस value में सिर्फ string"binary"ही संभव है
- boolean literal और int literal लगभग एक जैसे shape के हैं, इसलिए इन्हें
ExprLiteral<T, V, Tag>से abstract किया जाता हैExprBool<T>हैExprLiteral<T, boolean, "bool">ExprInt<T>हैExprLiteral<T, number, "int">
- type values
TypeBool,TypeIntमें बंटे हैं और singleton values भी उसी नाम से दी जाती हैं- TypeScript types को पूरी तरह erase कर देता है, इसलिए type-related names और value-related names अलग namespaces में मौजूद होते हैं
- इस property का उपयोग करके type और value को एक ही नाम से define किया जा सकता है
Visitor, transform, error type, desugaring
- TypeScript का
switchexpression नहीं बल्कि statement है, इसलिए expression kind को सुविधाजनक तरीके से handle करने के लिए visitor define किया जाता हैbool,int,binary,ifmethods अपने-अपने kind को handle करते हैं- editor autocomplete
switchcase और visitor implementation में मदद करता है
transform<U, V>एक generalized traversal function है जोExpr<U>कोExpr<V>में बदलता है- transformation bottom-up तरीके से चलती है
- internal node visit करते समय child expressions पहले ही transform हो चुकी होती हैं, इसलिए visitor type
Visitor<U, V>नहीं बल्किVisitor<V, V>बनता है
- TypeScript dynamic typed language भी है, इस बात का उपयोग करके
Object.keysसे और general traversal बनाया जा सकता है- इस case में भी static function signature बनाए रखा जा सकता है
- example में यह जरूरी नहीं है, लेकिन जरूरत पड़ने पर dynamic तरीके में उतरने की गुंजाइश है
- type errors को side effect के रूप में array में accumulate नहीं किया जाता, बल्कि
TypeErrortype से represent किया जाता हैTypeबनता हैTypeBool | TypeInt | TypeErrorTypeErrorमेंtag: "Error",location,messageहोते हैंtype_equalकिसी भी एक side केErrorहोने पर true लौटाता है, ताकि cascading failures रोके जा सकें
- final typechecker binary और if को check करता है
- binary expression में left-right operands के types अलग हों तो
"binary expression operands have different types"error लौटाता है - if expression में condition boolean न हो तो
"if condition is not a boolean"error लौटाता है - then और else branch के types अलग हों तो
"if branches have different types"error लौटाता है
- binary expression में left-right operands के types अलग हों तो
- result में कुछ हद तक typing की जरूरत होती है, लेकिन autocomplete काफी भरपाई कर देता है, language से लड़ने जैसा कम लगता है और problem की shape से naturally मेल खाता है
- TypeScript छोटे language hacking tool के रूप में productive क्यों है, इसे तीन बातों में समेटा जा सकता है
- Deno छोटा, self-contained, powerful, और effective development workflow के लिए optimized scripting runtime है
- TypeScript tools में IDE उपयोगी और productive है, और Deno की वजह से setup की जरूरत नहीं पड़ती
- language runtime और compile time दोनों तरफ powerful है, types से काफी sophisticated expression कर सकती है, फिर भी जरूरत पड़ने पर dynamic तरीके में उतर सकती है
- अतिरिक्त idea के रूप में बहुत सारे syntax sugar को type-safe तरीके से desugar करने की approach भी संभव है
ExprऔरExprKindको associated data के बजाय पूरेExprKindपर recursively parameterize किया जाता हैExprKindCorebasic expressions के set को represent करता हैExprKindSugarbasic expression या ऐसे expressions शामिल करता है जिन्हें basic expression में desugar किया जा सकता हैdesugar(expr: ExprSugar): ExprCoresyntax sugar expressions को core expressions में घटाता हैdesugar_one(expr: ExprKindSugar<ExprCore>): ExprKindCore<ExprCore>child expressions के पहले ही desugar हो चुके होने की स्थिति में एक-step transformation करता है
1 टिप्पणियां
Hacker News की राय
TypeScript कुल मिलाकर एक शानदार भाषा है, और यह बात कम आंकी जाती है कि functions ऐसे objects होते हैं जिनके properties/methods हो सकते हैं
functions की array को commands की तरह execute करते हुए बाद में
helpजैसी description जोड़ी जा सकती है, या closures/partial application से state जोड़ी जा सकती है, इसलिएCommandजैसी class जल्दबाज़ी में define करने की ज़रूरत नहीं पड़तीobject-oriented programming में चीज़ों को बहुत जल्दी नाम दे देने से कई टकराव पैदा होते हैं, और
VideoCompressor#compress()जैसी structure की बजाय ज़रूरी values को function में pass करना ज़्यादा natural लगता हैfunctions जैसे behave करने वाले objects support करने वाली दूसरी languages https://en.wikipedia.org/wiki/Function_object पर सूचीबद्ध हैं
net/httpके handler की तरह कोई structservemethod implement कर सकता है, और कोई handler function खुद को call करके interface satisfy कर सकता हैClojure में function
varपर metadata लगाकर इसी तरह handle किया जा सकता है, और Lisp होने की वजह से macros से लगभग कुछ भी संभव हैसाथ ही
core/asyncके CSP channels execution और communication को अलग करते हैं, जिससे callbacks/promises/async/awaitजैसी function color problem से बचा जा सकता है, और commands ऐसे producers की तरह काम कर सकते हैं जो results channel में भेजते हैंmethod का नाम जो भी हो, function context में इस्तेमाल किया जा सकता है, और concrete method name को refer करने की ज़रूरत नहीं होती
हालांकि मुझे यह पसंद नहीं कि function properties रखकर state अपने अंदर समेटे, और वही arguments देकर call करने पर भी behavior बदल सके। functional programming का बड़ा फायदा object-oriented style की state से बाहर निकलने में है
Funcऔर return value न होने वाले function कोActionसे similar तरीके से express किया जा सकता हैJavaScript के lambda expressions C# से काफी मिलते-जुलते हैं, और TypeScript तथा C# के function signatures भी काफी similar हैं
JavaScript, TypeScript, C# की similarities दिखाने वाला एक छोटा repository भी है: https://github.com/CharlieDigital/js-ts-csharp
वही logic JS/TS/C# में साथ-साथ दिखाने वाला screenshot: https://github.com/CharlieDigital/js-ts-csharp/blob/main/js-...
यह बहुत हैरान करने वाली बात नहीं है। TypeScript भी आखिरकार एक और ऐसी language है जिसने ML family की काफी features को मुश्किल से अपनाया है
असली pattern matching न होने से यह OCaml से ज़्यादा असुविधाजनक है, लेकिन C#, Swift, Dart, Kotlin जैसी languages से तुलना करें तो ठीक-ठाक level पर है
TypeScript का type system powerful है, लेकिन underlying standard library और language खुद कुछ कमज़ोर लगते हैं, और pattern matching/switch expressions नहीं हैं
Dart का object model closed है, इसलिए dynamic freedom कम है, type system और expressions भी ज़्यादा कमजोर हैं, और metaprogramming facilities लगभग नहीं हैं, इसलिए Java-style boilerplate और code generators पर निर्भर रहना पड़ता है
C# बताई गई languages में ML features के सबसे करीब है, लेकिन TypeScript के उलट इसमें sum types नहीं हैं, जिससे कई काम ज़्यादा झंझट भरे हो जाते हैं
उदाहरण के लिए
{ ... }पहचानने वाले parser को दूसरे parsers के साथ combine किया जा सकता है, और statement को control flow/declaration/assignment में से किसी एक के रूप में define किया जा सकता हैML-style list processing और pattern matching intermediate representation संभालते समय बेहद expressive थे
article में भी
switchexpression नहीं था, इसलिए visitor pattern से workaround करना पड़ा, और JavaScript का iterator support भी अजीब तरह से अधूरा है.map()है, लेकिन यह सिर्फ arrays पर काम करता है, सामान्य iterators पर सीधे apply नहीं किया जा सकताफर्क बहुत हैं, लेकिन switch करना मुश्किल नहीं है, और व्यक्तिगत तौर पर Kotlin को ज़्यादा पसंद करने के बावजूद दोनों इस्तेमाल किए जा सकते हैं
सोचता हूं कि अगर TypeScript JavaScript compatibility से निकलकर WASM में compile होने लगे तो कैसा होगा। Kotlin WASM compiler जोड़ रहा है और उसके पास पहले से JS transpiler भी है, लेकिन वही code WASM में ज़्यादा छोटा और तेज़ी से load होता है
browser JavaScript अच्छा compile target नहीं है, और जैसे-जैसे नए projects शुरू से TypeScript में शुरू हो रहे हैं, existing JavaScript से आसानी से transition करना चाहिए—यह वजह भी धीरे-धीरे कमज़ोर होती जा रही है
Rust और TypeScript के बीच आते-जाते यह बहुत साफ़ दिखता है कि tagged enums जैसी features की कितनी कमी है
ADT enum proposal रुका हुआ लगता है, जानना चाहता हूं कि कोई और effort चल रहा है या नहीं: https://github.com/Jack-Works/proposal-enum/discussions/19
TypeScript का type system दिलचस्प है, लेकिन अगर compiler किसी compiled language में लिखा गया होता तो कितना ज़्यादा तेज़ होता, यह सोचने पर मजबूर करता है
बेशक इसके लिए “अच्छी implementation” वाली बड़ी शर्त ज़रूरी है
swcऔरesbuildअच्छे comparison points नहीं हैं। speedup का काफ़ी हिस्सा इसलिए है क्योंकि वे TypeScript-specific syntax हटाकर JavaScript बनाते हैंtscआम तौर पर सिर्फ़ पहली run में धीमा होता है, औरincrementalflag या--transpile-onlywatch mode इस्तेमाल करने पर compile time अक्सर 100ms से नीचे आ जाता है, जिससे SWC या ESBuild से महसूस होने वाला फर्क लगभग खत्म हो जाता हैउनका कहना है कि अगर medium-size program की type checking में 20 सेकंड लगते हैं, तो अक्सर वजह JS होना नहीं, बल्कि types में combinatorial explosion होना होता है
दूसरे runtimes parallelism या startup time में फायदा दे सकते हैं, लेकिन उन्होंने ऐसा CPU-focused benchmark नहीं देखा जो कुल मिलाकर 20x speedup को support करे
फिर type logic की 5 lines को पूरा दिन debug करने के बाद आप खुद से पूछने लगते हैं कि मैं यहाँ तक आ कैसे गया
यह SWC के मुख्य developer द्वारा Rust में लिखा गया project है; SWC TS को JS में compile करता है और STC TS types की checking करता है
कहा जा रहा है कि आने वाला isolated declaration mode compile time को 75% तक घटा सकता है: https://github.com/microsoft/TypeScript/pull/53463#issuecomm...
compiler सीखना शुरू कर रहे लोगों के लिए मैं यह किताब recommend करूंगा: https://keleshev.com/compiling-to-assembly-from-scratch/
लेखक TypeScript के subset से 32-bit ARM assembly compiler बनाते हैं, और बताते हैं कि यह लगभग pseudocode जैसा दिखता है इसलिए approachable है
किताब दो हिस्सों में बंटी है; पहले में Java से language interpreter बनाया जाता है, और दूसरे में उसी language को bytecode में compile करने के बाद C में bytecode virtual machine implement की जाती है
implementation की हर code line किताब में reference की गई है
visitor pattern से बचना हो तो
runutility function के साथ IIFE-style switch इस्तेमाल किया जा सकता हैयानी तुरंत execute होने वाले function के अंदर
switchइस्तेमाल करें और return type infer होने देंअगर अंत का
()पसंद नहीं है और IIFE से बचना चाहते हैं, तो function अलग से define करके फिर call कर सकते हैंमैं TypeScript में compiler लिख रहा हूँ, और इस बात से सहमत हूँ कि यह उम्मीद से बुरा नहीं है
शुरुआत में लेखक की तरह Deno से शुरू किया था, लेकिन आखिर में Bun पर आ गया; कुछ rough edges हैं, फिर भी Deno से ज़्यादा संतोषजनक और बहुत तेज़ है
standard parser generator frontend के तौर पर Ohm-js काफ़ी pleasant है: https://ohmjs.org/
official
tsccompiler इतना बड़ा है कि उसे पढ़ने की सलाह नहीं दूंगा, औरtscकैसे काम करता है यह देखने के लिए mini-typescript बेहतर है: https://github.com/sandersn/mini-typescript/खास तौर पर
centi-typescriptbranch मददगार है: https://github.com/sandersn/mini-typescript/tree/centi-types...उम्मीद है WASM में GC और DOM access संभव हो पाएगा
मुझे लगा था TypeScript में interfaces की वजह से extra overhead ज़्यादा होगा, इसलिए यह हैरानी की बात है
सोचता हूँ कि क्या इसे दूसरे क्षेत्रों में भी apply किया जा सकता है, जैसे language parsing के लिए भी ठीक रहेगा या नहीं
compile process में वे पूरी तरह गायब हो जाते हैं
नतीजा बहुत चौंकाने वाला नहीं है। क्योंकि TypeScript compiler खुद TypeScript में लिखा गया है
ML के रूप में TypeScript पहले से ही रोज़ heavy production environments में validated हो रहा है
मैंने C# में compiler लिखा है, और यहाँ जो खास दिखता है वह बस union types जैसा है
निजी तौर पर मैंने visitor pattern की verbosity से बचने का फैसला किया, और compile-time completeness checking के लिए closed enum feature का इंतज़ार कर रहा हूँ
आम तौर पर alternatives awkward होते हैं। किसी sum type को represent करने वाली class में N nullable properties रखकर “हमेशा सिर्फ़ एक ही non-null होगा” जैसी documented condition पर निर्भर रहना, या कई classes को common class से inherit कराना—दोनों ही भारी लगते हैं
overlapping union types बनाने हों तो दोनों तरीकों में duplication या clever combinations की ज़रूरत पड़ती है