2016-04-26 12 views
5

मैंने एक डोमेन-विशिष्ट भाषा के लिए दो monads लिखा है जो मैं विकसित कर रहा हूं। पहला Lang है, जिसमें लाइन लाइन भाषा को पार्स करने के लिए आवश्यक सब कुछ शामिल होना चाहिए। मुझे पता था कि मैं पाठक, लेखक, और राज्य चाहेगा, तो मैं RWS इकाई का प्रयोग किया:दो मोनैड ट्रांसफॉर्मर स्टैक्स को जोड़ते समय आवेदक प्राप्त करने में असमर्थ

newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) a } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    , MonadIO 
    ) 

दोनों लगते हैं: एक उपयोगकर्ता के साथ बातचीत करने

type LangLog = [String] 
type LangState = [(String, String)] 
type LangConfig = [(String, String)] 

newtype Lang a = Lang { unLang :: RWS LangConfig LangLog LangState a } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    , MonadReader LangConfig 
    , MonadWriter LangLog 
    , MonadState LangState 
    ) 

दूसरा Repl है, जो Haskeline का उपयोग करता है व्यक्तिगत रूप से काम करते हैं (वे संकलित करते हैं और मैंने जीएचसीआई में उनके व्यवहार के साथ खेला है), लेकिन मैं Lang को उपयोगकर्ता से लाइनों को पार्स करने के लिए Repl में एम्बेड करने में असमर्थ रहा हूं। मुख्य सवाल यह है कि, मैं यह कैसे कर सकता हूं?

newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) (Lang a) } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    , MonadIO 
    , MonadReader LangConfig 
    , MonadWriter LangLog 
    , MonadState LangState 
    ) 

यह ज्यादातर typechecks, लेकिन मैं Applicative (Monad और सभी आराम के लिए आवश्यक) प्राप्त नहीं सकता:

अधिक विशेष रूप से, अगर मैं Repl लिखने Lang तरह से मैं मूल रूप से करना शामिल करने के लिए।

जब से मैं ट्रांसफार्मर और REPLs डिजाइनिंग इकाई के लिए नए हूँ, मैं अध्ययन कर रहा है/कार्गो-culting Glambda के Repl.hs और Monad.hs से। मैंने मूल रूप से इसे चुना क्योंकि मैं भी अपनी अभिव्यक्तियों के लिए जीएडीटी का उपयोग करने की कोशिश करूंगा। यह एक जोड़े अपरिचित प्रथाओं, जो मैं बदल रहा है करने के लिए अपनाया है, लेकिन हूँ पूरी तरह से खुला शामिल हैं:

  • newtype + GeneralizedNewtypeDeriving (यह खतरनाक है?)
  • MaybeTmzero
साथ आरईपीएल छोड़ने की अनुमति के लिए

यहाँ मेरे कार्य कोड अब तक बताया गया है:

{- LANGUAGE GeneralizedNewtypeDeriving #-} 

module Main where 

import Control.Monad.RWS.Lazy 
import Control.Monad.Trans.Maybe 
import System.Console.Haskeline 

-- Lang monad for parsing language line by line 

type LangLog = [String] 
type LangState = [(String, String)] 
type LangConfig = [(String, String)] 

newtype Lang a = Lang { unLang :: RWS LangConfig LangLog LangState a } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    , MonadReader LangConfig 
    , MonadWriter LangLog 
    , MonadState LangState 
    ) 

-- Repl monad for responding to user input 

newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) (Lang a) } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    , MonadIO 
    ) 

और एक जोड़े को यह विस्तार करने के लिए प्रयास करता है। सबसे पहले, जैसा कि ऊपर उल्लेख Repl में Lang सहित:

newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) (Lang a) } 
deriving 
    (Functor 
    , Applicative 
    ) 

--  Can't make a derived instance of ‘Functor Repl’ 
--  (even with cunning newtype deriving): 
--  You need DeriveFunctor to derive an instance for this class 
--  In the newtype declaration for ‘Repl’ 
-- 
-- After :set -XDeriveFunctor, it still complains: 
-- 
--  Can't make a derived instance of ‘Applicative Repl’ 
--  (even with cunning newtype deriving): 
--  cannot eta-reduce the representation type enough 
--  In the newtype declaration for ‘Repl’ 

