सवाल हास्केल में मोनाड और आवेदक के बीच अंतर


मैंने बस निम्नलिखित से पढ़ा है typeclassopedia के बीच के अंतर के बारे में Monad तथा Applicative। मैं समझ सकता हूं कि नहीं है join में Applicative। लेकिन निम्नलिखित वर्णन मेरे लिए अस्पष्ट दिखता है और मैं समझ नहीं पा रहा हूं कि एक monadic गणना / कार्रवाई के "परिणाम" का क्या मतलब है। तो, अगर मैं एक मूल्य डालता हूं Maybe, जो एक मोनड बनाता है, इस "गणना" का नतीजा क्या है?

आइए (>> =) के प्रकार पर अधिक बारीकी से देखें। बुनियादी अंतर्ज्ञान है   कि यह दो गणनाओं को एक बड़ी गणना में जोड़ती है।   पहला तर्क, एम ए, पहली गणना है। हालांकि, यह होगा   उबाऊ अगर दूसरा तर्क सिर्फ एक एम बी था; तो वहां नहीं होगा   कम्प्यूटेशंस के लिए एक दूसरे के साथ बातचीत करने का तरीका (वास्तव में, यह   आवेदक के साथ बिल्कुल स्थिति है)। तो, दूसरा तर्क है   (>> =) ने एक -> एम बी टाइप किया है: इस प्रकार का एक फ़ंक्शन, जिसके परिणामस्वरूप   पहली गणना, चलाने के लिए एक दूसरी गणना का उत्पादन कर सकते हैं।   ... सहजता से, यह पिछले से आउटपुट का उपयोग करने की क्षमता है   यह तय करने के लिए गणना कि मोनाड बनाता है जो आगे चलने के लिए गणना करता है   आवेदक से अधिक शक्तिशाली। एक आवेदक की संरचना   गणना निश्चित है, जबकि एक मोनाड गणना की संरचना कर सकते हैं   मध्यवर्ती परिणामों के आधार पर बदलें।

क्या कोई ठोस उदाहरण है "पिछले कंप्यूटेशंस से आउटपुट का उपयोग करने की क्षमता का निर्धारण करने के लिए यह तय करने के लिए कि कौन सी गणनाएं चलती हैं", जो आवेदक के पास नहीं है?


44
2018-04-28 13:17


मूल


Just 1 एक "गणना" का वर्णन करता है, जिसका "परिणाम" 1 है। Nothing एक गणना का वर्णन करता है जो कोई परिणाम नहीं देता है। - Will Ness
यह भी देखें arrowdodgerसवाल है "मोनाद हमें एक आवेदक पर क्या लाभ देता है?", जिसमें कुछ अच्छे उत्तर हैं (पूर्ण प्रकटीकरण: मेरा एक सहित)। - Antal Spector-Zabusky


जवाब:


मेरा पसंदीदा उदाहरण "पूरी तरह से आवेदक या तो" है। हम या तो बेस मोनाड इंस्टेंस का विश्लेषण करके शुरू करेंगे

instance Monad (Either e) where
  return = Right
  Left e  >>= _ = Left e
  Right a >>= f = f a

यह उदाहरण एक बहुत ही प्राकृतिक लघु-सर्किटिंग धारणा को एम्बेड करता है: हम बाएं से दाएं आगे बढ़ते हैं और एक बार एक गणना "विफल" हो जाती है Left तो बाकी सभी भी करते हैं। प्राकृतिक भी है Applicative उदाहरण है कि कोई भी Monad है

instance Applicative (Either e) where
  pure  = return
  (<*>) = ap

कहा पे ap ए से पहले बाएं से दाएं अनुक्रमिक से कुछ भी नहीं है return:

ap :: Monad m => m (a -> b) -> m a -> m b
ap mf ma = do 
  f <- mf
  a <- ma
  return (f a)

अब इसके साथ परेशानी Either उदाहरण प्रकाश में आता है जब आप त्रुटि संदेशों को इकट्ठा करना चाहते हैं जो गणना में कहीं भी होते हैं और किसी भी तरह से त्रुटियों का सारांश उत्पन्न करते हैं। यह शॉर्ट सर्किटिंग के चेहरे में उड़ता है। यह भी प्रकार के चेहरे में उड़ता है (>>=)

(>>=) :: m a -> (a -> m b) -> m b

अगर हम सोचते हैं m a "अतीत" और के रूप में m b तब "भविष्य" के रूप में (>>=) भविष्य से अतीत से भविष्य का उत्पादन करता है क्योंकि यह "stepper" चला सकता है (a -> m b)। यह "stepper" की कीमत है कि मूल्य a वास्तव में भविष्य में मौजूद है ... और यह असंभव है Either। इसलिये (>>=)  मांगों लघु सर्किटिंग।

