पाठक का उद्देश्य क्या है?


122

पाठक मोनद इतना जटिल है और बेकार लगता है। जावा या सी ++ जैसी अनिवार्य भाषा में, पाठक मोनाड के लिए कोई समान अवधारणा नहीं है, अगर मैं गलत नहीं हूं।

क्या आप मुझे एक सरल उदाहरण दे सकते हैं और इसे थोड़ा स्पष्ट कर सकते हैं?


21
यदि आप चाहते हैं कि आप पाठक मोनाद का उपयोग करें - इस अवसर पर - (अपरिवर्तनीय) वातावरण से कुछ मान पढ़ें, लेकिन उस वातावरण को स्पष्ट रूप से पास नहीं करना चाहते हैं। जावा या सी ++ में, आप वैश्विक चर का उपयोग करेंगे (हालांकि यह बिल्कुल समान नहीं है)।
डैनियल फिशर

5
@ डैनियल: यह एक जवाब की
एकलकरण ०६

@TokenMacGuy एक उत्तर के लिए बहुत कम है, और मुझे कुछ देर सोचने के लिए अब बहुत देर हो चुकी है। अगर कोई नहीं करता है, तो मैं सोने के बाद करूंगा।
डेनियल फिशर

8
जावा या C ++ में, रीडर मोनाड अपने कंस्ट्रक्टर में किसी ऑब्जेक्ट को दिए गए कॉन्फ़िगरेशन पैरामीटर के अनुरूप होगा, जो ऑब्जेक्ट के जीवनकाल के दौरान कभी नहीं बदले जाते हैं। क्लोजर में, यह एक डायनामिक रूप से स्कोपेड वैरिएबल जैसा होगा जो एक पैरामीटर के रूप में स्पष्ट रूप से पारित करने की आवश्यकता के बिना एक फ़ंक्शन के व्यवहार को पैरामीरिज करने के लिए उपयोग किया जाता है।
danidiaz

जवाबों:


169

डरो मत! पाठक मोनड वास्तव में इतना जटिल नहीं है, और वास्तविक उपयोग में आसान उपयोगिता है।

एक साधु से संपर्क करने के दो तरीके हैं: हम पूछ सकते हैं

  1. सन्यासी क्या करता है ? यह किस ऑपरेशन से सुसज्जित है? ये किस काम के लिए अच्छा है?
  2. मोनाड को कैसे लागू किया जाता है? यह कहाँ से उत्पन्न होता है?

पहले दृष्टिकोण से, पाठक मोनड कुछ सार प्रकार है

data Reader env a

ऐसा है कि

-- Reader is a monad
instance Monad (Reader env)

-- and we have a function to get its environment
ask :: Reader env env

-- finally, we can run a Reader
runReader :: Reader env a -> env -> a

तो हम इसका उपयोग कैसे करते हैं? खैर, पाठक मोनड एक संगणना के माध्यम से (अंतर्निहित) कॉन्फ़िगरेशन जानकारी पास करने के लिए अच्छा है।

किसी भी समय आपके पास एक संगणना में "निरंतर" होता है जिसकी आपको विभिन्न बिंदुओं पर आवश्यकता होती है, लेकिन वास्तव में आप विभिन्न मूल्यों के साथ एक ही संगणना करने में सक्षम होना चाहते हैं, तो आपको एक पाठक मोनाद का उपयोग करना चाहिए।

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

 import Control.Monad.Reader

 data GameState = NotOver | FirstPlayerWin | SecondPlayerWin | Tie

 data Game position
   = Game {
           getNext :: position -> [position],
           getState :: position -> GameState
          }

 getNext' :: position -> Reader (Game position) [position]
 getNext' position
   = do game <- ask
        return $ getNext game position

 getState' :: position -> Reader (Game position) GameState
 getState' position
   = do game <- ask
        return $ getState game position


 negamax :: Double -> position -> Reader (Game position) Double
 negamax color position
     = do state <- getState' position 
          case state of
             FirstPlayerWin -> return color
             SecondPlayerWin -> return $ negate color
             Tie -> return 0
             NotOver -> do possible <- getNext' position
                           values <- mapM ((liftM negate) . negamax (negate color)) possible
                           return $ maximum values

यह तब किसी भी परिमित, नियतात्मक, दो खिलाड़ी खेल के साथ काम करेगा।

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