इसके बाद, बस एक ही बार में उन दोनों का उपयोग करने की कोशिश कर रहा:

-- Repl around Lang: 
-- can't access Lang operations (get, put, ask, tell) 
type ReplLang a = Repl (Lang a) 

test1 :: ReplLang() 
test1 = do 
    liftIO $ putStrLn "can do liftIO here" 
    -- but not ask 
    return $ return() 

-- Lang around Repl: 
-- can't access Repl operations (liftIO, getInputLine) 
type LangRepl a = Lang (Repl a) 

test2 :: LangRepl() 
test2 = do 
    _ <- ask -- can do ask 
    -- but not liftIO 
    return $ return() 

नहीं दिखाया गया: मैं भी ask पर lift के विभिन्न क्रमपरिवर्तन की कोशिश की और putStrLn कॉल। अंत में, यकीन है कि यह एक RWS विशेष मुद्दा मैं Lang लेखन की कोशिश की यह बिना नहीं है होना करने के लिए:

newtype Lang2 a = Lang2 
    { unLang2 :: ReaderT LangConfig (WriterT LangLog (State LangState)) a 
    } 
    deriving 
    (Functor 
    , Applicative 
    ) 

ही ईटा-को कम त्रुटि देता है।

तो संक्षेप में, मुख्य बात यह है कि मैं जानना चाहता हूं कि मैं इन दो monads को कैसे जोड़ूं? क्या मुझे lift एस का स्पष्ट संयोजन याद आ रहा है, या ट्रांसफार्मर स्टैक को गलत तरीके से व्यवस्थित करना, या कुछ गहरे मुद्दे में चल रहा है?

अपडेट::

यहां कुछ संभवतः-संबंधी प्रश्नों मैं को देखा हैं मेरे हाथ-लहरदार इकाई ट्रांसफार्मर की समझ मुख्य था मुसीबत। RWSTRWS तो LangTRepl और IO के बीच डाला जा सकता है के बजाय का उपयोग करते हुए ज्यादातर इसे हल करती है:

newtype LangT m a = LangT { unLangT :: RWST LangConfig LangLog LangState m a } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    , MonadReader LangConfig 
    , MonadWriter LangLog 
    , MonadState LangState 
    ) 

type Lang2 a = LangT Identity a 

newtype Repl2 a = Repl2 { unRepl2 :: MaybeT (LangT (InputT IO)) a } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    -- , MonadIO -- ghc: No instance for (MonadIO (LangT (InputT IO))) 
    , MonadReader LangConfig 
    , MonadWriter LangLog 
    , MonadState LangState 
    ) 

केवल शेष मुद्दा मैं यह पता लगाने Repl2 एक उदाहरण बनाने के लिए कैसे MonadIO कब की जरूरत है।

अपडेट 2: अब सब अच्छा है! LangT के लिए व्युत्पन्न उदाहरणों की सूची में MonadTrans जोड़ने की आवश्यकता है।

+0

