The only thing I'm aware of offhand is that an existential is used to hide the internals, not because this is in some sense necessary for IO to work, but to allow the compiler to ensure that nothing is violating IO's "contract": you can't do anything with something you don't know the type of, so it's impossible for normal code to do anything out of bounds with an IO type. You could just as well write an implementation of IO without any existentials or magic internals, but it'd be trivial to "cheat". (GHC's, with the magic stripped away, is just state: sequential execution is guaranteed by passing a state "baton" between IO actions, and the magic just makes sure you can't stash a copy of the baton or manufacture one yourself. There's some slight additional magic that functions as an optimization.)