That assumes there is only one program running in the world. Its simply not clear what the "world" type means. Its a bad name. I'd rather go ahead and explain it as a token.
I don't see why it assumes there is only one program. If the world value is impossible to inspect (which it is), you can assign whatever semantics you like to it.
If you look at the generated core code, it's important to notice that it's not the same "token" being passed around everywhere. Each IO action returns a new token, which could be a different value from the old token. (Again, this is entirely theoretical, as the token/world state doesn't exist in the binary.)
> when you call "print", it returns a "new world" that is the state of the world after printing
makes no sense, since the word "state of the world" will be interpreted completely differently by the receiver of the explanation. State of the world = a large state object with lots of fields describing every single thing in some "World" (what world: program world? computer world? network world? all the world?). Thats what people hear.
The name of the type is horrible, and the world-passing intuition is also horrible. "Token" would've been much better, but its still not clear how to implement or why its even necessary (in a non-lazy language)
On the other hand, a tuple representing the current action and the function that will return the next action (or nil) is fairly clear, easy to implement an interpreter for, the pure/nonpure distinction becomes obvious (the runtime interpreter is the impure part), and laziness doesn't even need to get into the mix.
oh, and also regarding FFI: {type: 'FFICall', fn: string, arguments: [...]}
That's a fair argument. People might get caught up on thinking that the world object actually has to contain a description of the world.
> {type: 'FFICall', fn: string, arguments: [...]}
I agree that this is simpler to understand, but it's no good in practice. It's obviously not type safe, and you still have to bake support into the interpreter/runtime for whatever your string value is. It's also effectively impossible to inline/optimize away. Which, again, is probably why it's done using "State RealWorld#" in GHC.
I would like to reiterate that however RealWorld# happens to be defined, the way its use is enforced in GHC is the exact same as if it did actually contain a description of the entire universe. You just aren't allowed to look at it. But, you are correct, this is more confusing than a request/response free monad.