Невозможно получить Applicative при объединении двух стеков монадных трансформаторов.

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

Это в основном проверки типов, но я не могу получить Applicative (требуется для Monad и все остальное).

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

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

Обновление: моё волнообразное понимание монадных трансформаторов было главной проблемой. С помощью 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 экземпляр IO MonadIO,

Обновление 2: все хорошо сейчас! Просто нужно добавить MonadTrans в список экземпляров, полученных для LangT,

1 ответ

Решение

Вы пытаетесь соединить две монады, одна поверх другой. Но в целом монады так не сочиняются. Давайте посмотрим на упрощенную версию вашего случая. Давайте предположим, что у нас есть только 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 а также MaybeT (Reader r) 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, нет результата, но состояние останется доступным.

Другие вопросы по тегам