तो इसके बजाय हम एक लागू करेंगे Applicative उदाहरण जो संबंधित नहीं हो सकता है Monad

instance Monoid e => Applicative (Either e) where
  pure = Right

अब कार्यान्वयन (<*>) ध्यान से विचार करने के लायक विशेष हिस्सा है। यह अपने पहले में "शॉर्ट सर्किटिंग" की कुछ मात्रा करता है 3 मामले, लेकिन चौथे में कुछ दिलचस्प है।

  Right f <*> Right a = Right (f a)     -- neutral
  Left  e <*> Right _ = Left e          -- short-circuit
  Right _ <*> Left  e = Left e          -- short-circuit
  Left e1 <*> Left e2 = Left (e1 <> e2) -- combine!

फिर से ध्यान दें कि अगर हम बाएं तर्क को "अतीत" और सही भविष्य के रूप में सही भविष्य के बारे में सोचते हैं (<*>)की तुलना में विशेष है (>>=) क्योंकि "भविष्य" की गणना करने के लिए इसे "अतीत" से परिणामों की आवश्यकता के बजाय भविष्य में और अतीत को समानांतर में "खोलने" की अनुमति है।

इसका मतलब है, सीधे, कि हम पूरी तरह से उपयोग कर सकते हैं Applicative  Either त्रुटियों को इकट्ठा करने के लिए, अनदेखा Rightअगर कोई है Leftश्रृंखला में मौजूद है

> Right (+1) <*> Left [1] <*> Left [2]
> Left [1,2]

तो आइए इस अंतर्ज्ञान को अपने सिर पर फ़्लिप करें। हम पूरी तरह से आवेदक के साथ क्या नहीं कर सकते हैं Either? खैर, चूंकि इसका संचालन अतीत को चलाने से पहले भविष्य की जांच करने पर निर्भर करता है, इसलिए हम अतीत में मूल्यों के आधार पर भविष्य की संरचना निर्धारित करने में सक्षम होना चाहिए। दूसरे शब्दों में, हम नहीं लिख सकते हैं

ifA :: Applicative f => f Bool -> f a -> f a -> f a

जो निम्नलिखित समीकरणों को संतुष्ट करता है

ifA (pure True)  t e == t
ifA (pure False) t e == e

जबकि हम लिख सकते हैं ifM

ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM mbool th el = do
  bool <- mbool
  if bool then th else el

ऐसा है कि

ifM (return True)  t e == t
ifM (return False) t e == e

यह असंभवता उत्पन्न होती है क्योंकि ifA तर्क गणना में एम्बेडेड मानों के आधार पर परिणाम गणना के बिल्कुल सही विचार प्रस्तुत करता है।


54
2018-04-28 13:34



क्या गलत है ifA t c a = g <$> t <*> c <*> a where g b x y = if b then x else y? - Will Ness
@WillNess: वह हमेशा सभी कम्प्यूटेशनल संरचना का उपयोग करता है / सभी प्रभाव चलाता है। उदाहरण के लिए, ifA (Just True) (Just ()) Nothing == Nothing, जहाँ तक ifM (Just True) (Just ()) Nothing == Just ()। यह शायद कहने के लिए और अधिक सटीक होगा "हम नहीं लिख सकते हैं ifA  अपेक्षित अर्थशास्त्र के साथ"। - Antal Spector-Zabusky
@ एंटाल्स-जेड आह, हाँ, धन्यवाद। फिर फिर, आवेदक में, यह होगा अपेक्षित अर्थशास्त्र हो, है ना? मुद्दा यह है कि क्या हम इसे प्रभावित कर सकते हैं आकार एक गणना के अतीत जिस बिंदु पर हम हैं। शायद ifX इस मुद्दे का पता लगाने के लिए एक अच्छा वाहन नहीं है। - Will Ness
मैं उस पर बहस करता हूं ifM मोनाद की शक्ति की जांच करने के लिए बिल्कुल वाहन है, हालांकि यह मानता है ifM अपेक्षित अर्थशास्त्र नहीं है if' <$> a <*> b <*> c where if' b t e = if b then t else e। असली चुनौती तब होती है जब प्रभाव मूल्यों के साथ उलझ जाते हैं। - J. Abrahamson
मुझे लगता है कि यह ध्यान रखना महत्वपूर्ण है कि जब एक प्रकार का होता है Monad उदाहरण परिभाषित, इसकी Applicative उदाहरण जरूर उस के साथ संगत हो Monad उदाहरण (pure = return, (<*>) = एपी). While the second इस उत्तर में आवेदक की आवृत्ति परिभाषा को संतुष्ट करता है Applicative कानून, यह इस दस्तावेज की आवश्यकता का उल्लंघन करता है। इस दूसरे को पाने का सही तरीका Applicative उदाहरण यह है कि इसे किसी अन्य प्रकार के लिए परिभाषित करना है जो आइसोमोर्फिक है Either। - Luis Casillas


