No, you can force operations to execute in linear order without the use of monads, as long as one operation depends on the output of the previous. But you have to be careful not to reuse values.
Imagine if the IO operations took an explicit world parameter instead of using a monad:
main :: World -> World
main world1 =
let world2 = putStrLn world1 "Hello, what's your name?"
let (world3, name) <- getLine world2
let world4 = putStrLn world3 ("Hey " ++ name ++ ", you rock!")
in world4
The IO operations would be executed in the expected order due to the dependency of the "world" output of the previous operation. Monads not necessary.
But if you accidentally used world2 twice then you would split the universe into two timelines. A monad can avoid this issue by hiding the world parameter and passing it on behind the scenes.
It would just mean that IO operations would happen in random order or not at all, due to the laziness of Haskell. E.g. if you call getLine twice with the same "world" argument, then it would presumably only be executed once. And if you don't "consume" the returned world state from an IO operation, then the operation would not be executed at all.
Haskell does allow you to do something like this with the unsafePerformIO "backdoor".
The documentation states: "If the I/O computation wrapped in unsafePerformIO performs side effects, then the relative order in which those side effects take place (relative to the main I/O trunk, or other calls to unsafePerformIO) is indeterminate"
A less poetic way of stating it: if you re-use an already-used world state, you are violating a logical constraint of the system. No timelines will be violated, you just have an invalid program.
World states are supposed to consumed once only: they are "linearly typed" in fancy language. But Haskell cannot directly enforce the linear-type constraint on world states, so in theory these states could be used more than once. Fortunately, the IO monad encapsulates (hides) the states, so that you can't make this mistake in your application code -- you can't reuse what you can't access.
World-states in Haskell aren't first-class values. They aren't actual objects that are being passed around during the execution of the program. They are more like tokens that exist during type checking, but are erased when the program is compiled or interpreted.
Another way of phrasing it is that Haskell interpreters/compilers have specialized support for the IO monad: during type checking, it acts as if these states exist, but they have no operational meaning in the executed program.
> you are violating a logical constraint of the system
I'm looking for something even a little more concrete than this. Why would the program be invalid? Can you give an example of what would happen if we just went ahead and accessed a worldstate a second time, logical constraints be damned?
In a language like Mercury where the IO states are explicit, this would be true. You can run IO in reverse in Mercury if you want:
main(IO_In, IO_Out) :-
print_line("This will print last", IO_1, IO_Out),
print_line("This will print first", IO_In, IO_1).
But in Haskell, the world states aren't accessible to the programmer. It's misleading to suggest what could happen in Haskell if you reused a world state, because it's impossible to do so.
Due to HN's limitations, I can't reply to your reply, but I can reply here instead. :)
I'm not sure this is a helpful metaphor, but I don't think it's completely awful:
Think of a world state as being a variable representing a timestamp. Until the state is used up, the variable is empty.
Suppose I read from a file. I get a brand new state back from the file-reading action -- let's call it WorldState1 -- whose contents are "The time is now X." We don't have a value in X, it's just a variable. (You could think of it as "The time is now <NULL>" if that makes more sense.)
Later, when we use our world state in a subsequent action, X is resolved to an actual time. Now, WorldState1 means "the time is now 09:57:53.00001".
Later in your program (say, 09:57:53.00999), you try to reuse the same state. The system tries to write the current timestamp (.00999) into the state variable... but it fails, because the state already contains a value. The system requires that these variables can only be written once.
If these states existed at runtime, you would get a runtime error in your program. Instead, the type-checker simulates the execution of your program during compilation, realizes that the state would be written twice, and raises a compile-time error.
Again, it's just a metaphor. But hopefully it helps to get an intuition about these usable-once-only values.
-----
The problem with metaphors is that there are so many of them, and they are all wrong. :)
I just thought of another that might click better. Think of a world state as a container that is holding a little bit of potential energy. Like a tiny battery.
Every time you take an I/O action, you need to power it up. An I/O action converts potential energy into kinetic energy.
Once you use up a world state in an action, the battery is drained. It can't be recharged, so the battery is just dead.
Fortunately, each I/O action returns, along with its output, a brand new little battery that you can pass on to the next action.
At any point in your program, you have exactly one charged battery on hand (and a pile of used-up ones). So it only makes sense to use the charged one in your next I/O action.
Yes, this is the correct answer. For everything else you don't need monads. Monads impose an evaluation order, which Haskell by default does not have, since it is lazy.
Monads do not impose an evaluation order in Haskell. Monads let you thread state between computations, but that state may be lazily evaluated out-of-order.
The belief that monads sequence IO operations is why new Haskell programmers have difficulty writing functions like "open file, read file contents, close file, and return contents". They assume the contents will be read before the file closes, and not when the contents are lazily evaluated up the call chain (leading to a "can't read closed file" error).
Imagine if the IO operations took an explicit world parameter instead of using a monad:
The IO operations would be executed in the expected order due to the dependency of the "world" output of the previous operation. Monads not necessary.But if you accidentally used world2 twice then you would split the universe into two timelines. A monad can avoid this issue by hiding the world parameter and passing it on behind the scenes.