'आईओ' आपके मोनैड ट्रांसफार्मर स्टैक के नीचे होना चाहिए क्योंकि वहां [कोई' आईओटी 'मोनैड ट्रांसफार्मर नहीं है] (http://stackoverflow.com/questions/13056663/why-is-there-no-io- ट्रांसफार्मर में Haskell)। कुछ 'न्यूटाइप लैंगटी एम ए = लैंगटी (आरडब्लूएसटी .. .. .. एम ए) की तरह कुछ; newtype Repl a = Repl (हो सकता है (इनपुट टी (लैंगट आईओ)) ए) 'आपके लिए काम कर सकता है। – user2407038

+0

आप सही हैं, धन्यवाद! मुझे पता था कि 'आईओ' को नीचे होना है, लेकिन किसी कारण से यह मेरे लिए नहीं हुआ था कि पूरा ढेर रैखिक है। मैंने सोचा कि आप एक और प्रकार का "पक्ष से दूर" डाल सकते हैं। प्रश्न अपडेट करेंगे। – Jeff

+0

'लैंगटी' को 'मोनाडियो एम => मोनाडियो (लैंगटी एम)' आवृत्ति (जिसे शायद प्राप्त किया जा सकता है) की आवश्यकता है क्योंकि 'मोनाडियो एम => मोनाडियो (हो सकता है एम)' उदाहरण की आवश्यकता है। – user2407038

उत्तर

4

आप दो मोनैड लिखने की कोशिश कर रहे हैं, एक दूसरे के शीर्ष पर। लेकिन सामान्य रूप से monads don't compose this way। आइए आपके मामले के सरलीकृत संस्करण को देखें। मान लें कि के बजाय MaybeT ... और Reader के बजाय हमारे पास Maybe है। तो अपने इकाई के प्रकार होगा

Maybe (LangConfig -> a) 

अब अगर यह एक इकाई थे, हम एक कुल join समारोह, किस प्रकार होता

join :: Maybe (LangConfig -> Maybe (LangConfig -> a)) -> Maybe (LangConfig -> a) 

और यहाँ एक समस्या आता होगा: क्या होगा अगर तर्क एक मूल्य Just f जहां

f :: LangConfig -> Maybe (LangConfig -> a) 

और कुछ इनपुट f रिटर्न Nothing के लिए है? Just f से Maybe (LangConfig -> a) का सार्थक मूल्य बनाने का कोई उचित तरीका नहीं है। हमें LangConfig पढ़ने की आवश्यकता है ताकि f यह निर्धारित कर सके कि उसका आउटपुट Nothing या Just something होगा, लेकिन Maybe (LangConfig -> a) के भीतर हम या तो Nothing वापस कर सकते हैं या LangConfig पढ़ सकते हैं, दोनों नहीं! तो हमारे पास ऐसा join फ़ंक्शन नहीं हो सकता है।

यदि आप ध्यान से मोनड ट्रांसफार्मर देखते हैं, तो आप देखते हैं कि कभी-कभी दो मोनैड को गठबंधन करने का एक ही तरीका होता है, और यह उनकी मूर्ख रचना नहीं है। विशेष रूप से, ReaderT r Maybe a और दोनों r -> Maybe a पर isomorphic हैं। जैसा कि हमने पहले देखा था, रिवर्स एक मोनड नहीं है।

तो आपकी समस्या का समाधान मोनैड के बजाय मोनैड ट्रांसफार्मर बनाना है। आप दोनों इकाई ट्रांसफार्मर के रूप में हो सकता है या तो:

newtype LangT m a = Lang { unLang :: RWST LangConfig LangLog LangState m a } 
newtype ReplT m a = Repl { unRepl :: MaybeT (InputT m) a } 

और उन्हें LangT (ReplT IO) a या ReplT (LangT IO) a (टिप्पणियों में से एक में वर्णित है, IO हमेशा ढेर के नीचे हो गया है) के रूप में इस्तेमाल करते हैं। या आप उनमें से केवल एक (बाहरी एक) ट्रांसफॉर्मर के रूप में और दूसरे को एक मोनड के रूप में प्राप्त कर सकते हैं। लेकिन जैसा कि आप IO का उपयोग कर रहे हैं, आंतरिक मोनड को आंतरिक रूप से IO शामिल करना होगा।

ध्यान दें कि LangT (ReplT IO) a और ReplT (LangT IO) a के बीच कोई अंतर है। यह StateT s Maybe a और MaybeT (State s) a के बीच के अंतर के समान है: यदि पूर्व mzero के साथ विफल रहता है, न तो परिणाम और न ही आउटपुट स्थिति उत्पन्न होती है। लेकिन बाद में mzero के साथ विफल रहता है, इसका कोई परिणाम नहीं है, लेकिन राज्य उपलब्ध रहेगा।

+1

धन्यवाद! मुझे लगता है कि मैं (धीरे-धीरे, आखिरकार) इस सामान के लिए अंतर्ज्ञान प्राप्त करना शुरू कर रहा हूं। – Jeff

संबंधित मुद्दे