कार्यात्मक भाषाएं यादृच्छिक संख्याओं को कैसे संभालती हैं?


68

इसके बारे में मेरा मतलब यह है कि मैंने लगभग हर ट्यूटोरियल में कार्यात्मक भाषाओं के बारे में पढ़ा है, वह यह है कि कार्यों के बारे में एक बड़ी बात यह है कि यदि आप एक ही पैरामीटर के साथ दो बार फ़ंक्शन कहते हैं, तो आप हमेशा के साथ समाप्त हो जाएंगे एक ही परिणाम।

फिर आप पृथ्वी पर कैसे एक कार्य करते हैं जो एक बीज को एक पैरामीटर के रूप में लेता है, और फिर उस बीज के आधार पर एक यादृच्छिक संख्या देता है?

मेरा मतलब है कि यह उन चीजों में से एक के खिलाफ जाना होगा जो कार्यों के बारे में बहुत अच्छे हैं, है ना? या मैं पूरी तरह से यहाँ कुछ याद कर रहा हूँ?

जवाबों:


89

आप एक शुद्ध फ़ंक्शन नहीं बना सकते हैं जिसे कहा जाता है 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 के बजाय वापस आ सकते हैं। हमें मुफ्त में फ़ंक्शंस का पूरा लोड भी मिलता है।

यह इसके लायक है? आप यह तर्क दे सकते हैं कि इस स्तर तक पहुंचना, जहां यादृच्छिक संख्याओं के साथ काम करना पूरी तरह से भयावह नहीं है, काफी कठिन और लंबे समय से घुमावदार था। इस प्रयास के बदले में हमें क्या मिला?

सबसे तात्कालिक इनाम यह है कि अब हम यह देख सकते हैं कि हमारे कार्यक्रम के कौन से हिस्से यादृच्छिकता पर निर्भर हैं और कौन से भाग पूरी तरह निर्धारक हैं। मेरे अनुभव में, इस तरह एक सख्त अलगाव को मजबूर करना चीजों को बेहद सरल करता है।

हमने अब तक मान लिया है कि हम केवल प्रत्येक यादृच्छिक चर से एक नमूना चाहते हैं जो हम उत्पन्न करते हैं, लेकिन अगर यह पता चलता है कि भविष्य में हम वास्तव में अधिक वितरण देखना चाहते हैं, तो यह तुच्छ है। आप बस अलग-अलग बीजों के साथ एक ही रैंडम वैरिएबल पर कई बार रनग्रैंडम का उपयोग कर सकते हैं। यह, ज़ाहिर है, अनिवार्य भाषाओं में संभव है, लेकिन इस मामले में, हम निश्चित हो सकते हैं कि हम हर बार अप्रत्याशित आईओ का प्रदर्शन करने नहीं जा रहे हैं, हम एक यादृच्छिक चर का नमूना लेते हैं और हमें प्रारंभिक अवस्था के बारे में सावधान रहने की आवश्यकता नहीं है।


6
एप्लाइड फंक्शनलर्स / मोनाड्स व्यावहारिक उपयोग के अच्छे उदाहरण के लिए +1।
जोजफग

9
अच्छा जवाब है, लेकिन यह कुछ कदमों के साथ बहुत तेजी से आगे बढ़ता है। उदाहरण के लिए, bad :: Random a -> aविसंगतियों का परिचय क्यों होगा ? इसके बारे में क्या बुरा है? कृपया स्पष्टीकरण में धीरे-धीरे जाएं, विशेष रूप से पहले चरणों के लिए :) यदि आप बता सकते हैं कि "उपयोगी" फ़ंक्शन उपयोगी क्यों हैं, तो यह 1000-बिंदु उत्तर हो सकता है! :)
एंड्रेस एफ।

@AndresF। ठीक है, मैं इसे थोड़ा संशोधित करूंगा।
dan_waterworth

1
@AndresF। मैंने अपने उत्तर को संशोधित कर दिया है, लेकिन मुझे नहीं लगता कि मैंने पर्याप्त रूप से समझाया है कि आप इसका उपयोग कैसे कर सकते हैं, यह अभ्यास है, इसलिए मैं बाद में वापस आ सकता हूं।
dan_waterworth

