
On 4/10/08, Adam Smyczek
If yes, is this a general concept/pattern how to hide functionality of a underlying monad, in this case hide IO entirely?
Yes, that's correct, although with IO you can't hide it entirely; eventually you need a way to actually run the computation, and if that's built on IO there's no way to do that without at least a way to get -back- to the IO Monad. On the other hand, you can use this to encapsulate "sandboxed" computations:
module Console (Console, execConsole, consoleGetLine, consolePutLine) where
newtype Console a = MkConsole { execConsole :: IO a } deriving (Monad, Functor)
consoleGetLine :: Console String consoleGetLine = MkConsole getLine
consolePutLine :: String -> Console () consolePutLine = MkConsole . putStrLn
MkConsole is a private constructor not exported from this module, so the only way to construct one is via the operations we provide and the monad/functor operations. So we can prove that these operations never do any network access, or file I/O, or weird pointer access. Of course, with unsafeCoerce# and/or unsafePerformIO, client code can break either/both of these claims:
runConsole :: Console a -> a runConsole = unsafePerformIO . execConsole
instance MonadIO Console where liftIO = unsafeCoerce# -- works because newtype is guaranteed not to change -- the runtime representation
-- ryan