type CurrencyDict = Map CurrencyName Dollars
currencyDict :: CurrencyDict

हाजिर भाव पाने के लिए। फिर आप इस शब्दकोष को अपने कोड में कॉल कर सकते हैं .... लेकिन रुकिए! यह काम नहीं करेगा! मुद्रा शब्दकोश अपरिवर्तनीय है और इसलिए इसे न केवल आपके कार्यक्रम के जीवन के लिए समान होना चाहिए, लेकिन समय से यह संकलित हो जाता है ! तो तुम क्या करते हो? खैर, एक विकल्प यह होगा कि पाठक मोन का उपयोग करें:

 computePrice :: Reader CurrencyDict Dollars
 computePrice
    = do currencyDict <- ask
      --insert computation here

व्याख्याताओं को लागू करने में शायद सबसे क्लासिक उपयोग-मामला है। लेकिन, इससे पहले कि हम देखें, हमें एक और फ़ंक्शन शुरू करने की आवश्यकता है

 local :: (env -> env) -> Reader env a -> Reader env a

ठीक है, इसलिए हास्केल और अन्य कार्यात्मक भाषाएं लैम्ब्डा कैलकुलस पर आधारित हैं । लैम्ब्डा कैलकुलस में एक सिंटैक्स होता है जो दिखता है

 data Term = Apply Term Term | Lambda String Term | Var Term deriving (Show)

और हम इस भाषा के लिए एक मूल्यांकनकर्ता लिखना चाहते हैं। ऐसा करने के लिए, हमें एक पर्यावरण पर नज़र रखने की आवश्यकता होगी, जो शर्तों से जुड़ी बाइंडिंग की एक सूची है (वास्तव में यह बंद हो जाएगा क्योंकि हम स्थैतिक स्कूपिंग करना चाहते हैं)।

 newtype Env = Env ([(String, Closure)])
 type Closure = (Term, Env)

जब हम कर रहे हैं, हम एक मूल्य (या एक त्रुटि) बाहर निकलना चाहिए:

 data Value = Lam String Closure | Failure String

तो, दुभाषिया लिखें:

interp' :: Term -> Reader Env Value
--when we have a lambda term, we can just return it
interp' (Lambda nv t)
   = do env <- ask
        return $ Lam nv (t, env)
--when we run into a value, we look it up in the environment
interp' (Var v)
   = do (Env env) <- ask
        case lookup (show v) env of
          -- if it is not in the environment we have a problem
          Nothing -> return . Failure $ "unbound variable: " ++ (show v)
          -- if it is in the environment, then we should interpret it
          Just (term, env) -> local (const env) $ interp' term
--the complicated case is an application
interp' (Apply t1 t2)
   = do v1 <- interp' t1
        case v1 of
           Failure s -> return (Failure s)
           Lam nv clos -> local (\(Env ls) -> Env ((nv, clos) : ls)) $ interp' t2
--I guess not that complicated!

अंत में, हम एक तुच्छ वातावरण पारित करके इसका उपयोग कर सकते हैं:

interp :: Term -> Value
interp term = runReader (interp' term) (Env [])

और वह यह है। लैम्ब्डा कैलकुलस के लिए एक पूरी तरह कार्यात्मक दुभाषिया।


इसके बारे में सोचने का दूसरा तरीका यह है कि यह कैसे लागू किया जाए? इसका उत्तर यह है कि पाठक मोनाड वास्तव में सभी साधुओं में से सबसे सरल और सुरुचिपूर्ण है।

newtype Reader env a = Reader {runReader :: env -> a}

पाठक कार्यों के लिए सिर्फ एक फैंसी नाम है! हमने पहले ही परिभाषित runReaderकर दिया है कि एपीआई के अन्य भागों के बारे में क्या है? खैर, हर Monadभी एक है Functor:

instance Functor (Reader env) where
   fmap f (Reader g) = Reader $ f . g

अब, एक सनद पाने के लिए:

instance Monad (Reader env) where
   return x = Reader (\_ -> x)
   (Reader f) >>= g = Reader $ \x -> runReader (g (f x)) x

जो इतना डरावना नहीं है। askवास्तव में सरल है:

ask = Reader $ \x -> x

जबकि localइतना बुरा नहीं है:

local f (Reader g) = Reader $ \x -> runReader g (f x)

ठीक है, इसलिए पाठक मोनाड सिर्फ एक फ़ंक्शन है। पाठक आखिर क्यों हैं? अच्छा प्रश्न। वास्तव में, आपको इसकी आवश्यकता नहीं है!

instance Functor ((->) env) where
  fmap = (.)

instance Monad ((->) env) where
  return = const
  f >>= g = \x -> g (f x) x

ये और भी सरल हैं। क्या अधिक है, askबस है idऔर localबस फंक्शंस के क्रम के साथ फ़ंक्शन रचना है!


6
बहुत दिलचस्प जवाब। ईमानदारी से, मैं इसे कई बार फिर से पढ़ता हूं, जब मैं मोनाड की समीक्षा करना चाहता हूं। वैसे, नगमैक्स एल्गोरिथ्म के बारे में, "मान <- mapM (नकारात्मक। नेगैमैक्स (नकारात्मक रंग)) संभव है" सही नहीं लगता है। मुझे पता है कि, आपके द्वारा प्रदान किया जाने वाला कोड सिर्फ यह दिखाने के लिए है कि पाठक मोनाड कैसे काम करता है। लेकिन अगर आपके पास समय है, तो आप नेगैमैक्स एल्गोरिथ्म के कोड को सही कर सकते हैं? क्योंकि, यह दिलचस्प है, जब आप नेग्मैक्स को हल करने के लिए रीडर मोनाड का उपयोग करते हैं।
चिपबेक 10

4
तो Readerक्या एक प्रकार्य है जो कुछ विशेष प्रकार के मोनड प्रकार के कार्यान्वयन के साथ होता है? यह कहना कि पहले मुझे थोड़ा कम हैरान होने में मदद मिली होगी। पहले मैं यह नहीं कर रहा था। आधा रास्ता मैंने सोचा "ओह, यह आपको कुछ ऐसा वापस करने की अनुमति देता है जो आपको लापता मूल्य की आपूर्ति करने के बाद वांछित परिणाम देगा।" मैंने सोचा कि यह उपयोगी है, लेकिन अचानक एहसास हुआ कि एक फ़ंक्शन ठीक यही करता है।
जिग्गीस्टार

1
इसे पढ़ने के बाद मैं इसे सबसे ज्यादा समझता हूं। हालांकि, इस localकार्य को कुछ और स्पष्टीकरण की आवश्यकता है ..
क्रिस्टोफ़ डे ट्रॉयर

@Philip मैं मोनाड उदाहरण के बारे में एक सवाल है। क्या हम बाइंड फ़ंक्शन को नहीं लिख सकते (Reader f) >>= g = (g (f x))?
ज़ेरोनोन

@ एज़रॉन कहाँ है x?
आशीष नेगी

56

मुझे याद है कि आप हैरान थे, जब तक कि मुझे अपने दम पर पता नहीं चला कि पाठक मोनाड के वेरिएंट हर जगह हैं । मुझे इसकी खोज कैसे हुई? क्योंकि मैं कोड लिखता रहा जो उस पर छोटे बदलाव के रूप में निकला।

उदाहरण के लिए, एक बिंदु पर मैं ऐतिहासिक मूल्यों से निपटने के लिए कुछ कोड लिख रहा था ; समय के साथ बदलते मूल्य। इसका एक बहुत ही सरल मॉडल समय के बिंदुओं से उस समय के मूल्य पर कार्य करता है:

import Control.Applicative

-- | A History with timeline type t and value type a.
newtype History t a = History { observe :: t -> a }

instance Functor (History t) where
    -- Apply a function to the contents of a historical value
    fmap f hist = History (f . observe hist)

instance Applicative (History t) where
    -- A "pure" History is one that has the same value at all points in time
    pure = History . const

    -- This applies a function that changes over time to a value that also 
    -- changes, by observing both at the same point in time.
    ff <*> fx = History $ \t -> (observe ff t) (observe fx t)

instance Monad (History t) where
    return = pure
    ma >>= f = History $ \t -> observe (f (observe ma t)) t

Applicativeउदाहरण के साधन कि यदि आपनेemployees :: History Day [Person] और customers :: History Day [Person]आप यह कर सकते हैं:

-- | For any given day, the list of employees followed by the customers
employeesAndCustomers :: History Day [Person]
employeesAndCustomers = (++) <$> employees <*> customers

Ie, Functorऔर Applicativeइतिहास के साथ काम करने के लिए हमें नियमित, गैर-ऐतिहासिक कार्यों को अनुकूलित करने की अनुमति देता है।

फ़ंक्शन पर विचार करके मोनड का उदाहरण सबसे सहज रूप से समझा जाता है (>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c। प्रकार a -> History t bका एक फ़ंक्शन एक फ़ंक्शन है जो मानों के aइतिहास में मैप करता है b; उदाहरण के लिए, आप कर सकते हैं getSupervisor :: Person -> History Day Supervisor, और getVP :: Supervisor -> History Day VP। तो इस Historyतरह के कार्यों की रचना के बारे में मोनाड उदाहरण है; उदाहरण के लिए,getSupervisor >=> getVP :: Person -> History Day VP वह फ़ंक्शन है जो किसी भी Person, के लिए , जो VPकि उनके पास है , का इतिहास है ।

खैर, यह Historyमोनाड वास्तव में वैसा ही है ReaderHistory t aवास्तव में जैसा है Reader t a(जैसा है वैसा ही है t -> a)।

एक और उदाहरण: मैं OLAP प्रोटोटाइप कर रहा हूं हाल ही में हास्केल में डिजाइन करती है। यहाँ एक विचार एक "हाइपरक्यूब" का है, जो आयामों के एक सेट के मानों के मानों से मैपिंग है। चलो हम फिरसे चलते है:

newtype Hypercube intersection value = Hypercube { get :: intersection -> value }

हाइपरक्यूब पर एक सामान्य ऑपरेशन हाइपरक्यूब के संबंधित बिंदुओं के लिए एक बहु-स्थान स्केलर फ़ंक्शन को लागू करना है। इसके लिए हम एक Applicativeउदाहरण को परिभाषित करके प्राप्त कर सकते हैं Hypercube:

instance Functor (Hypercube intersection) where
    fmap f cube = Hypercube (f . get cube)


instance Applicative (Hypercube intersection) where
    -- A "pure" Hypercube is one that has the same value at all intersections
    pure = Hypercube . const

    -- Apply each function in the @ff@ hypercube to its corresponding point 
    -- in @fx@.
    ff <*> fx = Hypercube $ \x -> (get ff x) (get fx x)

मैंने अभी Historyऊपर दिए गए कोड को कॉपी किया और नाम बदल दिए। जैसा आप कह सकें,Hypercube बस भी है Reader

यह चलता ही जाता है। उदाहरण के लिए, भाषा दुभाषिए भी उबलते हैंReader जब आप इस मॉडल को लागू करते हैं तो :

  • अभिव्यक्ति = ए Reader
  • मुफ्त चर = का उपयोग करता है ask
  • मूल्यांकन वातावरण = Reader निष्पादन पर्यावरण।
  • बंधन बांधना = local

एक अच्छा सादृश्य यह है कि इसमें "छेद" का Reader r aप्रतिनिधित्व करता aहै, जो आपको यह जानने से रोकता है कि aहम किस बारे में बात कर रहे हैं। aजब आप rछेद में भरने के लिए आपूर्ति करते हैं तो आप केवल एक वास्तविक प्राप्त कर सकते हैं । इस तरह की कई चीजें हैं। उपरोक्त उदाहरणों में, "इतिहास" एक ऐसा मूल्य है जिसकी गणना तब तक नहीं की जा सकती जब तक आप एक समय निर्दिष्ट नहीं करते हैं, एक हाइपरक्यूब एक मूल्य है जिसे तब तक गणना नहीं किया जा सकता जब तक आप एक चौराहा निर्दिष्ट नहीं करते हैं, और एक भाषा अभिव्यक्ति एक मूल्य है जो कर सकता है जब तक आप चर के मूल्यों की आपूर्ति नहीं करेंगे, तब तक गणना नहीं की जा सकती। यह आपको एक अंतर्ज्ञान भी देता है कि क्यों Reader r aवही है r -> a, क्योंकि इस तरह के एक कार्य भी सहज रूप से एक aलापता है r

तो Functor, Applicativeऔर Monadउदाहरण के मामलों के Readerलिए एक बहुत ही उपयोगी सामान्यीकरण है जहाँ आप किसी भी प्रकार के मॉडलिंग कर रहे हैं "जो aकि एक लापता हैr ," और आपको इन "अपूर्ण" वस्तुओं का इलाज करने की अनुमति देता है जैसे कि वे पूर्ण थे।

फिर भी एक ही बात कहने का एक और तरीका: एक Reader r aऐसी चीज है जो उपभोग करता है rऔर पैदा करता aहै Functor, Applicativeऔर Monadइंस्टेंस Readerएस के साथ काम करने के लिए बुनियादी पैटर्न हैं । Functor= जो Readerदूसरे के आउटपुट को संशोधित करता है Reader; Applicative= दो Readerएस को एक ही इनपुट से कनेक्ट करें और उनके आउटपुट को संयोजित करें; Monad= एक के परिणाम का निरीक्षण करें Readerऔर दूसरे का निर्माण करने के लिए इसका उपयोग करें Readerlocalऔर withReaderकार्यों = एक बनाने Readerकि संशोधित दूसरे करने के लिए इनपुट Reader


5
बहुत बढ़िया जवाब। तुम भी उपयोग कर सकते हैं GeneralizedNewtypeDerivingप्राप्त करने के लिए विस्तार Functor, Applicative, Monadउनकी अंतर्निहित प्रकार के आधार पर newtypes के लिए, आदि।
रीन हेनरिक 19

20

Java या C ++ में आप बिना किसी समस्या के किसी भी चर को एक्सेस कर सकते हैं। समस्या तब दिखाई देती है जब आपका कोड बहु-थ्रेड हो जाता है।

हास्केल में आपके पास एक फ़ंक्शन से दूसरे में मान पारित करने के लिए केवल दो तरीके हैं:

  • आप कॉल करने योग्य फ़ंक्शन के एक इनपुट पैरामीटर के माध्यम से मान पास करते हैं। कमियां हैं: 1) आप उस तरह से सभी चर पास नहीं कर सकते हैं - इनपुट मापदंडों की सूची बस आपके दिमाग को उड़ा देती है। 2) फ़ंक्शन कॉल के अनुक्रम में: fn1 -> fn2 -> fn3फ़ंक्शन fn2को पैरामीटर की आवश्यकता नहीं हो सकती है जो आप से पास fn1करते हैं fn3
  • आप कुछ मोनाड के दायरे में मान पास करते हैं। दोष यह है: आपको यह समझना होगा कि मोनाड गर्भाधान क्या है। मानों को पास करना, उन अनुप्रयोगों में से एक है, जहां आप मोनाड्स का उपयोग कर सकते हैं। वास्तव में मोनाड गर्भाधान अविश्वसनीय शक्तिशाली है। परेशान मत हो, अगर आपको एक बार में जानकारी नहीं मिली। बस प्रयास करते रहें, और विभिन्न ट्यूटोरियल पढ़ें। आपको जो ज्ञान मिलेगा वह आपको चुकाना होगा।

