It's nice to know (in Safe Haskell, at least) from the signature whether or not something performs IO actions, but IO encompasses a lot of different things - putStr
, database access, removing and writing to files, IORefs, etc.
If I'm using the type signatures as a security measure when running arbitrary code, it might be the case that I'm willing to accept some IO actions - putStr
and the ilk, for instance - but not others.
Is there a way to define a restricted version of the IO monad, with only a subset of the normal IO actions? If so, an example (with putStr
, for instance) would be very welcome!
As a follow up to my comment, you can implement it yourself with something like
class Monad io => Stdout io where
putStr_ :: String -> io ()
putStrLn_ :: String -> io ()
print_ :: Show a => a -> io ()
-- etc
instance Stdout IO where
putStr_ = putStr
putStrLn_ putStrLn
print_ = print
myFunc :: Stdout io => io ()
myFunc = do
val <- someAction
print_ val
let newVal = doSomething val
print_ newVal
main :: IO ()
main = myFunc
This will have absolutely no runtime overhead, since GHC will optimize away those typeclasses to use only the IO
monad, it's extensible, easy to write, and can be combined with monad transformes and the MonadIO
class quite easily. If you have multiple class, such as a Stdin
class with getLine_
, getChar_
, etc defined, you can even combine these typeclasses with
class (Stdout io, Stdin io) => StdOutIn io where
myFunc :: StdOutIn io => io ()
myFunc = do
val <- getLine_
putStrLn_ $ "Echo: " ++ val
main :: IO ()
main = myFunc
Just define a newtype
around IO a
with a Monad instance, define wrapped versions of your pre-approved functions, and don't export the constructor, so that only the functions you wrapped can be used in the monad.