A function from a fixed input is a monad (called the “Reader” monad). If you constrain all IO to happen underneath that function then it will be deferred until you evaluate the result.
In those senses, these are the same. You can emulate the IO monad in this way.
Haskell provides guarantees that make this stronger. There is no way to perform IO-like side effects except via the IO monad. And no way to “evaluate” the IO monad except to declare it as your main entry point, compile, and hit play.
(Except unsafePerformIO, of course, the highly discouraged escape hatch).
These restrictions are actually what makes the use of the IO monad powerful. As you show, it’s easy to “add” it to a language, and also almost valueless. The value is in removing everything that’s not in the IO monad.
In those senses, these are the same. You can emulate the IO monad in this way.
Haskell provides guarantees that make this stronger. There is no way to perform IO-like side effects except via the IO monad. And no way to “evaluate” the IO monad except to declare it as your main entry point, compile, and hit play.
(Except unsafePerformIO, of course, the highly discouraged escape hatch).
These restrictions are actually what makes the use of the IO monad powerful. As you show, it’s easy to “add” it to a language, and also almost valueless. The value is in removing everything that’s not in the IO monad.