Я написал две монады для языка, специфичного для домена, который я разрабатываю. Первый - Lang
, который должен включать все необходимое для синтаксического разбора языка. Я знал, что хочу, читатель, писатель, и состояние, поэтому я использовал RWS
монады:Невозможно получить Прикладной при объединении двух блоков трансформатора монады
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 для взаимодействия с пользователем:
newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) a }
deriving
(Functor
, Applicative
, Monad
, MonadIO
)
И, кажется, работайте индивидуально (они компилируются, и я играл со своим поведением в GHCi), но мне не удалось вставить Lang
в Repl
для анализа строк от пользователя. Главный вопрос: как я могу это сделать?
Более конкретно, если я пишу Repl
включить Lang
, как я первоначально предназначенный:
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, я изучал/обрабатывал грузы с GlambdaRepl.hs
и Monad.hs
. Я изначально выбрал его, потому что я также попытаюсь использовать GADT для своих выражений. Она включает в себя пару незнакомых практик, которые я приняты, но я полностью открыт для изменения:
newtype
+GeneralizedNewtypeDeriving
(это опасно?)MaybeT
, чтобы выход из REPL сmzero
Вот мой рабочий код до сих пор:
{- 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
)
И пара пытается продлить его. Во-первых, в том числе Lang
в Repl
, как упоминалось выше:
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()
Не показано: Я также пробовал различные перестановки lift
на ask
и putStrLn
звонки. Наконец, чтобы быть уверенным, что это не является проблемой RWS конкретных Я пытался писать Lang
без него:
newtype Lang2 a = Lang2
{ unLang2 :: ReaderT LangConfig (WriterT LangLog (State LangState)) a
}
deriving
(Functor
, Applicative
)
Это дает ту же ошибку ETA-свертка.
Итак, главное, что я хочу знать, как объединить эти две монады? Я пропустил очевидную комбинацию lift
s или неправильно поставил блок трансформатора или столкнулся с какой-то более глубокой проблемой?
Вот несколько, возможно, связанные с вопросами, которые я посмотрел на:
- Tidying up Monads - turning application of a monad transformer into newtype monad
- Generalized Newtype DerivingGeneralized Newtype Deriving
- Issue deriving MonadTrans for chained custom monad transformers
Update: моя рука волнистого понимания монад трансформаторов было главным проблема. Использование RWST
вместо RWS
так LangT
может быть вставлен между Repl
и 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: Все хорошо сейчас! Просто нужно добавить MonadTrans
в список экземпляров, полученных для LangT
.
'IO' должен быть внизу вашего стека трансформатора монады, потому что [не является преобразователем монады IOT'] (http://stackoverflow.com/questions/13056663/why-is-there-no-io- трансформатор-в-Haskell). Что-то вроде 'newtype LangT m a = LangT (RWST .. .. .. m a); newtype Repl a = Repl (MaybeT (InputT (LangT IO)) a) 'может работать для вас. – user2407038
Вы правы, спасибо! Я знал, что «IO» должен быть внизу, но по какой-то причине мне не приходило в голову, что весь стек линейный. Я думал, что ты можешь поместить другой тип «в сторону». Обновит вопрос. – Jeff
'LangT' нуждается в экземпляре' MonadIO m => MonadIO (LangT m) ', который, вероятно, может быть получен, потому что это требует экземпляр' MonadIO m => MonadIO (MaybeT m) '. – user2407038