无法匹配 monad 堆栈深处的类型
Couldn't match types deep in a monad stack
我正在为学习目的编写 OpenGL 程序,使用 GPipe
库。该库执行了一些黑魔法,并且(我的 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
s return 一个 Transition
被主循环使用到 select 一个适当的执行路径。编译错误如下:
/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 和 monad 的理解不允许我解决这个问题,我可以看出 os1
和 os
是由不同的方程产生的,因此 GHC 可以只是将它们标记为相同,但我不知道如何修复它。如果我从 Transition
枚举中删除 os
参数,错误就会消失,但我 需要 它来传递状态而不是在每个处理器中重新初始化它。
有人能解释一下出了什么问题以及如何解决吗?
PS。哦,当我将所有代码集中在一个文件中时,出现了一个新错误,该错误之前被编译顺序掩盖了。
returns 一个 ContextT
值(这里包含在 Processor
中)的函数,如 start
,不应调用 GP.runContextT
.
GP.runContextT
用于初始化并提供执行处理器的上下文,您只想在整个程序开始时执行一次。因此,它可能应该在 main
中,连同 newWindow
、defaultWindowConfig
和 mkFullState
.
像start
这样的Processor
可以使用StateT
转换器获取当前状态。但首先,我们必须修复 Processor
类型。注意 runContextT
的类型,特别是 forall
:
runContextT
:: (MonadIO m, MonadAsyncException m, ContextHandler ctx)
=> ContextHandlerParameters ctx -> (forall os. ContextT ctx os m a) -> m a
这个forall
强制类型变量os
不能出现在m
或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 ()
我正在为学习目的编写 OpenGL 程序,使用 GPipe
库。该库执行了一些黑魔法,并且(我的 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
s return 一个 Transition
被主循环使用到 select 一个适当的执行路径。编译错误如下:
/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 和 monad 的理解不允许我解决这个问题,我可以看出 os1
和 os
是由不同的方程产生的,因此 GHC 可以只是将它们标记为相同,但我不知道如何修复它。如果我从 Transition
枚举中删除 os
参数,错误就会消失,但我 需要 它来传递状态而不是在每个处理器中重新初始化它。
有人能解释一下出了什么问题以及如何解决吗?
PS。哦,当我将所有代码集中在一个文件中时,出现了一个新错误,该错误之前被编译顺序掩盖了。
returns 一个 ContextT
值(这里包含在 Processor
中)的函数,如 start
,不应调用 GP.runContextT
.
GP.runContextT
用于初始化并提供执行处理器的上下文,您只想在整个程序开始时执行一次。因此,它可能应该在 main
中,连同 newWindow
、defaultWindowConfig
和 mkFullState
.
像start
这样的Processor
可以使用StateT
转换器获取当前状态。但首先,我们必须修复 Processor
类型。注意 runContextT
的类型,特别是 forall
:
runContextT
:: (MonadIO m, MonadAsyncException m, ContextHandler ctx)
=> ContextHandlerParameters ctx -> (forall os. ContextT ctx os m a) -> m a
这个forall
强制类型变量os
不能出现在m
或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 ()