使用带外数据编写monad(也就是说,并行编写monad)

Cla*_*bel 3 monads haskell

我正在编写一个包含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,然后独立运行它们?

sha*_*ang 7

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)