स्टैटिक टाइप और फावड़ा
(carefully.understood.systems)- 2000 के दशक से 2010 के शुरुआती वर्षों तक स्टैटिक टाइप की लोकप्रियता घटी, फिर 2010 के मध्य और बाद के वर्षों में इसके फिर बढ़ने का कारण स्टैटिक टाइप सिस्टम की गुणवत्ता में सुधार माना गया है
- डायनेमिक टाइप सिस्टम में वेरिएबल और फील्ड की स्थिति व सामग्री का आकलन इंसान को खुद करना पड़ता है, और कंप्यूटर न मदद करता है न बाधा डालता है; इसे नंगे हाथों से ज़मीन खोदने से तुलना की गई है
- शुरुआती Java या C++98 जैसे पुराने स्टैटिक टाइप सिस्टम nullable और non-nullable pointer में फर्क करने में भी मदद नहीं कर पाते थे, और टाइप नाम बार-बार लिखवाते थे; इन्हें कागज़ के फावड़े से तुलना की गई है
- TypeScript, Haskell, MyPy, Swift, Rust जैसे आधुनिक टाइप सिस्टम null handling, sum type·union type, और type inference के ज़रिए प्रोग्राम त्रुटियों की जाँच और state expression को बेहतर समर्थन देते हैं
- IDE में method name autocomplete जैसी सुविधियाँ व्यापक होने के साथ, स्टैटिक टाइप सिस्टम में डाली गई जानकारी error checking के अलावा productivity benefits भी देने लगी
मुख्य दावे
- स्टैटिक टाइप की लोकप्रियता सिर्फ़ एक फैशन नहीं, बल्कि व्यापक रूप से उपयोगी स्टैटिक टाइप सिस्टम की गुणवत्ता बेहतर होने के कारण फिर बढ़ी है
- अगर अच्छा फावड़ा हो तो नंगे हाथों से बेहतर फावड़े से ज़मीन खोदना है, लेकिन अगर सिर्फ़ कागज़ का फावड़ा हो तो नंगे हाथ बेहतर हैं — यही उपमा दी गई है
- डायनेमिक टाइप सिस्टम में प्रोग्राम के वेरिएबल और फील्ड किस state और content में हैं, यह इंसान को खुद सोचना पड़ता है, और कंप्यूटर उस निर्णय में मदद नहीं करता
- खराब स्टैटिक टाइप सिस्टम मदद से ज़्यादा बोझ बन सकता है, और इसकी तुलना कागज़ के फावड़े से ज़मीन खोदने से की गई है
पुराने स्टैटिक टाइप सिस्टम की सीमाएँ
- 90 के दशक और 2000 के शुरुआती वर्षों में व्यापक रहे शुरुआती Java या C++98 के स्टैटिक टाइप सिस्टम nullable pointer और non-nullable pointer में फर्क करने जैसे सरल काम में भी ठीक से मदद नहीं कर पाते थे
- पुराने स्टैटिक टाइप सिस्टम को ऐसे ढाँचे के रूप में समझाया गया है जिसमें sum type नहीं थे और सिर्फ़ product type थे
- पुराने स्टैटिक टाइप सिस्टम जगह-जगह टाइप नाम हाथ से लिखवाते थे
BufferedReader bufferedReader = new BufferedReader(new FileReader(filename));जैसे कोड को एक छोटी आपदा कहा गया है
आधुनिक टाइप सिस्टम में सुधार
- TypeScript, Haskell, MyPy, Swift, Rust जैसे आधुनिक टाइप सिस्टम nullable type और non-nullable type को अलग करने के तरीके देते हैं
- Haskell में
Maybe t, TypeScript मेंT | null, Swift मेंT?, Rust मेंOptional<T>का उदाहरण देते हुए बताया गया है कि टाइप सिस्टम यह बता सकता है कि null check कहाँ ज़रूरी है और कहाँ वह छूट गया है - कहा गया है कि व्यवहार में runtime पर null pointer error लगभग देखने को नहीं मिलती
- आधुनिक टाइप सिस्टम sum type या union type में से कम-से-कम एक उपलब्ध कराते हैं, जिससे "Make invalid states unrepresentable" को व्यवहार में लागू करना संभव होता है
- यह तरीका state machine को दर्शाने वाले object में हर field को सिर्फ़ उसी संबंधित state में मौजूद रहने लायक व्यक्त करने देता है
- आधुनिक टाइप सिस्टम type inference देते हैं; अगर compiler
let x = 5;को संख्या समझ सकता है, तोlet x: number = 5;लिखने की ज़रूरत नहीं होती
IDE सुविधियाँ और निष्कर्ष
- method name autocomplete जैसी IDE सुविधियाँ व्यापक होने के साथ स्टैटिक टाइप सिस्टम की उपयोगिता और बढ़ गई है
- 90 के दशक में Intellisense Visual Studio की मुख्य सुविधा थी, लेकिन 2020 के दशक में ऐसी सुविधियाँ लगभग हर IDE और editor में उपलब्ध हैं
- स्टैटिक टाइप सिस्टम में डाली गई जानकारी सिर्फ़ प्रोग्राम error checking ही नहीं, बल्कि अतिरिक्त productivity benefits भी पैदा करती है
- अच्छा डायनेमिक टाइप सिस्टम, खराब स्टैटिक टाइप सिस्टम से बेहतर है, लेकिन अब हमारे पास अतीत की तुलना में कहीं बेहतर स्टैटिक टाइप सिस्टम उपलब्ध हैं
1 टिप्पणियां
Lobste.rs की रायें
यह लेख अच्छा है, लेकिन मैं इससे पूरी तरह सहमत नहीं हूँ। 2000 के दशक की शुरुआत के static type systems भले शानदार नहीं थे, फिर भी वे बिल्कुल static typing न होने से कहीं बेहतर थे
closed sum types नहीं थे, लेकिन subtyping से काफी हद तक उसे model किया जा सकता था, और non-nullable types नहीं थे, लेकिन C++ के references और non-pointer types, तथा Java के primitive types कुछ हद तक यह काम कर लेते थे। Ruby या JavaScript में न सिर्फ हर type nullable था, बल्कि उसे string की तरह, integer की तरह, और प्रोग्राम के हर दूसरे type की तरह भी treat किया जा सकता था, इसलिए स्थिति और खराब थी
मेरा मानना है कि static typing के पक्ष में रुख बदलने का बड़ा कारण Web 2.0 social network boom के दौरान यह था कि first-mover advantage हर चीज़ से ज़्यादा महत्वपूर्ण था। Ruby या Python में technical debt इकट्ठा करते हुए भी जल्दी launch करना और iterate करना, Friendster या Digg की तरह पीछे छूट जाने से बेहतर था, और अगर performance धीमी हो तो उस समय आसानी से मिलने वाली low-interest capital से बस और server खरीद लिए जाते थे
बाद के mobile boom में software को सीमित और हमारे नियंत्रण से बाहर user devices पर चलना पड़ा, और धीमे dynamic type apps सचमुच धीमे थे, साथ ही type error आने पर server की तरह top-level response handler में gracefully recover भी नहीं किया जा सकता था। उस माहौल में static typing की safety और performance कहीं अधिक असरदार लगने लगी
2000 के दशक की शुरुआत में मैं भी इससे सहमत था, क्योंकि उस समय के type systems अक्सर लगभग कभी गलत न होने वाले गुणों को ही enforce करते थे, जबकि code को structure करने में मदद न देने वाली पाबंदियाँ लगा देते थे। खासकर subtyping और implementation inheritance का मिला-जुला तरीका लचीला नहीं था
ज़्यादा modern type systems इस्तेमाल करने के बाद मेरी राय बदल गई। snmalloc में C++ type system से memory ownership state machine enforce की जाती है, और दूसरे codebases में ring buffer counters के सही overflow behavior की जाँच की जाती है। दोनों ही चीज़ें गलत हों तो debug करना झंझट भरा होता है और आम error source भी होती हैं, लेकिन compiler ने ऐसे code पर fail कराया जिसे मैं सही समझ रहा था, और इस तरह bug को tree में जाने से रोक दिया
IDE में
.दबाकर method name के कुछ अक्षर टाइप करना और सही candidate पर Enter दबा देना, हर कुछ सेकंड में 2 सेकंड बचाता है, और जब यह न पता हो कि कौन-सा method मौजूद है, तब class definition खोजने में लगने वाले 30 सेकंड भी बचते हैं। यह बात https://grugbrain.dev/#grug-on-type-systems में भी अच्छी तरह दर्ज हैfunction parameter types लिखने की तुलना में method call करने वाली code lines हम कहीं ज़्यादा बार लिखते हैं, इसलिए यह सौदा dynamic typing के बहुत ख़िलाफ़ जाता है। असली मूल्य runtime पर fail होने वाला बेतुका code अनुमति देने में नहीं था, बल्कि local variable types को omit करने में था, और static typed languages को शुरू से ही उसे मना करने की ज़रूरत नहीं थी
जो दुर्लभ codebases type system का गंभीरता से उपयोग करते थे, उनमें कई पन्नों तक ऐसा code भरा रहता था जो कुछ कहता ही नहीं था, फिर भी runtime conditionals का ढेर लगा रहता था, और Java में type hierarchy बढ़ने के साथ program भी वास्तव में धीमा हो जाता था। ज़्यादातर codebases type का छिटपुट उपयोग करते थे और runtime conditionals बहुत रखते थे, इसलिए आवश्यक test coverage के मामले में dynamic type systems की तुलना में बहुत कम बचत होती थी
dynamic typed languages में static rewards नहीं थे, लेकिन वे concise थीं, इसलिए पढ़ना और review करना आसान था, और testing भी आसान थी। खासकर late 90s से early 2000s के dependency injection frameworks जैसे माहौल में, जहाँ हर नई service जोड़ने पर कई XML files बदलनी पड़ती थीं, यह और भी सच था। RAM का आधा हिस्सा खा जाने वाले IDE के बिना भी काम किया जा सकता था
मेरे शुरुआती करियर का अनुभव बिल्कुल ऐसा ही था, इसलिए मैं इस लेख से पूरी तरह सहमत हूँ। Java 1.4 से Java 6 तक cost-benefit इतना खराब था कि मैं static typed languages को लगभग छोड़ ही बैठा था, और कुछ साल बाद शौक़िया तौर पर Haskell आज़माने के बाद ही समझ पाया कि static typing का cost-benefit वाजिब भी हो सकता है, समस्या Java थी। “python is not java” निबंध भी उस अंधेरे दौर को अच्छी तरह दिखाता है
यह सवाल है कि static typing युग की मुख्यधारा बनने के बाद क्या हमने अपने software में reliability का फायदा वास्तव में देखा भी है
मुझे लगा था कि static typing का फायदा ज़्यादा immediate development feedback और गंभीर runtime failure को कम करने में है, लेकिन सैद्धांतिक रूप से ऐसे failure हमेशा संभव होने पर भी व्यवहार में वे इतने बार होते नहीं लगे
undefinedऔरnullपर method call करने की कोशिशें तेज़ी से कम हो गईंjunior और कुछ senior शुरू में सशंकित थे कि जगह-जगह
@ts-ignoreभर जाएगा, लेकिन वास्तव में ऐसे लगभग तीन ही मामले थे, जिनमें dependencies के टूटे हुए type भी शामिल थे। पहले development branch में type confusion की वजह से हफ्ते में लगभग एक बार app crash हो जाता था और मेरा काम रुक जाता था, लेकिन अब आख़िरी बार ऐसा कब हुआ था, यह भी याद नहींसिर्फ
tscको संतुष्ट करने से भी type-related bug कम हुए, यहाँ तक कि उन हिस्सों में भी जहाँ code मैंने खुद नहीं लिखा था। दूसरी ओर आजकल linter बहुत ज़्यादा उत्साही हो गए हैं, और Sonar जैसे tools को संतुष्ट करने की कोशिश में मैंने असली refactoring breakage देखी है। 95% warning झूठी थीं, 3% tool की bug थीं, और जो 2% मददगार थीं वे भी असली bug का कारण नहीं थीं। codebase को उनके हिसाब से ढालने में 1 हफ्ता लगाने पर एक bug पकड़ा, लेकिन उसी प्रक्रिया में दो और डाल दिएtscको संतुष्ट करने वाले काम से मोटे तौर पर रोज़ 2 शुद्ध bug fix और 1 regression निकलती थी, लेकिन regression आम तौर पर पूरे crash की बजाय गलत behavior जैसी कम गंभीर चीज़ होती थीइसमें property-based testing जोड़ने पर औसतन 2~4 घंटे लगते थे और हर बार कम-से-कम एक bug सामने आता था। अगर code को property-based testing के लिए तैयार किया जा सकता है, तो यह करना चाहिए
DeepSeek V4 Flash जैसे सस्ते model से test coverage बढ़ाई, लेकिन इस बात का ध्यान रखा कि बेकार test न बनें; इससे रोज़ लगभग 2~3 logic bug ठीक हुए और crash नहीं हुए। हालांकि test suite बस किसी तरह maintainable स्तर पर है
जब junior को Sonnet और Opus 4.5, 4.6 सीरीज़ से मोटे तौर पर test बनवाने दिए, तो models ने सिर्फ ऐसे test बनाए जो “मौजूदा behavior को document” करते थे, इसलिए सुधार का असर कम था, और test suite maintain न हो पाने लायक थी, इसलिए उसे फेंकना पड़ा
model-based testing bug पकड़ने में बहुत अच्छी है, लेकिन इसकी setup जटिल है, और models को सतही features पर cycles बर्बाद किए बिना कोने-कोने में घुसने के लिए प्रेरित करना बहुत झंझट भरा है। profile-based model-based fuzzer जैसा कुछ हो तो दिलचस्प होगा
संक्षेप में, type checker गंभीर failure और कई तरह की confusion को अच्छी तरह पकड़ते हैं, और property-based testing शानदार है। सामान्य testing में लगातार लाभ पाने के लिए बहुत अनुशासन चाहिए
यहाँ TypeScript को अच्छे type system के साथ एक ही श्रेणी में रखना सबसे कम स्वीकार्य लगता है
awaitके पार type narrowing जिस तरह काम करती है, उसने मुझे कई बार काटा है। फिर भी इसने स्थिति को नाटकीय रूप से बेहतर किया हैसच कहूँ तो structural typing को भी मैंने आख़िरकार स्वीकार कर लिया, और मुझे लगता है कि इसका आगे की language design पर सकारात्मक असर होगा
यह दावा बहुत कमज़ोर है। algebraic data type और type inference वाले ठीक-ठाक programming language 90 के दशक के मध्य से मौजूद थे
Java और C++ के type system बहुत कमज़ोर थे, लेकिन SML, OCaml, Haskell पहले से थे और उनका अनुभव आज से बहुत अलग नहीं था। अगर लोगों ने उन भाषाओं का इस्तेमाल नहीं किया, तो वह culture, adoption और उन आवश्यकताओं का सवाल है जो शब्दों में सामने नहीं आईं; इसे सिर्फ इस तरह नहीं समझाया जा सकता कि “उपयोगी type system काफ़ी अच्छे नहीं थे”
या अगर दावा यह है कि “तब की लोकप्रिय भाषाओं के type system खराब थे, और आज की लोकप्रिय भाषाओं के type system बेहतर हैं, इसलिए type system ज़्यादा लोकप्रिय हो गए”, तो यह circular logic जैसा लगता है
उन भाषाओं में भी बहुत बारीकी है जो type system के साथ डिज़ाइन हुईं, और उन भाषाओं में भी जो मूल रूप से बिना type के डिज़ाइन हुईं और बाद में उन पर type system चढ़ाया गया
जो व्यक्ति मूल रूप से dynamic typing को पसंद करता रहा है, उसकी नज़र से भी यह लेख काफ़ी संतुलित लगता है। आजकल मैं C# में काम करता हूँ, शौक़ में Lisp इस्तेमाल करता हूँ, और पहले Python भी इस्तेमाल करता था
जब मुझे Java 5 इस्तेमाल करनी पड़ती थी, तो ज़्यादातर library developer के ख़राब फ़ैसलों की वजह से type system से लगातार लड़ना पड़ता था। 2010 के आसपास C# पर आने के बाद type system सक्रिय रूप से हानिकारक तो नहीं था, लेकिन ज़्यादातर दोहराव-भरा था, और Python में सबसे आम type confusion यानी null pointer exception को भी नहीं रोक पाता था
C# का type system वास्तव में 2020 के आसपास मददगार बनना शुरू हुआ, जब non-nullable reference type आए। इस साल native union type भी आएँगे, लेकिन exhaustiveness लागू करने वाली union type library कम-से-कम 2016 से संभव थीं और मैं उन्हें 2020 से इस्तेमाल कर रहा हूँ
मुझे अब भी लगता है कि trend की भूमिका है, लेकिन उसका कुछ हिस्सा बुरा नहीं है। ज़्यादा expressive type system वाली trendy भाषाओं ने उन साधारण भाषाओं में भी सुधार लाया है जिन्हें हम पैसे लेकर काम में इस्तेमाल करते हैं
Haskell और उसका type system 2000 के दशक में भी पहले से मौजूद था। आज जितना व्यापक रूप से इस्तेमाल नहीं होता था, लेकिन वह निश्चित रूप से मौजूद था, इसलिए इस दावे में उस हिस्से को और बेहतर तरीके से शामिल किया जाना चाहिए
मेरी निजी राय में, TypeScript मुख्यधारा की languages इस्तेमाल करने वाले users को बेहतर type system से परिचित कराने वाला एक बड़ा कारण था। quality और Microsoft के support के अलावा, इसका फ़ायदा यह भी था कि यह JavaScript पर लागू होता था, और JavaScript को Python की तुलना में types की ज़रूरत ज़्यादा थी। इसकी वजह “Undefined is not a function.” और “The good parts.” थे
“Real World Haskell” 2008 में आई थी, और उसका लक्ष्य मुख्यधारा के programmers के लिए Haskell को अधिक आकर्षक बनाना था। अच्छी ख़बर फैलाने में उसने कितना योगदान दिया, यह मुझे नहीं पता
Java दुनिया में Scala 2004 में शानदार types लेकर आया था, और .NET में F# 2005 में आया। Scala ने शायद Twitter जैसे सबसे नज़र आने वाले users हासिल किए, लेकिन TypeScript की तरह वह उस platform के users के बड़े हिस्से को अपने भीतर समाहित करने की स्थिति में नहीं था, और न ही Rust या Go की तरह दूसरे language users को बड़ी संख्या में खींच लाने जितना आकर्षक था
ठीक अगले paragraph में Haskell को “modern type system” कहा गया है, लेकिन 90 के दशक के अंत और 2000 के शुरुआती दौर में Haskell का अनुभव रखने वाले लोग, सिर्फ़ व्यक्तिगत स्तर पर थोड़ा बहुत छूकर देखने वालों को भी शामिल करें तो, व्यावहारिक रूप से 0% के करीब थे। लेख यह बता रहा है कि उस समय ज़्यादातर developers ने static typed languages को कैसे अनुभव किया, और क्यों उस बहुसंख्यक समूह ने सामूहिक रूप से static typed languages से दूरी बनाई
उदाहरण के लिए, OCaml में
duneइस्तेमाल करने के लिए आपकोopamfile,dunefile,ocaml modulesyntax, औरocamlsyntax समझना पड़ता है। Haskell के optional compiler extensions भी उतने ही डरावने लगते हैंइसके उलट,
cargoमें आपको सिर्फ़tomlऔर Rust जानना होता है