आप एक शुद्ध फ़ंक्शन नहीं बना सकते हैं जिसे कहा जाता है random
जो हर बार कॉल करने पर एक अलग परिणाम देगा। वास्तव में, आप शुद्ध कार्यों को "कॉल" भी नहीं कर सकते। आप उन्हें लागू करें। तो आप कुछ भी याद नहीं कर रहे हैं, लेकिन इसका मतलब यह नहीं है कि कार्यात्मक प्रोग्रामिंग में यादृच्छिक संख्याएं ऑफ-लिमिट हैं। मुझे प्रदर्शित करने की अनुमति दें, मैं हास्केल सिंटैक्स का उपयोग करूंगा।
एक अनिवार्य पृष्ठभूमि से आ रहा है, आप शुरू में इस तरह के एक प्रकार के यादृच्छिक की उम्मीद कर सकते हैं:
random :: () -> Integer
लेकिन यह पहले ही खारिज कर दिया गया है क्योंकि यादृच्छिक एक शुद्ध कार्य नहीं हो सकता है।
एक मूल्य के विचार पर विचार करें। एक मूल्य एक अपरिवर्तनीय चीज है। यह कभी नहीं बदलता है और हर अवलोकन जो आप इसके बारे में कर सकते हैं वह सभी समय के लिए सुसंगत है।
जाहिर है, यादृच्छिक एक पूर्णांक मूल्य का उत्पादन नहीं कर सकता है। इसके बजाय, यह एक पूर्णांक यादृच्छिक चर पैदा करता है। यह इस तरह दिख सकता है:
random :: () -> Random Integer
सिवाय इसके कि एक तर्क को पारित करना पूरी तरह से अनावश्यक है, कार्य शुद्ध हैं, इसलिए एक random ()
दूसरे के रूप में अच्छा है random ()
। मैं यादृच्छिक, यहाँ से, इस प्रकार दे दूँगा:
random :: Random Integer
जो सभी अच्छी तरह से और ठीक है, लेकिन बहुत उपयोगी नहीं है। आप की तरह अभिव्यक्ति लिखने में सक्षम होने की उम्मीद random + 42
कर सकते हैं , लेकिन आप ऐसा नहीं कर सकते, क्योंकि यह टाइपकास्ट नहीं होगा। आप यादृच्छिक चर के साथ कुछ भी नहीं कर सकते, फिर भी।
यह एक दिलचस्प सवाल उठाता है। यादृच्छिक चर में हेरफेर करने के लिए क्या कार्य मौजूद होना चाहिए?
यह फ़ंक्शन मौजूद नहीं हो सकता:
bad :: Random a -> a
किसी भी उपयोगी तरीके से, क्योंकि तब आप लिख सकते हैं:
badRandom :: Integer
badRandom = bad random
जो एक असंगति का परिचय देता है। badRandom एक मूल्य है, लेकिन यह भी एक यादृच्छिक संख्या है; एक विरोधाभास।
शायद हमें इस फ़ंक्शन को जोड़ना चाहिए:
randomAdd :: Integer -> Random Integer -> Random Integer
लेकिन यह सिर्फ एक अधिक सामान्य पैटर्न का एक विशेष मामला है। अन्य यादृच्छिक चीजें प्राप्त करने के लिए आपको किसी भी फ़ंक्शन को रैंडम चीज़ पर लागू करने में सक्षम होना चाहिए:
randomMap :: (a -> b) -> Random a -> Random b
लिखने के बजाय random + 42
अब हम लिख सकते हैं randomMap (+42) random
।
यदि आपके पास सभी यादृच्छिक मानचित्र थे, तो आप यादृच्छिक चर को एक साथ जोड़ नहीं पाएंगे। आप इस फ़ंक्शन को उदाहरण के लिए नहीं लिख सकते:
randomCombine :: Random a -> Random b -> Random (a, b)
आप इसे इस तरह से लिखने का प्रयास कर सकते हैं:
randomCombine a b = randomMap (\a' -> randomMap (\b' -> (a', b')) b) a
लेकिन इसका गलत प्रकार है। ए के साथ समाप्त होने के बजाय Random (a, b)
, हम एक के साथ समाप्त होते हैंRandom (Random (a, b))
यह एक और फ़ंक्शन जोड़कर तय किया जा सकता है:
randomJoin :: Random (Random a) -> Random a
लेकिन, ऐसे कारणों से जो अंततः स्पष्ट हो सकते हैं, मैं ऐसा नहीं करने जा रहा हूं। इसके बजाय मैं इसे जोड़ने जा रहा हूं:
randomBind :: Random a -> (a -> Random b) -> Random b
यह तुरंत स्पष्ट नहीं है कि यह वास्तव में समस्या को हल करता है, लेकिन यह करता है:
randomCombine a b = randomBind a (\a' -> randomMap (\b' -> (a', b')) b)
वास्तव में, randomJoin और randomMap के संदर्भ में randomBind लिखना संभव है। RandomBind के संदर्भ में randomJoin लिखना भी संभव है। लेकिन, मैं इसे एक अभ्यास के रूप में करना छोड़ दूंगा।
हम इसे थोड़ा सरल कर सकते थे। मुझे इस फ़ंक्शन को परिभाषित करने की अनुमति दें:
randomUnit :: a -> Random a
randomUnit एक यादृच्छिक चर में एक मान को बदल देता है। इसका मतलब है कि हमारे पास यादृच्छिक चर हो सकते हैं जो वास्तव में यादृच्छिक नहीं हैं। हालांकि यह हमेशा मामला था; हम randomMap (const 4) random
पहले कर सकते थे । RandomUnit को परिभाषित करने का कारण यह एक अच्छा विचार है कि अब हम randomUnit और randomBnit के संदर्भ में randomMap को परिभाषित कर सकते हैं:
randomMap :: (a -> b) -> Random a -> Random b
randomMap f x = randomBind x (randomUnit . f)
ठीक है, अब हम कहीं जा रहे हैं। हमारे पास यादृच्छिक चर हैं जिन्हें हम हेरफेर कर सकते हैं। हालाँकि:
- यह स्पष्ट नहीं है कि हम वास्तव में इन कार्यों को कैसे लागू कर सकते हैं,
- यह काफी बोझिल है।
कार्यान्वयन
मैं छद्म यादृच्छिक संख्याओं से निपटूंगा। वास्तविक यादृच्छिक संख्याओं के लिए इन कार्यों को लागू करना संभव है, लेकिन यह उत्तर पहले से ही काफी लंबा हो रहा है।
अनिवार्य रूप से, जिस तरह से यह काम करने जा रहा है वह यह है कि हम हर जगह बीज मूल्य पारित करने जा रहे हैं। जब भी हम एक नया यादृच्छिक मान उत्पन्न करते हैं, हम एक नया बीज उत्पन्न करेंगे। अंत में, जब हम एक रैंडम वैरिएबल का निर्माण कर रहे होते हैं, तो हम इस फंक्शन का उपयोग करके इसका नमूना लेना चाहेंगे:
runRandom :: Seed -> Random a -> a
मैं रैंडम प्रकार को परिभाषित करने जा रहा हूं:
data Random a = Random (Seed -> (Seed, a))
फिर, हमें बस randomUnit, randomBind, runRandom और यादृच्छिक के कार्यान्वयन प्रदान करने की आवश्यकता है जो काफी सीधे-आगे हैं:
randomUnit :: a -> Random a
randomUnit x = Random (\seed -> (seed, x))
randomBind :: Random a -> (a -> Random b) -> Random b
randomBind (Random f) g =
Random (\seed ->
let (seed', x) = f seed
Random g' = g x in
g' seed')
runRandom :: Seed -> Random a -> a
runRandom seed (Random f) = (snd . f) seed
यादृच्छिक के लिए, मुझे लगता है कि वहाँ पहले से ही प्रकार का एक समारोह है:
psuedoRandom :: Seed -> (Seed, Integer)
जिस मामले में यादृच्छिक बस है Random psuedoRandom
।
चीजों को कम बोझिल बनाना
हास्केल में आंखों पर इस तरह के अच्छे अक्षर बनाने के लिए सिंटैक्टिक चीनी होती है। इसे डू-नोटेशन कहा जाता है और इसका उपयोग करने के लिए हमें इसे रैंडम के लिए मोनाड का उदाहरण बनाना होगा।
instance Monad Random where
return = randomUnit
(>>=) = randomBind
किया हुआ। randomCombine
पहले से अब लिखा जा सकता है:
randomCombine :: Random a -> Random b -> Random (a, b)
randomCombine a b = do
a' <- a
b' <- b
return (a', b')
अगर मैं अपने लिए ऐसा कर रहा था, तो मैं इससे भी एक कदम आगे जाऊंगा और एक उदाहरण बनाऊंगा। (अगर यह कोई मतलब नहीं है चिंता मत करो)।
instance Functor Random where
fmap = liftM
instance Applicative Random where
pure = return
(<*>) = ap
तब randomCombine लिखा जा सकता है:
randomCombine :: Random a -> Random b -> Random (a, b)
randomCombine a b = (,) <$> a <*> b
अब जब हमारे पास ये उदाहरण हैं, तो हम >>=
randomBind के बजाय उपयोग कर सकते हैं , randomJoin के बजाय शामिल हो सकते हैं , randomMap के बजाय fmap, randomUnit के बजाय वापस आ सकते हैं। हमें मुफ्त में फ़ंक्शंस का पूरा लोड भी मिलता है।
यह इसके लायक है? आप यह तर्क दे सकते हैं कि इस स्तर तक पहुंचना, जहां यादृच्छिक संख्याओं के साथ काम करना पूरी तरह से भयावह नहीं है, काफी कठिन और लंबे समय से घुमावदार था। इस प्रयास के बदले में हमें क्या मिला?
सबसे तात्कालिक इनाम यह है कि अब हम यह देख सकते हैं कि हमारे कार्यक्रम के कौन से हिस्से यादृच्छिकता पर निर्भर हैं और कौन से भाग पूरी तरह निर्धारक हैं। मेरे अनुभव में, इस तरह एक सख्त अलगाव को मजबूर करना चीजों को बेहद सरल करता है।
हमने अब तक मान लिया है कि हम केवल प्रत्येक यादृच्छिक चर से एक नमूना चाहते हैं जो हम उत्पन्न करते हैं, लेकिन अगर यह पता चलता है कि भविष्य में हम वास्तव में अधिक वितरण देखना चाहते हैं, तो यह तुच्छ है। आप बस अलग-अलग बीजों के साथ एक ही रैंडम वैरिएबल पर कई बार रनग्रैंडम का उपयोग कर सकते हैं। यह, ज़ाहिर है, अनिवार्य भाषाओं में संभव है, लेकिन इस मामले में, हम निश्चित हो सकते हैं कि हम हर बार अप्रत्याशित आईओ का प्रदर्शन करने नहीं जा रहे हैं, हम एक यादृच्छिक चर का नमूना लेते हैं और हमें प्रारंभिक अवस्था के बारे में सावधान रहने की आवश्यकता नहीं है।