Haskell: एक बेहतरीन procedural language
(entropicthoughts.com)Side effects को first-class values की तरह संभालना
- Haskell में side effects (जैसे random number generation, output आदि) को
first class valueकी तरह treat किया जाता है - यानी,
randomRIO(1, 6)जैसे side effect पैदा करने वाले function call का return value खुद result नहीं होता, बल्कि वह एक ऐसा object लौटाता है जो “कभी चलाए जाने वाले action” को describe करता है - यह object वास्तव में execute होने पर random value बनाता है, लेकिन उससे पहले इसमें सिर्फ execution plan होता है
IO Intजैसा type उस “action” को दर्शाता है जो वास्तव में चलने परIntपैदा करेगा; यह call करते ही execute नहीं होता, बल्कि बाद में ज़रूरत पड़ने पर execute होता है- इस विशेषता की वजह से पारंपरिक procedural languages के “function call = immediate execution” मॉडल से अलग, Haskell में side effects को compose किया जा सकता है और बाद में वास्तव में execute किया जा सकता है
do blocks को समझना
doblock कोई जादुई syntax नहीं है; यह मूलतः दो operators से बना है जो side effects को bind करते हैं और sequence में execute करते हैं
then
*>operator पहले बाईं ओर वाले side effect को execute करता है, उसका result फेंक देता है, और फिर दाईं ओर वाले side effect को execute करता है- उदाहरण के लिए
putStr "hello" *> putStrLn "world"दो outputs को sequence में जोड़कर एकIO ()action बनाता है doblock में कई lines लिखने पर अंदर ही अंदर इसी तरह के sequential execution operator का उपयोग होता है
bind
>>=operator बाईं ओर के side effect को execute करके मिला value दाईं ओर के function को देता है- उदाहरण:
randomRIO(1, 6) >>= print_sideएक ऐसा side effect बनाता है जो dice result कोprint_sideको देकर print करता है doblock में\<-pattern इसी operator को आसान रूप में व्यक्त करता है
Two operators are all of do blocks
- अंततः
doblock इन्हीं दो operators*>,>>=से बना है - Code readability और convenience की वजह से
dosyntax बहुत इस्तेमाल होता है, लेकिन Haskell की ताकत का बेहतर उपयोग करने के लिए इससे भी अधिक समृद्ध side-effect composition functions का इस्तेमाल करना चाहिए
Side effects पर काम करने वाले functions
- Standard library में side effects को और विविध तरीकों से संभालने के लिए कई functions मौजूद हैं
pure
pure xऐसा action बनाता है जो “किसी अतिरिक्त side effect के बिना x value को result के रूप में पैदा करता है”- उदाहरण:
loaded_die = pure 4ऐसाIO Intबनाता है जो हमेशा 4 लौटाता है
fmap
fmap :: (a -> b) -> IO a -> IO bके रूप में, यह side effect के result value पर pure function apply करके नया result value बनाने वाला action तैयार करता है- उदाहरण:
length <$> getEnv "HOME"की तरह, environment variable लाने वाले side effect परlengthapply करके उसकी length निकालने वाला action बनाया जा सकता है
liftA2, liftA3, …
liftA2,liftA3जैसे functions कई side-effect results को एक pure function के साथ combine करके नया side effect बनाते हैं- उदाहरण:
liftA2 (+) (randomRIO(1,6)) (randomRIO(1,6))ऐसा side effect बनाता है जो दो dice values का sum निकालता है - यही काम
<>$और<*>के combination से भी किया जा सकता है
Intermission: what’s the point?
- यह तरीका दूसरी भाषाओं में संभव साधारण functionality जैसा लग सकता है, लेकिन Haskell में इसका लाभ यह है कि side-effect actions को कभी भी variables में निकालकर या दोबारा combine करके भी execution timing या result नहीं बदलता
- Side effects को स्वतंत्र रूप से संभालने की वजह से code refactoring में भ्रम कम होता है, और equational reasoning के आधार पर सुरक्षित reuse संभव होता है
sequenceA
sequenceA [IO a] -> IO [a]“side-effect actions की list” को “list result देने वाले एक single side-effect action” में बदल देता है- उदाहरण: कई
logactions को list में जमा करके, बाद मेंsequenceAसे एक बार में execute किया जा सकता है - अनंत बार दोहराए जाने वाले side effects (जैसे
repeat (randomRIO(1,6))) को भी list में रखकर, ज़रूरत के अनुसार सिर्फtake nकरकेsequenceAसे execute किया जा सकता है
Interlude: convenience functions
void,sequenceA_,replicateM,replicateM_आदि तब उपयोगी होते हैं जब result value का उपयोग नहीं करना हो या किसी action को बार-बार चलाना हो- उदाहरण:
replicateM_ 500 (putStrLn "I will not cheat again.")की तरह repeat count को manually track किए बिना side effect को कई बार execute किया जा सकता है
traverse
traverse :: (a -> IO b) -> [a] -> IO [b]list के हर element पर side-effect function apply करके results को list में इकट्ठा करने वाला action बनाता हैsequenceAवास्तव मेंtraverse idके बराबर है, औरtraverse_वह version है जो results को discard कर देता है
for
-
forकी functionalitytraverseजैसी ही है, लेकिन यह arguments को उल्टे क्रम में लेता है -
उदाहरण:
for numbers $ \n -> ...के रूप में “for loop” जैसी syntax को प्राकृतिक ढंग से व्यक्त किया जा सकता है -
ऐसी composition की वजह से जिन चीज़ों के लिए दूसरी भाषाओं में अलग syntax चाहिए—जैसे repetition, traversal, data structure transformation—उन्हें Haskell में library functions के composition से लागू किया जा सकता है
Effects की first-class प्रकृति का पूरा उपयोग
- Haskell में side effects को first-class values की तरह सक्रिय रूप से उपयोग करने पर code duplication कम किया जा सकता है और structure बेहतर बनाया जा सकता है
- उदाहरण के लिए cache इस्तेमाल करने वाले large-number prime factorization logic में
IOकी जगहStateआदि का उपयोग करके ऐसी संरचना बनाई जा सकती है जहाँ “effect मौजूद है लेकिन बाहर की दुनिया पर असर नहीं पड़ता” - इस तरह के structured effects सिर्फ ज़रूरी हिस्सों पर लागू होते हैं, जबकि बाकी code pure functions के रूप में बना रह सकता है; इससे safety और flexibility दोनों मिलती हैं
- अंत में
evalStateआदि से वास्तविक effect को run करके result को pure value में बदला जा सकता है
वे चीज़ें जिनकी आपको कभी परवाह करने की ज़रूरत नहीं
- पुराने Haskell दौर से चले आ रहे कई नाम (
>>,return,mapMआदि) को आधुनिक functions (*>,pure,traverseआदि) से बदला जा सकता है - ये नाम “पुराने नामकरण” या monad-केंद्रित design से आए थे, जबकि आजकल Applicative या अधिक सामान्य Functor-आधारित approach की सिफारिश की जाती है
Appendix A: सफलता और निरर्थकता से बचना
- “Haskell avoids success” का मतलब यह है कि “भाषा लोकप्रियता या convenience के लिए अपने मूल मूल्यों से समझौता नहीं करती”
- “Haskell is useless” का संदर्भ यह है कि शुरुआत में यह सिर्फ पूरी तरह pure functions की अनुमति देने के कारण ऐसा लगता था जैसे भाषा में कुछ भी व्यावहारिक नहीं किया जा सकता; बाद में side effects को ‘first-class’ रूप में संभालने की विधि आने से इसमें practicality आई
Appendix B: Why fmap maps over both side effects and lists
fmapका रूप बहुत सामान्य है (Functor f => (a -> b) -> f a -> f b), इसलिए यह list, Maybe, IO जैसे अलग-अलग containers या effect types पर समान रूप से लागू होता है- List पर
fmapलगाने से function हर element पर लागू होता है, औरIOपर लगाने से function result value पर लागू होता है - इस तरह “ऐसी सभी structures जिन पर function apply किया जा सकता है” को Functor कहा जाता है
Appendix C: Foldable and Traversable
Foldableवह structure है जिसमें elements को traverse करते हुए process किया जा सकता हैTraversableऐसा structure है जिसमें traverse करने के साथ-साथ उसी shape की structure को नए elements के साथ फिर से बनाया जा सकता हैsequenceAयाtraverseको values इकट्ठा करते समय मूल structure बनाए रखना हो तो वह structureTraversableहोना चाहिए- Tree या Set जैसी data structures में structure values के अनुसार बदल सकता है, इसलिए ऐसे मामलों में सिर्फ traversal संभव होने (
Foldable) और वास्तव में structure को पुनर्निर्मित कर पाने (Traversable) के बीच अंतर किया जाता है - ज़रूरत के अनुसार पहले list में बदलकर फिर
traverseइस्तेमाल करने जैसे तरीकों से effects को लचीले ढंग से संभाला जा सकता है
2 टिप्पणियां
Reddit देखते हुए ऐसे काफ़ी विज्ञापन दिख जाते हैं.. लेकिन नाम से ही थोड़ा मानसिक अवरोध सा महसूस होता है.
किसी तरह यह बहुत कठिन और शक्तिशाली भाषा जैसी लगती है..
Hacker News राय
Haskell का type system दूसरी लोकप्रिय भाषाओं की तुलना में जटिल है। खासकर
*>,<*>,<*जैसे operators पूरे codebase में learning curve बढ़ा देते हैं>>=और>>जैसे operators फिर से पढ़ने पड़ते हैंHaskell imperative programming को बेहतर बनाने में मदद करता है
traverse/mapMका generalized version सिर्फ lists ही नहीं बल्कि सभीTraversabletypes पर काम करता है, और यह बहुत उपयोगी हैtraverse :: Applicative f => (a -> f b) -> t a -> f (t b)के रूप में इस्तेमाल किया जा सकता हैHaskell में शक्तिशाली monads हैं, जो Haskell को और अधिक procedural बनाते हैं
doblock में intermediate variables का इस्तेमाल किया जा सकता हैHaskell में लिखे गए software में ImplicitCAD शामिल है
Haskell का code procedural language की तरह पढ़ा जाता है, लेकिन side-effect functions के साथ काम करते समय फायदे देता है
>><i>>का पुराना नाम है, और दोनों operators left-associative हैं>>कोinfixl 1के रूप में define किया गया है और<i>>कोinfixl 4के रूप में define किया गया है, इसलिए<i>>>>की तुलना में अधिक मजबूती से bind करता हैHaskell का
IO aऔरaasynchronous और synchronous जैसा महसूस हो सकता हैदूसरी भाषाओं में
console.log("abc")जैसे function से सरल IO किया जा सकता हैजिन्होंने Haskell को आज़माया नहीं है, उन्हें GHC extensions के साथ इस्तेमाल होने वाला वास्तविक Haskell बहुत जटिल लग सकता है