3
उल्लेखनीय जवाब। मैं एक कार्यात्मक प्रोग्रामर नहीं हूं, लेकिन मैं अधिकांश अवधारणाओं को समझता हूं और मैंने हास्केल के साथ "खेला" है। यह उत्तर का प्रकार है जो दोनों प्रश्नकर्ता को सूचित करता है और दूसरों को गहराई से खुदाई करने और विषय के बारे में अधिक जानने के लिए प्रेरित करता है। काश, मैं आपको अपने वोट से 10 से ऊपर कुछ अतिरिक्त अंक दे पाता।
RLH

10

आप गलत नहीं हो। यदि आप एक ही बीज को दो बार RNG को देते हैं, तो पहला छद्म यादृच्छिक संख्या जो वह देता है, वही होगा। यह कार्यात्मक बनाम साइड-इफ़ेक्ट प्रोग्रामिंग से कोई लेना-देना नहीं है; एक बीज की परिभाषा यह है कि एक विशिष्ट इनपुट अच्छी तरह से वितरित लेकिन निश्चित रूप से गैर-यादृच्छिक मूल्यों के एक विशिष्ट उत्पादन का कारण बनता है। इसलिए इसे छद्म यादृच्छिक कहा जाता है, और यह अक्सर एक अच्छी बात है, उदाहरण के लिए, एक ही समस्या पर विभिन्न अनुकूलन विधियों की तुलना करने के लिए, अनुमान लगाने योग्य इकाई परीक्षण लिखने के लिए।

यदि आप वास्तव में एक कंप्यूटर से गैर-छद्म यादृच्छिक संख्या चाहते हैं, तो आपको इसे कुछ हद तक यादृच्छिक रूप से हुक करना होगा, जैसे कि कण क्षय स्रोत, नेटवर्क के भीतर होने वाली अप्रत्याशित घटनाएं जो कंप्यूटर चालू है, आदि। यह कठिन है। सही है और आम तौर पर महंगा है भले ही यह काम करता है, लेकिन यह छद्म यादृच्छिक मूल्यों को प्राप्त नहीं करने का एकमात्र तरीका है (आमतौर पर आपके प्रोग्रामिंग भाषा से आपके द्वारा प्राप्त किए गए मूल्य कुछ बीज पर आधारित होते हैं , भले ही आपने स्पष्ट रूप से एक प्रदान नहीं किया हो।)

यह, और केवल यह, एक प्रणाली की कार्यात्मक प्रकृति से समझौता करेगा। चूंकि गैर-छद्म-यादृच्छिक जनरेटर दुर्लभ हैं, यह अक्सर नहीं आता है, लेकिन हां, अगर आपके पास वास्तव में सही यादृच्छिक संख्याओं को उत्पन्न करने वाला एक तरीका है, तो कम से कम आपकी प्रोग्रामिंग भाषा में थोड़ा सा भी 100% शुद्ध कार्यात्मक नहीं हो सकता है। क्या कोई भाषा इसके लिए अपवाद बनाएगी या नहीं, यह सिर्फ एक सवाल है कि भाषा कार्यान्वयनकर्ता कितना व्यावहारिक है।


9
एक सच्चा RNG बिल्कुल भी कंप्यूटर प्रोग्राम नहीं हो सकता है, भले ही वह शुद्ध हो (लाइक्स फंक्शनल) या नहीं। हम सभी जानते हैं कि यादृच्छिक अंकों के उत्पादन के अंकगणितीय तरीकों के बारे में वॉन न्यूमैन बोली (जो ऐसा नहीं करते हैं, वे देखते हैं - अधिमानतः पूरी बात, न कि केवल पहला वाक्य)। आपको कुछ गैर-नियतात्मक हार्डवेयर के साथ बातचीत करने की आवश्यकता होगी, जो निश्चित रूप से अशुद्ध भी है। लेकिन यह सिर्फ I / O है, जिसे बहुत अलग-अलग वैन में कई बार शुद्धता के साथ समेटा गया है। कोई भी भाषा जो किसी भी तरह से उपयोग करने योग्य नहीं है I / O को पूरी तरह से अस्वीकार कर देती है - आप कार्यक्रम का परिणाम भी नहीं देख सकते।

डाउन वोट के साथ क्या है?
l0b0

6
एक बाहरी और सही मायने में यादृच्छिक स्रोत प्रणाली की कार्यात्मक प्रकृति से समझौता क्यों करेगा? यह अभी भी "एक ही इनपुट -> एक ही आउटपुट" है। जब तक आप बाहरी स्रोत को सिस्टम का हिस्सा नहीं मानते हैं, लेकिन तब यह "बाहरी" नहीं होगा, क्या ऐसा होगा?
एंड्रेस एफ।

