0
votes

IO, just like Maybe, is just an instance of Monad. On the other hand we have all data constructors for Maybe (Just and Nothing), but no constructors for IO. Reader and Writer do not export constructors too, they have functions, which return instance of this type (reader and writer) and more importantly runReader and runWriter, which unwrap computation result from Monad.

Is there a way to unwrap IO Monad? I would like to have pure function which do some impure IO computations under the hood. Currently I am able to do this with most of the Monads

I know one example of such tricky function: Debug.Trace.trace

3
You can use ST for locally impure operations. - Lee
This is precisely what the IO monad tries to prevent. All the impure effects are relegated inside the IO. Why on earth do you want to break that? - chi
It definitely doesn't mean you can unwrap something because it's a Monad. Consider data Proxy a = Proxy (Note that there is no value "contained in" Proxy at all) which is a Monad. - David Young
You cannot unwrap IO by design, because its very purpose is to mark operations that are necessarily implemented by the compiler, not in Haskell code. - chepner
It is far too hasty to say ST is what he's looking for. While it is a good solution for many problems, he has yet to state a need and his one example was trace - certainly not an ST sort of task. @yanpas Can you please be more specific as to your actual goal? - Thomas M. DuBuisson

3 Answers

2
votes

unsafePerformIO :: IO a -> a in System.IO.Unsafe (base).

Use it with caution and read the description in the documentation carefully.

1
votes

The correct answer is

No, you can't!

Well, yes, GHC has a thing called unsafePerformIO, but this is not part of the Haskell standard, merely a hack to allow certain “morally pure” functions from other languages to be called using the foreign function interface, and reflect the type of those functions with the type they would have if you'd written them straight in pure Haskell.

Note that “unwrapping the IO monad” would not simply give you the result of that computation. If IO were a public-constructors type, it would actually look (conceptually) something like the following:

data IO' a =
    WriteToFile FilePath String
  | PutStr String
  | WithStdLine (String -> IO' a)
  | ...
  | SequenceIO (IO' ()) (IO' a)

Pattern matching on such an IO' a value would normally not give you access to anything of type a, it would merely give you some description of actions to be performed, and perhaps some function that could possibly yield an a value from intermediate results obtained from the environment.

The only way to actually get useful work done would then still be like it is now: by binding it to something like the main action, which then executed by some “real world entity” (the runtime).

If you want to implement an algorithm that describes a proper mathematical (i.e. pure) function but seems to lend itself to an imperative programming style with mutation etc., then you should not implement this in the IO monad at all. You might well be able to just implement it in ordinary pure Haskell98 by just choosing suitable data structures, or perhaps it makes sense to use the ST monad to achieve e.g. array updates with the same performance they'd have in imperative languages.

1
votes

Is there a way to place some impure code inside pure functions?

  • What if there was a way to do this?

  • What if everyone else could use it?

Would you be willing to sit down and scrutinise the sources of each and every library and program you use to check they are safe?

If you are, then Haskell probably isn't for you - I suggest you have a look at languages like Standard ML or OCaml...


You're still here?

Alright then, there's an alternative approach which you can use:

  • abstract out the regular Haskell code from the effect-centric code, using plain ol' ordinary functions.

It's possible to do this because functions are first-class values in Haskell - in particular, functions can be used as arguments e.g:

fmap  :: Functor f => (a -> b) -> f a -> f b
liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c

As your powers of abstraction improve, you'll delegate more and more work to regular Haskell code (in the form of functions), with just a small cohort of definitions being tainted by effects (including main :: IO ()). It requires some extra effort to start with, but the long-term rewards are substantial...