2016-04-26 12 views
5

Я написал две монады для языка, специфичного для домена, который я разрабатываю. Первый - 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 или неправильно поставил блок трансформатора или столкнулся с какой-то более глубокой проблемой?

Вот несколько, возможно, связанные с вопросами, которые я посмотрел на:

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.

+0

'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

+0

Вы правы, спасибо! Я знал, что «IO» должен быть внизу, но по какой-то причине мне не приходило в голову, что весь стек линейный. Я думал, что ты можешь поместить другой тип «в сторону». Обновит вопрос. – Jeff

+0

'LangT' нуждается в экземпляре' MonadIO m => MonadIO (LangT m) ', который, вероятно, может быть получен, потому что это требует экземпляр' MonadIO m => MonadIO (MaybeT m) '. – user2407038

ответ

4

Вы пытаетесь составить две монады, одну поверх другой. Но вообще monads don't compose this way. Давайте посмотрим на упрощенную версию вашего дела. Предположим, у нас есть только Maybe вместо MaybeT ... и Reader вместо Lang. Таким образом, тип вашей монады будет

Maybe (LangConfig -> a) 

Теперь, если бы это была монада, мы имели бы общую join функцию, которая будет иметь тип

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

И здесь проблема приходит: Что делать, если аргумент значение Just f где

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

и для некоторых входных f возвращается Nothing? Нет разумного способа, как мы могли бы построить значимое значение Maybe (LangConfig -> a) от Just f. Нам нужно прочитать LangConfig, так что f может решить, будет ли его выход Nothing или Just something, но в пределах Maybe (LangConfig -> a) мы можем либо вернуть Nothing, либо читать LangConfig, не оба! Таким образом, мы не можем иметь такую ​​функцию join.

Если вы внимательно посмотрите на трансформаторы монады, вы увидите, что иногда есть только один способ, как объединить две монады, и это не их наивная композиция. В частности, как ReaderT r Maybe a, так и изоморфны r -> Maybe a. Как мы видели ранее, обратное - это не монада.

Итак, решение вашей проблемы состоит в том, чтобы строить монадные трансформаторы вместо монадов. Вы можете иметь и как монада трансформаторов:

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

 Смежные вопросы

  • Нет связанных вопросов^_^