4 ms·
If Haskell I/O is pure, why can't it be intermingled with other pure functions?
by millstone 14y ago
If Haskell I/O is pure, why can't it be intermingled with other pure functions?
- jerf 14y agoIt can be. But what gets intermingled with the other functions is indeed the pure language describing what IO actions to take, not their result. For instance, I'd had some good experiences returning IO actions out of STM transactions.
- tmhedberg 14y agoIt can be. A value of type `IO foo` is an ordinary, pure value just like a value of any other data type. It can be passed as an argument to a pure function, returned from a pure function, manipulated using any functions that can apply to `IO` values, stored inside an arbitrary data structure, composed with other `IO` values to make new ones, etc. What isn't pure is the process of extracting the `foo` from the `IO foo`, i.e. getting the result of the I/O action. For instance, getLine :: IO String is not a function in Haskell, which you can "call" in order to get a string from stdin. It is instead a value which represents an I/O operation yet to be performed. There is no way to force it to yield its result within your entirely pure program (ignoring escape hatches like `unsafePerformIO` and friends), but you can nonetheless compose it with other I/O operations which depend on its result, such as putStrLn: echo :: IO () echo = getLine >>= putStrLn The above defines a pure value representing an I/O operation which, when performed, will echo a line from stdin to stdout. The `>>=` combines the two pure values to yield a new pure value. Again, there is no way to directly cause this I/O action to be executed, which would necessarily introduce impurity. You can only ensure that it will happen later by threading it into the special I/O operation `Main.main :: IO ()`, which every program must define, and which will be implicitly performed when your program is run. By reifying "statements with side effects" into simple, pure, first-class values of type `IO`, Haskell lets us remain 100% pure even while our program will eventually have effects on the real world outside of the machine. Haskell code is just a set of pure declarations; the side effects are deferred until after our code has already run (which conceptually happens instantaneously when the program is launched). Since our code runs "before" any side effects occur, it cannot possibly depend on their results. A Haskell program merely sets up the dominoes in an elaborate arrangement, then looks the other way while the runtime system knocks over the first one.
- Evbn 14y agoThat's not entirely fair. Better to say that Haskell separates the pure from impure, but it has both. The straight line dependencies of the IO sequencing are still part of the Haskell program. The domino analogy could apply to C just as well, except that there are only a few lines of code before the first domino falls.
- tmhedberg 14y agoSorry, but I disagree. A Haskell program emphatically does not have any concept of impurity or statefulness whatsoever, except in the form of `unsafePerformIO`, which circumvents the type system, only comes into play under "extenuating circumstances", and which an ordinary program has no use or need for. The language itself does not have any notion of sequencing or order of evaluation, nor does it need one. It inhabits a pure, mathematical universe where such things have no meaning. Given an arithmetic expression such as (1 + 2) * (3 - 4) it naturally makes no difference which parenthesized expression is evaluated first, because the evaluation of mathematical expressions cannot have side effects. It would be absurd to consider a universe in which such a choice could influence the result of fully evaluating the expression, because such a universe would be wholly unlike our own, to the extent that our intuitive rules of logic and causality would no longer apply. Likewise, given the Haskell expression putStrLn "Hello" >> putStrLn "world!" it makes no difference which of the two IO expressions is evaluated first, because the evaluation of those values cannot have side effects. The second one may be evaluated before the first; it makes no difference. The execution of the resulting action will have side effects and impose sequencing, but there is no way to express execution or cause it to occur in the Haskell language. Execution can only happen "outside" of the context of the program itself, not in the program's abstract and pure universe, but in our own imperative and stateful one, as a side effect of the RTS. A C program, to extend the metaphor, knocks over each domino one by one as soon as it is placed. In fact, placing the domino and knocking it over are one and the same thing. The programmer has no opportunity to leave the room before the side effects come into play, because imperative languages do not distinguish between evaluation and execution. A purely functional language not only draws that distinction, but eliminates execution from the picture entirely, leaving it to some external entity to actually do the deed that the program constructs from pure components.