4
इसका PRNG बनाम TRNG से कोई लेना-देना नहीं है। आपके पास प्रकार का गैर-स्थिर फ़ंक्शन नहीं हो सकता है () -> Integer। आपके पास शुद्ध रूप से कार्यात्मक PRNG हो सकता है PRNG_State -> (PRNG_State, Integer), लेकिन आपको इसे अशुद्ध साधनों द्वारा आरंभ करना होगा)।
गिल्स

4
@ ब्रायन सहमत हैं, लेकिन शब्दांकन ("हुक अप टू सम जेनुइनली रैंडम") से पता चलता है कि यादृच्छिक स्रोत सिस्टम के लिए बाहरी है। इसलिए, सिस्टम ही शुद्ध रूप से कार्यात्मक रहता है; यह इनपुट स्रोत है जो नहीं है।
एंड्रेस एफ।

6

एक तरीका यह है कि इसे यादृच्छिक संख्याओं का अनंत क्रम माना जाए:

IEnumerable<int> randomNumberGenerator = new RandomNumberGenerator(seed);

यही है, बस इसे एक अथक डेटा संरचना के रूप में सोचें, Stackजहां आप केवल कॉल कर सकते हैं Pop, लेकिन आप इसे हमेशा के लिए कॉल कर सकते हैं। एक सामान्य अपरिवर्तनीय स्टैक की तरह, एक को ऊपर ले जाने से आपको एक और (अलग) स्टैक मिलता है।

तो एक अपरिवर्तनीय (आलसी मूल्यांकन के साथ) यादृच्छिक संख्या जनरेटर की तरह लग सकता है:

class RandomNumberGenerator
{
    private readonly int nextSeed;
    private RandomNumberGenerator next;

    public RandomNumberGenerator(int seed)
    {
        this.nextSeed = this.generateNewSeed(seed);
        this.RandomNumber = this.generateRandomNumberBasedOnSeed(seed);
    }

    public int RandomNumber { get; private set; }

    public RandomNumberGenerator Next
    {
        get
        {
            if(this.next == null) this.next = new RandomNumberGenerator(this.nextSeed);
            return this.next;
        }
    }

    private static int generateNewSeed(int seed)
    {
        //...
    }

    private static int generateRandomNumberBasedOnSeed(int seed)
    {
        //...
    }
}

वह क्रियाशील है।


मैं यह नहीं देखता कि रैंडम नंबरों की अनंत सूची कैसे बनाई जा सकती है जैसे कि फंक्शन के साथ काम करना आसान है pseudoRandom :: Seed -> (Seed, Integer):। आप इस प्रकार का एक कार्य भी लिख सकते हैं[Integer] -> ([Integer], Integer)
dan_waterworth

2
@dan_waterworth वास्तव में यह बहुत मायने रखता है। पूर्णांक को यादृच्छिक नहीं कहा जा सकता है। संख्याओं की एक सूची में यह मुख्य हो सकता है। तो सच है, एक यादृच्छिक जनरेटर का प्रकार int -> [int] हो सकता है, यानी एक फ़ंक्शन जो एक बीज लेता है और पूर्णांकों की एक यादृच्छिक सूची लौटाता है। ज़रूर, आप हैकेल के संकेतन प्राप्त करने के लिए इसके चारों ओर एक राजकीय मठ हो सकते हैं। लेकिन सवाल के एक सामान्य जवाब के रूप में, मुझे लगता है कि यह वास्तव में मददगार है।
साइमन बर्गोट

5

यह गैर-कार्यात्मक भाषाओं के लिए समान है। यहाँ वास्तव में यादृच्छिक संख्याओं की थोड़ी अलग समस्या को अनदेखा करना।

एक यादृच्छिक संख्या जनरेटर हमेशा एक बीज मूल्य लेता है और उसी बीज के लिए यादृच्छिक संख्याओं का एक ही क्रम देता है (बहुत उपयोगी है यदि आपको एक प्रोग्राम का परीक्षण करने की आवश्यकता है जो यादृच्छिक संख्याओं का उपयोग करता है)। मूल रूप से यह आपके द्वारा चुने गए बीज से शुरू होता है और फिर अगले पुनरावृत्ति के लिए बीज के रूप में अंतिम परिणाम का उपयोग करता है। इसलिए अधिकांश कार्यान्वयन "शुद्ध" कार्य हैं जैसा कि आप उनका वर्णन करते हैं: एक मान लें और उसी मूल्य के लिए हमेशा समान परिणाम लौटाएं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.