Just 1 एक "गणना" का वर्णन करता है, जिसका "परिणाम" 1 है। Nothing एक गणना का वर्णन करता है जो कोई परिणाम नहीं देता है।

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

इसका मतलब यह है कि इसका क्या अर्थ है। Monadic श्रृंखला में

return 42            >>= (\x ->
if x == 1
   then
        return (x+1) 
   else 
        return (x-1) >>= (\y -> 
        return (1/y)     ))

if चुनने के लिए क्या गणना गणना करता है।

आवेदक के मामले में, में

pure (1/) <*> ( pure (+(-1)) <*> pure 1 )

सभी कार्य "अंदर" computations काम करते हैं, एक श्रृंखला तोड़ने का कोई मौका नहीं है। प्रत्येक फ़ंक्शन बस उस मूल्य को बदल देता है जो इसे खिलाया जाता है। गणना संरचना के "आकार" कार्यों के दृष्टिकोण से पूरी तरह से "बाहरी पर" है।

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

मोनैड के साथ, कार्य स्वयं अपने चयन के लिए गणना बनाते हैं।


32
2018-04-28 16:41



यह एक बहुत अच्छा जवाब है। +1 - eazar001
यह उदाहरण कुछ चीजें दिखाता है succintly : आप न केवल आवेदक फंक्शंस के रूप में मूल्यों को बदल सकते हैं, बल्कि आप भी ... 1) monadic संचालन की एक श्रृंखला के भीतर कहीं भी गणना के इतिहास को स्टोर कर सकते हैं, 2) तय करें कि कैसे, और मूल्यों को बदलने के लिए कब (यदि मान हैं सहेजे गए गणनाओं के इतिहास के आधार पर (संभवतः गैर-रैखिक तरीके से) परिवर्तित किया जा सकता है, 3) इन monadic संचालन के शरीर के भीतर मॉडल दुष्प्रभाव, 4) अधिक trivially, उपयोग do-block अंकन। - eazar001
सीएफ यह, बाद में, संबंधित, मेरा जवाब कुछ स्पष्टीकरण तुलना के लिए। - Will Ness
सही के रूप में चिह्नित एक बेकार है, जबकि यह असफल वास्तव में मदद करता है जब आप भाषा से परिचित नहीं हैं ... - Ivan
@Ivan यह गैर-हास्केलर्स के लिए कठिन हो सकता है, लेकिन यह वास्तव में बहुत बेहतर है। मुख्य अंतर यह है कि आवेदक के संयोजन में शामिल सभी गणना विवरण (a <*> b <*> ... ) पहले से ज्ञात हैं; लेकिन मोनाडिक संयोजन के साथ ( a >>= (\ ... -> b >>= ... ) ) प्रत्येक गणना की गणना पिछले गणना द्वारा उत्पादित मूल्य पर गणना की जाती है ("निर्भर है")। दो समयरेखाएं शामिल हैं, दो दुनिया: एक शुद्ध जहां गणना गणना (a, b ...) बनाए और संयुक्त होते हैं, और संभावित रूप से अशुद्ध होते हैं जहां वे "रन" होते हैं - जहां वास्तविक गणना होती है। - Will Ness


यहां @ जे पर मेरा लेना है। अब्राहमसन का उदाहरण क्यों है ifA उदाहरण के अंदर मूल्य का उपयोग नहीं कर सकते (pure True)। संक्षेप में, यह अभी भी अनुपस्थिति के लिए उबलता है join से कार्य Monad में Applicative, जो दो अलग-अलग दृष्टिकोणों को एकजुट करता है typeclassopedia के बीच अंतर की व्याख्या करने के लिए Monad तथा Applicative

तो @ जे का उपयोग कर। अब्राहमसन का पूरी तरह से आवेदक का उदाहरण Either:

instance Monoid e => Applicative (Either e) where
  pure = Right

  Right f <*> Right a = Right (f a)     -- neutral
  Left  e <*> Right _ = Left e          -- short-circuit
  Right _ <*> Left  e = Left e          -- short-circuit
  Left e1 <*> Left e2 = Left (e1 <> e2) -- combine!

