Hacker News new | ask | show | jobs
by sullyj3 2389 days ago
what actual concrete, real world, non sci-fi sounding problem does "splitting the timelines" represent?
3 comments

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?

One of the operations using the same worldstate might not get executed, or the operations might happen in unpredictable order.
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.