पाठक मोनद इतना जटिल है और बेकार लगता है। जावा या सी ++ जैसी अनिवार्य भाषा में, पाठक मोनाड के लिए कोई समान अवधारणा नहीं है, अगर मैं गलत नहीं हूं।
क्या आप मुझे एक सरल उदाहरण दे सकते हैं और इसे थोड़ा स्पष्ट कर सकते हैं?
पाठक मोनद इतना जटिल है और बेकार लगता है। जावा या सी ++ जैसी अनिवार्य भाषा में, पाठक मोनाड के लिए कोई समान अवधारणा नहीं है, अगर मैं गलत नहीं हूं।
क्या आप मुझे एक सरल उदाहरण दे सकते हैं और इसे थोड़ा स्पष्ट कर सकते हैं?
जवाबों:
डरो मत! पाठक मोनड वास्तव में इतना जटिल नहीं है, और वास्तविक उपयोग में आसान उपयोगिता है।
एक साधु से संपर्क करने के दो तरीके हैं: हम पूछ सकते हैं
पहले दृष्टिकोण से, पाठक मोनड कुछ सार प्रकार है
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
बस फंक्शंस के क्रम के साथ फ़ंक्शन रचना है!
Reader
क्या एक प्रकार्य है जो कुछ विशेष प्रकार के मोनड प्रकार के कार्यान्वयन के साथ होता है? यह कहना कि पहले मुझे थोड़ा कम हैरान होने में मदद मिली होगी। पहले मैं यह नहीं कर रहा था। आधा रास्ता मैंने सोचा "ओह, यह आपको कुछ ऐसा वापस करने की अनुमति देता है जो आपको लापता मूल्य की आपूर्ति करने के बाद वांछित परिणाम देगा।" मैंने सोचा कि यह उपयोगी है, लेकिन अचानक एहसास हुआ कि एक फ़ंक्शन ठीक यही करता है।
local
कार्य को कुछ और स्पष्टीकरण की आवश्यकता है ..
(Reader f) >>= g = (g (f x))
?
x
?
मुझे याद है कि आप हैरान थे, जब तक कि मुझे अपने दम पर पता नहीं चला कि पाठक मोनाड के वेरिएंट हर जगह हैं । मुझे इसकी खोज कैसे हुई? क्योंकि मैं कोड लिखता रहा जो उस पर छोटे बदलाव के रूप में निकला।
उदाहरण के लिए, एक बिंदु पर मैं ऐतिहासिक मूल्यों से निपटने के लिए कुछ कोड लिख रहा था ; समय के साथ बदलते मूल्य। इसका एक बहुत ही सरल मॉडल समय के बिंदुओं से उस समय के मूल्य पर कार्य करता है:
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
मोनाड वास्तव में वैसा ही है Reader
। History 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
और दूसरे का निर्माण करने के लिए इसका उपयोग करें Reader
। local
और withReader
कार्यों = एक बनाने Reader
कि संशोधित दूसरे करने के लिए इनपुट Reader
।
GeneralizedNewtypeDeriving
प्राप्त करने के लिए विस्तार Functor
, Applicative
, Monad
उनकी अंतर्निहित प्रकार के आधार पर newtypes के लिए, आदि।
Java या C ++ में आप बिना किसी समस्या के किसी भी चर को एक्सेस कर सकते हैं। समस्या तब दिखाई देती है जब आपका कोड बहु-थ्रेड हो जाता है।
हास्केल में आपके पास एक फ़ंक्शन से दूसरे में मान पारित करने के लिए केवल दो तरीके हैं:
fn1 -> fn2 -> fn3
फ़ंक्शन fn2
को पैरामीटर की आवश्यकता नहीं हो सकती है जो आप से पास fn1
करते हैं fn3
।रीडर मोनाड केवल उन डेटा को पास करता है जिन्हें आप फ़ंक्शन के बीच साझा करना चाहते हैं। फ़ंक्शंस उस डेटा को पढ़ सकते हैं, लेकिन इसे बदल नहीं सकते। वह सब पाठक पाठक कर रहे हैं। खैर, लगभग सभी। ऐसे कार्यों की संख्या भी है जैसे local
, लेकिन पहली बार आप asks
केवल साथ रह सकते हैं ।
do
इंपॉर्टेंट -स्टाइल' कोड इन- नोटेशन के बहुत सारे लिखने में आसानी हो, जो एक शुद्ध फ़ंक्शन में रिफैक्ट किए जाने से बेहतर होगा।
where
क्लॉज द्वारा एक से जुड़ा हुआ है , तो क्या इसे चर को पारित करने के लिए तीसरे तरीके के रूप में स्वीकार किया जाएगा?