(जिसमें समान शॉर्ट सर्किटिंग प्रभाव है Either  Monad), और यह ifA समारोह

ifA :: Applicative f => f Bool -> f a -> f a -> f a

यदि हम उल्लिखित समीकरणों को प्राप्त करने का प्रयास करते हैं तो क्या होगा:

ifA (pure True)  t e == t
ifA (pure False) t e == e

?

खैर, जैसा कि पहले से ही बताया गया है, अंत में, की सामग्री (pure True), बाद की गणना द्वारा उपयोग नहीं किया जा सकता है। लेकिन तकनीकी रूप से बोलते हुए, यह सही नहीं है। हम कर सकते हैं की सामग्री का उपयोग करें (pure True) से एक Monad भी एक है Functor साथ में fmap। हम कर सकते हैं:

ifA' b t e = fmap b (\x -> if x then t else e) 

समस्या रिटर्न प्रकार के साथ है ifA', जो है f (f a)। में Applicative, दो घोंसले को ध्वस्त करने का कोई तरीका नहीं है Applicativeएक में एस। लेकिन यह ढहने वाला कार्य ठीक वही है join में Monad प्रदर्शन। इसलिए,

ifA = join . ifA' 

के लिए समीकरणों को पूरा करेगा ifA, अगर हम कार्यान्वित कर सकते हैं join उचित रूप से। क्या Applicative यहां गायब है बिल्कुल ठीक है join समारोह। दूसरे शब्दों में, हम किसी भी तरह से पिछले परिणाम से परिणाम का उपयोग कर सकते हैं Applicative। लेकिन एक में ऐसा कर रहा हूँ Applicative ढांचे में एक नेस्टेड आवेदक मूल्य पर वापसी मूल्य के प्रकार को बढ़ाने में शामिल होगा, जिसका हमारे पास एक-स्तर के आवेदक मूल्य पर वापस लाने का कोई साधन नहीं है। यह एक गंभीर समस्या होगी क्योंकि, उदाहरण के लिए, हम फ़ंक्शंस का उपयोग करके रचना नहीं कर सकते हैं Applicativeएस उचित रूप से। का उपयोग करते हुए join इस मुद्दे को हल करता है, लेकिन बहुत परिचय join बढ़ावा देता है Applicative ए के लिए Monad


15
2018-04-28 23:56



बिंगो! ठीक ठीक :) - J. Abrahamson
मुझे लगता है कि आपको मिल गया fmap में तर्क ifA' गलत क्रम में कार्य करें। Afais यह होना चाहिए ifA' b t e = fmap (\x -> if x then t else e) b अन्यथा प्रकार हस्ताक्षर बहुत अजीब लग रहा है। - hasufell
क्या समस्या यहां शामिल हो रही है कि ये 3 ठीक हैं: join Right (Right a) = Right a; join Right (Left e) = Left e; join Left (Left e) = Left eलेकिन यह कोई अच्छा नहीं है: join Left (Right a) =? Left (Right a)? - RomnieEE
वास्तव में पहली बार इसे थोड़ा सा प्रयास कर रहा हूं, मुझे लगता है कि मैं एक प्रकार की गड़बड़ी में समाप्त हो रहा हूं joinका तर्क Result<Result<_,_>,Result<_,_>>, या खराब। - RomnieEE


अंतर की कुंजी के प्रकार में देखा जा सकता है ap बनाम प्रकार का =<<

ap :: m (a->b) -> (m a->m b)
=<< :: (a->m b) -> (m a->m b)

दोनों मामलों में है m a, लेकिन केवल दूसरे मामले में m a तय कर सकते हैं कि समारोह क्या है (a->m b) लग जाता है। इसके बदले में, समारोह (a->m b) "निर्णय ले सकते हैं" कि आगे के कार्य को लागू किया जाता है - इस तरह के उत्पादन से m b इसमें "शामिल नहीं है" b (पसंद [], Nothing या Left)।

में Applicative "अंदर" कार्यों के लिए कोई रास्ता नहीं है m (a->b) ऐसे "निर्णय" करने के लिए - वे हमेशा प्रकार का मूल्य उत्पन्न करते हैं b

f 1 = Nothing -- here f "decides" to produce Nothing
f x = Just x

Just 1 >>= f >>= g -- g doesn't get applied, because f decided so.

में Applicative यह संभव नहीं है, इसलिए एक उदाहरण नहीं दिखा सकता है। निकटतम है:

f 1 = 0
f x = x

g <$> f <$> Just 1 -- oh well, this will produce Just 0, but can't stop g
                   -- from getting applied

