8

Obviously, the following function is impossible, because it is impossible to unwrap an IO value permanently (ignoring unsafePerformIO or similar):

unwrapIO :: IO String -> String unwrapIO (IO str) = str 

However, similar functions such as the following are possible:

unwrapJust :: Maybe String -> String unwrapJust (Just str) = str unwrapJust Nothing = "ignore this plz" 

I fully understand the reasoning behind why #2 is possible but #1 is not, but I do not understand how. Can I also make my own types that are not unwrappable?

1
  • 2
    IO is a primitive and has no constructors, so cannot be unwrapped. Commented Nov 27, 2018 at 0:31

3 Answers 3

11

Just and Nothing are data constructors for the type Maybe a. IO has no data constructors to speak of (in GHC it actually has constructors but they're really implementation details of GHC, and other implementations might define IO differently).

unwrapIO (IO str) = str doesn't make sense in the same way unwrapMaybe (Maybe str) = str doesn't make sense. IO and Maybe are not data constructors, so you cannot pattern-match on them.

Sign up to request clarification or add additional context in comments.

Comments

9

It's because the data constructor of IO is not exported. I mean, you can think it's not exported.

You can prevent your own type from being unwrapped by using the same strategy.

module Test (Test, test) where data Test a = MkTest a test :: a -> Test a test = MkTest 

You can create a value of Test using test, but you cannot unwrap it using pattern-match because MkTest is not exported.

Comments

1

I believe that while the existing answers are mostly true, there is a deeper reason why IO can not be unwrapped. Conceptually, type IO a = RealWorld -> (a, RealWorld). IO is a function type (in real implementations hidden behind a newtype wrapper or equivalent machinery).

So, how would you go about unwrapping a function? Easy, you just call it!. But how are you going to get an instance of RealWorld? That is the truer primitive: you can not construct a RealWorld, there is only ever one.

The monadic instance of course can just pass RealWorld as a state, kind of like StateT and in the end, the only instance ever constructed is at startup of the program, then it is passed down between the IOs.

Coming back to reality again, this is (again) a lie. You actually can get hold of an instance of RealWorld and you can call an IO a action "ahead of time". There is a function that does exactly what you asked for. It is called System.IO.Unsafe.unsafePerformIO :: IO a -> a though it is in an unsafe package for a reason.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.