Parse करें, सत्यापित न करें
type-driven design का सार
- type-driven design को समझाने वाला एक सरल स्लोगन: Parse करें, सत्यापित न करें
- इस स्लोगन का मतलब है type system का उपयोग करके code की safety और correctness को बढ़ाने का तरीका
संभावनाओं का क्षेत्र
- static type system यह आसानी से तय करने में मदद करता है कि कोई विशेष function implement किया जा सकता है या नहीं
- उदाहरण:
foo :: Integer -> Void को implement नहीं किया जा सकता (Void type में कोई value नहीं हो सकती)
- उदाहरण:
head :: [a] -> a function तब defined नहीं होता जब list खाली हो
partial function को total function में बदलना
अपेक्षाओं का प्रबंधन
head function खाली list होने पर कोई value return नहीं कर सकता, इसलिए Maybe type का उपयोग करके Nothing return करने दिया जा सकता है
- लेकिन इससे उपयोग के समय असुविधा हो सकती है
अपेक्षाओं को type में व्यक्त करना
NonEmpty type का उपयोग करके non-empty list को व्यक्त किया जा सकता है, जिससे यह सुनिश्चित होता है कि head function हमेशा value return करे
NonEmpty type का उपयोग अनावश्यक checks को हटाता है और type system के जरिए compile time पर errors पकड़ने देता है
parsing की ताकत
- parsing और validation का अंतर इस बात में है कि जानकारी को कैसे सुरक्षित रखा जाता है
validateNonEmpty function यह सत्यापित करता है कि list खाली नहीं है, लेकिन वह इस जानकारी को सुरक्षित नहीं रखता
parseNonEmpty function यह सत्यापित करता है कि list खाली नहीं है और NonEmpty type के रूप में इस जानकारी को सुरक्षित भी रखता है
validation का जोखिम
- validation-आधारित approach से "shotgun parsing" जैसी समस्या पैदा हो सकती है
- इससे ऐसी स्थिति बन सकती है जहाँ program input के एक हिस्से को process करने के बाद पता लगाए कि बाकी input valid नहीं है
- parsing program को दो चरणों में बाँटता है: पहले चरण में input की validity जाँची जाती है, और दूसरे चरण में केवल valid input को process किया जाता है
व्यवहार में parsing
- data types पर ध्यान दें और function type signatures को जितना संभव हो उतना specific बनाएँ
- ऐसे data structures का उपयोग करें जिनमें illegal states को व्यक्त ही न किया जा सके, और data को जितनी जल्दी हो सके concrete representation में बदलें
- data types को code का मार्गदर्शन करने दें, code को data types को नियंत्रित नहीं करना चाहिए
m () return करने वाले functions का सावधानी से उपयोग करना चाहिए
- data को कई बार parse करने से डरना नहीं चाहिए
- data की denormalized representation से बचना चाहिए, और जरूरत पड़ने पर encapsulation के जरिए उसे प्रबंधित करना चाहिए
- ऐसे abstract data types का उपयोग करना चाहिए जो validators को parser जैसा दिखाएँ
सारांश, विचार और संबंधित पढ़ाई
- Haskell के type system का पूरा लाभ उठाना कठिन नहीं है, और इसके लिए आधुनिक language extensions का उपयोग करना भी जरूरी नहीं है
- मुख्य विचार है "total functions लिखना", जो सरल है लेकिन व्यवहार में अपनाना कठिन हो सकता है
- संबंधित पढ़ाई के रूप में Matt Parson की blog post "Type Safety Back and Forth" और Matt Noonan का paper "Ghosts of Departed Proofs" सुझाए गए हैं
GN⁺ का सार
- यह लेख बताता है कि Haskell के type system का उपयोग करके code की safety और correctness कैसे बढ़ाई जा सकती है
- यह parsing और validation के अंतर को समझने और parsing के जरिए input की validity सुनिश्चित करने के महत्व पर जोर देता है
- यह type system की मदद से ऐसे data structures इस्तेमाल करने के महत्व पर जोर देता है जिनमें illegal states व्यक्त न की जा सकें, और data को जितनी जल्दी हो सके concrete representation में बदला जा सके
- संबंधित पढ़ाई के रूप में Matt Parson की blog post और Matt Noonan का paper सुझाए गए हैं
1 टिप्पणियां
Hacker News राय
यह सलाह और लेख बहुत उपयोगी हैं
जो लोग statically typed functional language का उपयोग नहीं करते, उनके लिए भी यह उपयोगी है
यह विचार paradigms की सीमाओं से परे जाता है
80–90 के दशक के object-oriented साहित्य में भी इसी तरह की अवधारणाएँ मिल सकती हैं, उदाहरण के लिए Design by Contract
TypeScript को अक्सर runtime पर types को और अधिक विशिष्ट करने के तरीके से लिखा जाता है
संभव है कि Design by Contract ने Clojure के spec को प्रभावित किया हो (Clojure एक dynamic language है)
मूल रूप से यह assumptions और guarantees के बारे में है (requirements और provisions)
जब assumptions की पुष्टि हो जाए और guarantees स्थापित हो जाएँ, तो प्रोग्राम के दूसरे हिस्सों में वही assumptions दोबारा जाँचने की ज़रूरत नहीं रहती
जब code में पहले से guaranteed properties को फिर से जाँचा जाता हुआ दिखता है, तो यह भ्रम पैदा कर सकता है, जिससे code को समझना और सुधारना कठिन हो जाता है
यह pattern आधुनिक C# में भी अच्छी तरह काम करता है और space बचाने में भी मदद करता है
मज़बूत type system का उपयोग करके error cases को अभिव्यक्त करना ही असंभव बना देना अच्छा है; इससे software bugs कम करने में मदद मिलती है
समस्या पर सोचने और design के अनुसार चलने में अधिक समय लगता है, लेकिन कई मामलों में वह समय सार्थक होता है
"Parse, don’t validate" का नारा type-based design का अच्छा सार प्रस्तुत करता है
व्यक्तिगत रूप से, "हमेशा केवल एक ही constructor में validation करना" बेहतर लगता है; इससे invalid objects का अस्तित्व ही नहीं रहता
अगर object को बदलना हो, तो उसी constructor को फिर से call करके नया state बनाना चाहिए
इससे qmail का section 5 याद आता है, जिसमें "parse मत करो" और "अच्छे interfaces और user interfaces होते हैं" जैसी बातें शामिल हैं
अगर मैं मध्यम स्तर की programming class पढ़ा रहा होता, तो मैं छात्रों से इन प्रस्तावों की तुलना और विरोध पर निबंध लिखवाता; हर प्रस्ताव में सीखने लायक बात है और शुरुआत में वे परस्पर विरोधी लग सकते हैं
संबंधित सामग्री: Richard Feldman का "Making Impossible States Impossible"
पिछली चर्चा:
Crowdstrike को भेज दिया गया
2000 के दशक के मध्य की XML लहर के समय की किसी टिप्पणी की याद आती है; कई संगठनों ने XML इसलिए चुना क्योंकि XML parser उपलब्ध कराता है
parser लिखना न तो इतना कठिन है और न ही उसमें मज़ा कम है, फिर भी लोग parser क्यों नहीं लिखना चाहते, यह समझना कठिन है
यह सोचकर उत्सुकता होती है कि क्या यह Protocol Buffers के "required" keyword के बहुत बड़ी गलती होने वाली राय के विपरीत है
लचीली, बिना validation वाली parsing और validated parsing — दोनों सुविधाएँ होना शायद सबसे अच्छा होगा