无法匹配monad堆栈中的类型

时间:2018-06-10 12:45:24

标签: haskell monad-transformers

我正在使用GPipe库编写用于学习目的的OpenGL程序。该库做了一些黑色魔法,并且(我用newtype引入了一个很好的措施)使我无法正确解析错误消息。以下代码无法编译:

{-# LANGUAGE PackageImports #-}
module Main where

import Control.Monad.State
import Control.Monad.Except

import qualified "GPipe" Graphics.GPipe as GP
import qualified "GPipe-GLFW" Graphics.GPipe.Context.GLFW as GLFW

---- types ----

newtype Processor ctx os a = Processor {
    runProcessor :: GP.ContextT ctx os (StateT (FullState os) IO) a
}

data Transition os = ToMainMenu (FullState os)
                   | Quit

type CType = GP.RGBFloat
type UnitWindow os = GP.Window os CType ()

data ArtState os = ArtState {
    _asWindow :: UnitWindow os
}

data ProgState = ProgState

data FullState os = FullState {
    _fsArtState :: ArtState os
  , _fsProgState :: ProgState
}

---- constructors ----

mkFullState :: UnitWindow os -> FilePath -> ExceptT String IO (FullState os)
mkFullState window directory = do
    art <- mkArtState window directory
    prog <- mkProgState directory
    return FullState {
        _fsArtState = art
      , _fsProgState = prog
    }

mkArtState :: UnitWindow os -> FilePath -> ExceptT String IO (ArtState os)
mkArtState window _ = return ArtState {
    _asWindow = window
}

mkProgState :: FilePath -> ExceptT String IO ProgState
mkProgState _ = return ProgState

---- processors ----

start :: Processor ctx os (Transition os)
start = Processor $ GP.runContextT GLFW.defaultHandleConfig $ do
    win <- GP.newWindow (GP.WindowFormatColor GP.RGB8) (GLFW.defaultWindowConfig "Foobar")
    possiblyState <- liftIO $ runExceptT $ mkFullState win "./"
    case possiblyState of
         Left err -> liftIO $ putStrLn err >> return Quit
         Right state -> return $ ToMainMenu state

---- Main ----

main :: IO ()
main = do
    transition <- runProcessor start
    case transition of 
         Quit -> return ()
         ToMainMenu _ -> return ()

我们的想法是让Processor返回Transition以供主循环使用以选择适当的执行路径。编译错误如下:

/tmp/testing/app/Main.hs:60:25: error:
    • Couldn't match type ‘os1’ with ‘os’
      ‘os1’ is a rigid type variable bound by
        a type expected by the context:
          forall os1.
          GP.ContextT
            GLFW.Handle
            os1
            (GP.ContextT ctx os (StateT (FullState os) IO))
            (Transition os)
        at app/Main.hs:(55,21)-(60,49)
      ‘os’ is a rigid type variable bound by
        the type signature for:
          start :: forall ctx os. Processor ctx os (Transition os)
        at app/Main.hs:54:1-41
      Expected type: GP.ContextT
                       GLFW.Handle
                       os1
                       (GP.ContextT ctx os (StateT (FullState os) IO))
                       (Transition os)
        Actual type: GP.ContextT
                       GLFW.Handle
                       os1
                       (GP.ContextT ctx os (StateT (FullState os) IO))
                       (Transition os1)
    • In the expression: return $ ToMainMenu state
      In a case alternative: Right state -> return $ ToMainMenu state
      In a stmt of a 'do' block:
        case possiblyState of
          Left err -> liftIO $ putStrLn err >> return Quit
          Right state -> return $ ToMainMenu state
    • Relevant bindings include
        state :: FullState os1 (bound at app/Main.hs:60:16)
        possiblyState :: Either String (FullState os1)
          (bound at app/Main.hs:57:5)
        win :: GP.Window os1 GP.RGBFloat () (bound at app/Main.hs:56:5)
        start :: Processor ctx os (Transition os)
          (bound at app/Main.hs:55:1)
   |
60 |          Right state -> return $ ToMainMenu state
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^

我对Haskell和monads的理解并不能解决这个问题,我可以看到os1os是由不同的方程产生的,因此GHC不能只是将它们标记为相同,但我对如何修复它感到茫然。如果我从os枚举中删除Transition参数,则错误消失,但我需要它来传递状态而不是在每个处理器中重新初始化它。

有人可以解释出现了什么问题以及如何解决问题吗?

PS。哦,当我在单个文件中聚集所有代码时,出现了一个先前被编译顺序屏蔽的新错误。

1 个答案:

答案 0 :(得分:2)

返回ContextT值的函数(此处包含在Processor中),如start,不应调用GP.runContextT

GP.runContextT用于初始化并提供执行处理器的上下文,您只需要在整个程序开始时执行一次。因此,它可能应该在main,以及newWindowdefaultWindowConfigmkFullState

Processor start可以使用StateT转换器获取当前状态。但首先,我们必须修复Processor类型。请注意runContextT的类型,尤其是forall

runContextT
    :: (MonadIO m, MonadAsyncException m, ContextHandler ctx)
    => ContextHandlerParameters ctx -> (forall os. ContextT ctx os m a) -> m a

forall强制要求osm中不能出现类型变量a,从而阻止某些资源泄露。这与Processor的当前定义不兼容,因为StateT (FullState os) IO包含os。你可以交换变压器。

newtype Processor ctx os a = Processor {
    runProcessor :: StateT (FullState os) (GP.ContextT ctx os IO) a
}

现在start可以使用get来访问当前状态,并且因为它不应该处理初始化,所以它不再具有Quit分支(您可能不再我希望此时start成为Processor,但希望这与你真正喜欢用其他处理器做的事情相近:)

start :: Processor ctx os (Transition os)
start = Processor $ do
  s <- get
  return $ ToMainMenu s

main可能如下所示:

main :: IO ()
main =
    -- Initialize and provide context, i.e, convert the wrapped
    -- do-block of type `ContextT _ _ IO` to `IO`
    GP.runContextT GLFW.defaultHandleConfig $ do

        -- Create a GLFW window
        -- You can probably create more than one
        win <- GP.newWindow (GP.WindowFormatColor GP.RGB8) (GLFW.defaultWindowConfig "Foobar")

        -- Create the initial processor state, handling initialization failures
        s_ <- liftIO $ runExceptT $ mkFullState win "./"
        s0 <- case s_ of
            Left e -> fail e
            Right s0 -> return s0

        -- Run a processor
        (transition, s1) <- (`runStateT` s0) $ runProcessor start

        case transition of
            Quit -> return ()
            ToMainMenu _ -> return ()