Проблема с вашим типом GL в том, что «результат вычисления» a
зависит от действий ввода-вывода, и поэтому вы не можете реализовать экземпляр монады, где вы могли бы вычислить окончательный HashSet текстуры без запуска ввода-вывода.-actions.
Правильное решение зависит от деталей того, как вы хотите использовать монаду GL, но при условии, что вы можете решить, какие текстуры использовать, не выполняя IO-действия, тогда вы можете подобного типа
type GL a = WriterT (Set String) (Writer (IO ())) a
Т.е. вы используете две вложенные монады писателя, одну для текстур и одну для накопления действий ввода-вывода.Результирующий стек монад запускается в два этапа, и вы можете получить окончательный набор текстур без выполнения действий ввода-вывода.
К сожалению, Writer
работает только для моноидов, поэтому нам нужно определить экземпляр Monoid
дляIO ()
first.
{-# LANGUAGE FlexibleInstances #-}
import Data.Monoid
instance Monoid (IO ()) where
mempty = return ()
mappend = (>>)
Теперь вы можете написать функцию, которая регистрирует новую текстуру, например:
addTexture :: String -> GL ()
addTexture = tell . S.singleton
И еще одну функцию, которая кэширует действие IO, которое будет выполнено позже.
addIO :: IO () -> GL ()
addIO = lift . tell
Вот вспомогательная функция для запуска монады GL
runGL :: GL a -> (a, Set String, IO ())
runGL gl = let iow = runWriterT gl
((a, textures), io) = runWriter iow
in (a, textures, io)
Повторяет кортеж из трех элементов: значение результата вычисления, набор накопленных текстур и накопленный текст.действия.Обратите внимание, что на данный момент значение IO ()
в кортеже просто описывает действие, и ничего (например, операции рисования) еще не были выполнены.
Я не уверен, охватывает ли это ваш сценарий использования, но, надеюсь, это даст вам некоторые идеи о том, как построить подходящий стек монад.Если вам нужна дополнительная помощь, приведите несколько примеров того, как вы на самом деле хотите использовать монаду GL
.
Вот полный код, который я тестировал.Обратите внимание, что я использовал тип 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)
РЕДАКТИРОВАТЬ: Вы также можете избежать расширения языка, если вы оберните эффекты ввода-вывода в новый тип, как предложено 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)