3 ms·
A good mental model is that Haskell has expressions that are evaluated and JavaScript has statements that are executed. const io_actions = ["hello", "world"]
by pwm 5y ago
A good mental model is that Haskell has expressions that are evaluated and JavaScript has statements that are executed.
const io_actions = ["hello", "world"].map(s => console.log(s));
io_actions[1];
vs.
io_actions = map putStrLn ["hello", "world"]
main = io_actions !! 1
JS will print out "hello" and "world" and the value of io_actions[0] is undefined. In Haskell, the value of io_actions !! 0 is IO "world" which is just a value you can pass around in your program. Only when the Haskell runtime evaluates your code is when it will do the effect, which is to print "world". "hello" is never printed.
Naturally you can also pattern match on IO ():
f :: IO ()
f = case putStrLn "hello" of
_ -> putStrLn "world"
also only prints "world".
The IO type in Haskell is akin to IO (State World -> (State World, a)), ie. the state monad where the state threaded through the computation is the "real world". This can be demonstrated with the following:
f :: IO ()
f = IO $ \world -> case putStrLn "foo" of
IO _ -> case putStrLn "bar" of
IO g -> g world
prints "bar".
- goto11 5y agoThe JavaScript equivalent would be: const io_actions = ["hello", "world"].map(s => () => console.log(s)); io_actions[1](); which also prints "world" when the JavaScript runtime evaluates the code.
- pwm 5y agoIf your point is that one can implement a Haskell like language in any turing complete programming language then sure, you are correct. GP's point however was that Haskell is purposefully designed to be written and thought about this way from the ground up. Ie. putStrLn "hello" is not a command that is executed verbatim when the runtime gets to its source position. It's an IO action and an IO action is just a value that can be passed around or, by virtue of IO being a Functor, mapped over, etc... If you say that purity and referential transparency are not only reserved for Haskell then again technically sure, but the point of Haskell is that it makes these concepts the centre of its design. Instead of having to jump through hoops to write code where you can do the above you have to jump through hoops to do the opposite.
- goto11 5y agoMy point is just that input/output is not pure in Haskell. I believe it is an unfortunate misconception that obscures what the actual cool properties of Haskell is. IO functions like putStrLn is curried by default, so you can pass them around without executing them. But this does not affect the purity of executing them. And it is not something special for Haskell, you can pass functions around without executing them in almost any language. The cool thing about Haskell is that code is pure by default, and it is only due to special-casing by the language (the type of "main" being "IO ()") that you can even have operations with side-effects. Haskell allows you to have parts of the program guaranteed pure, but the program is a whole is not pure if it does any input/output.
- przemo_li 5y agoIO is also pure. It will not be executed until the very last line of your app. O_O No seriously. If you wanted to model IO in say Java. Your whole program would be about constructing values. No I/O what so ever. Only do this then that then get a, then if a is this do x other..... Whole Java app and I/O would only happen when at the very end you execute run method on that constructed value. That'd not usual Java. After all in Java code itself can do control flow and I/O. Do why bother with those values. Need I/O on line ten out of 100. Do I/O immediately on line 10. What I/O on Haskell is is side effect. Explicit side effect. It's possible to write impure code in Haskell but that is not IO. It's when you use escape hatches that break type system guarantees.