我正在编写一个包含OpenGL的monad GL,我希望能够查询计算以获得它可能需要的每个纹理的列表.
这是一个解决的问题吗?我在为GL编写Monad实例时遇到了很多麻烦.
这是我到目前为止所尝试的:
-- GL should be able to be inspected for its HashSet without running the computation.
newtype GL a = GL (S.HashSet String) (IO a)
instance Monad (GL a) where
return = GL S.empty . return -- Calls IO.return
(>>=) (GL textures action) f = -- What goes here?
Run Code Online (Sandbox Code Playgroud)
但我在吠叫错了树吗?它并不真正作为monad工作,因为我必须在运行它之前查询它.我应该用什么呢?我真的喜欢使用do-notation.
我认为这可以分解为:我如何并行组合两个monad,然后独立运行它们?
GL类型的问题在于"计算结果" a依赖于IO操作,因此您无法实现monad实例,您可以在不运行IO操作的情况下计算最终纹理HashSet.
正确的解决方案取决于您如何使用GL monad的详细信息,但假设您可以决定使用哪些纹理而不运行IO-actions,那么您可以使用这样的类型
type GL a = WriterT (Set String) (Writer (IO ())) a
Run Code Online (Sandbox Code Playgroud)
即,您使用两个嵌套的编写器monad,一个用于纹理,一个用于累积IO操作.生成的monad堆栈分两个阶段运行,您可以在不执行IO操作的情况下获取最终纹理集.
不幸的是,Writer只适用于幺半群,所以我们需要先定义一个Monoid实例IO ().
{-# LANGUAGE FlexibleInstances #-}
import Data.Monoid
instance Monoid (IO ()) where
mempty = return ()
mappend = (>>)
Run Code Online (Sandbox Code Playgroud)
现在,您可以编写一个注册新纹理的函数,如下所示:
addTexture :: String -> GL ()
addTexture = tell . S.singleton
Run Code Online (Sandbox Code Playgroud)
另一个缓存IO动作的函数稍后执行
addIO :: IO () -> GL ()
addIO = lift . tell
Run Code Online (Sandbox Code Playgroud)
这是一个用于运行GL monad的实用程序函数
runGL :: GL a -> (a, Set String, IO ())
runGL gl = let iow = runWriterT gl
((a, textures), io) = runWriter iow
in (a, textures, io)
Run Code Online (Sandbox Code Playgroud)
这会使元组返回三个元素:计算的结果值,累积纹理集和累积的io动作.请注意,此时,IO ()元组中的值仅描述了操作,并且尚未执行任何操作(例如绘图操作).
我不确定这是否涵盖了您的用例,但希望它能为您提供有关如何构建合适的monad堆栈的一些想法.如果您需要更多帮助,请提供一些有关如何实际使用GLmonad的示例.
这是我测试的完整代码.请注意,我使用的是类型Set而不是HashSet,因为根据hashmap库的文档,HashSet不推荐使用该名称.
{-# LANGUAGE FlexibleInstances #-}
import Control.Monad.Writer
import Data.Monoid
import Data.HashSet (Set)
import qualified Data.HashSet as S
instance Monoid (IO ()) where
mempty = return ()
mappend = (>>)
type GL a = WriterT (Set String) (Writer (IO ())) a
addTexture :: String -> GL ()
addTexture = tell . S.singleton
addIO :: IO () -> GL ()
addIO = lift . tell
runGL :: GL a -> (a, Set String, IO ())
runGL gl = let iow = runWriterT gl
((a, textures), io) = runWriter iow
in (a, textures, io)
Run Code Online (Sandbox Code Playgroud)
编辑:如果你将IO效果包装成newtype,你也可以避免使用语言扩展,如dave4420所示.
import Control.Monad.Writer
import Data.Monoid
import Data.HashSet (Set)
import qualified Data.HashSet as S
newtype WrapIO = WrapIO { unwrapIO :: IO () }
instance Monoid WrapIO where
mempty = WrapIO $ return ()
WrapIO a `mappend` WrapIO b = WrapIO $ a >> b
type GL a = WriterT (Set String) (Writer WrapIO) a
addTexture :: String -> GL ()
addTexture = tell . S.singleton
addIO :: IO () -> GL ()
addIO = lift . tell . WrapIO
runGL :: GL a -> (a, Set String, IO ())
runGL gl = let iow = runWriterT gl
((a, textures), WrapIO io) = runWriter iow
in (a, textures, io)
Run Code Online (Sandbox Code Playgroud)