我正在使用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的理解并不能解决这个问题,我可以看到os1
和os
是由不同的方程产生的,因此GHC不能只是将它们标记为相同,但我对如何修复它感到茫然。如果我从os
枚举中删除Transition
参数,则错误消失,但我需要它来传递状态而不是在每个处理器中重新初始化它。
有人可以解释出现了什么问题以及如何解决问题吗?
PS。哦,当我在单个文件中聚集所有代码时,出现了一个先前被编译顺序屏蔽的新错误。
答案 0 :(得分:2)
返回ContextT
值的函数(此处包含在Processor
中),如start
,不应调用GP.runContextT
。
GP.runContextT
用于初始化并提供执行处理器的上下文,您只需要在整个程序开始时执行一次。因此,它可能应该在main
,以及newWindow
,defaultWindowConfig
和mkFullState
。
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
强制要求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 ()