रीडर मोनाड केवल उन डेटा को पास करता है जिन्हें आप फ़ंक्शन के बीच साझा करना चाहते हैं। फ़ंक्शंस उस डेटा को पढ़ सकते हैं, लेकिन इसे बदल नहीं सकते। वह सब पाठक पाठक कर रहे हैं। खैर, लगभग सभी। ऐसे कार्यों की संख्या भी है जैसे local, लेकिन पहली बार आप asksकेवल साथ रह सकते हैं ।


3
आस-पास के डेटा को गुप्त रूप से पास करने के लिए मोनाड्स का उपयोग करने का एक और दोष यह है कि यह बहुत आसान है कि अपने आप को ' doइंपॉर्टेंट -स्टाइल' कोड इन- नोटेशन के बहुत सारे लिखने में आसानी हो, जो एक शुद्ध फ़ंक्शन में रिफैक्ट किए जाने से बेहतर होगा।
बेंजामिन हॉजसन

4
@BenjaminHodgson-doation में भिक्षुओं के साथ 'अनिवार्य-दिखने वाला' कोड लिखना जरूरी नहीं है कि साइड-इफ़ेक्टिव (अशुद्ध) कोड लिखा जाए। दरअसल, हास्केल में साइड-इफ़ेक्ट कोड केवल IO मोनड के अंदर ही संभव हो सकता है।
दिमित्री बेस्पालोव

यदि अन्य फ़ंक्शन एक whereक्लॉज द्वारा एक से जुड़ा हुआ है , तो क्या इसे चर को पारित करने के लिए तीसरे तरीके के रूप में स्वीकार किया जाएगा?
Elmex80s
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.