8
2018-04-28 13:47





लेकिन निम्नलिखित वर्णन मेरे लिए अस्पष्ट दिखता है और मैं समझ नहीं पा रहा हूं कि एक monadic गणना / कार्रवाई के "परिणाम" का क्या मतलब है।

खैर, यह अस्पष्टता कुछ हद तक जानबूझकर है, क्योंकि "परिणाम" एक monadic गणना का क्या है जो कुछ प्रकार पर निर्भर करता है। सबसे अच्छा जवाब थोड़ा सा ट्यूटोलॉजिकल है: "परिणाम" (या परिणाम, क्योंकि एक से अधिक हो सकते हैं) उदाहरण के कार्यान्वयन के किसी भी मूल्य (ओं) है (>>=) :: Monad m => m a -> (a -> m b) -> m b समारोह तर्क के साथ invokes।

तो, अगर मैं एक मूल्य डालता हूं Maybe, जो एक मोनड बनाता है, इस "गणना" का नतीजा क्या है?

Maybe मोनैड इस तरह दिखता है:

instance Monad Maybe where
    return = Just
    Nothing >>= _ = Nothing
    Just a >>= k = k a

यहां एकमात्र चीज जो "परिणाम" के रूप में योग्य है वह है a के लिए दूसरे समीकरण में >>=, क्योंकि यह एकमात्र चीज है जिसे कभी भी दूसरे तर्क के लिए "खिलाया" मिलता है >>=

अन्य उत्तरों के बारे में गहराई में चला गया है ifA बनाम ifM अंतर, इसलिए मैंने सोचा कि मैं एक और महत्वपूर्ण अंतर को उजागर करूंगा: आवेदक लिखते हैं, monads नहीं करते हैं। साथ में Monadअगर आप एक बनाना चाहते हैं Monad जो दो मौजूदा लोगों के प्रभाव को जोड़ती है, आपको उनमें से एक को मोनाड ट्रांसफार्मर के रूप में फिर से लिखना होगा। इसके विपरीत, यदि आपके पास दो हैं Applicatives जैसा कि नीचे दिखाया गया है, आप आसानी से उनमें से अधिक जटिल बना सकते हैं। (कोड से कॉपी किया गया है transformers।)

-- | The composition of two functors.
newtype Compose f g a = Compose { getCompose :: f (g a) }

-- | The composition of two functors is also a functor.
instance (Functor f, Functor g) => Functor (Compose f g) where
    fmap f (Compose x) = Compose (fmap (fmap f) x)

-- | The composition of two applicatives is also an applicative.
instance (Applicative f, Applicative g) => Applicative (Compose f g) where
    pure x = Compose (pure (pure x))
    Compose f <*> Compose x = Compose ((<*>) <$> f <*> x)


-- | The product of two functors.
data Product f g a = Pair (f a) (g a)

-- | The product of two functors is also a functor.
instance (Functor f, Functor g) => Functor (Product f g) where
    fmap f (Pair x y) = Pair (fmap f x) (fmap f y)

-- | The product of two applicatives is also an applicative.
instance (Applicative f, Applicative g) => Applicative (Product f g) where
    pure x = Pair (pure x) (pure x)
    Pair f g <*> Pair x y = Pair (f <*> x) (g <*> y)


-- | The sum of a functor @f@ with the 'Identity' functor
data Lift f a = Pure a | Other (f a)

-- | The sum of two functors is always a functor.
instance (Functor f) => Functor (Lift f) where
    fmap f (Pure x) = Pure (f x)
    fmap f (Other y) = Other (fmap f y)

-- | The sum of any applicative with 'Identity' is also an applicative 
instance (Applicative f) => Applicative (Lift f) where
    pure = Pure
    Pure f <*> Pure x = Pure (f x)
    Pure f <*> Other y = Other (f <$> y)
    Other f <*> Pure x = Other (($ x) <$> f)
    Other f <*> Other y = Other (f <*> y)

अब, अगर हम इसमें जोड़ते हैं Constant functor / अनुप्रयोगी:

newtype Constant a b = Constant { getConstant :: a }

instance Functor (Constant a) where
    fmap f (Constant x) = Constant x

instance (Monoid a) => Applicative (Constant a) where
    pure _ = Constant mempty
    Constant x <*> Constant y = Constant (x `mappend` y)

... हम "आवेदक" इकट्ठा कर सकते हैं Either"अन्य प्रतिक्रियाओं से बाहर Lift तथा Constant:

type Error e a = Lift (Constant e) a

1
2018-04-29 18:05