6 ms·
Isn't it more like, no mutation?
by dataflow 5y ago
Isn't it more like, no mutation?
- tinco 5y agoNot exactly. When using the I/O monad in a pure language like Haskell, your code doesn't directly have side effects, instead you're building a container (a monad) of instructions of what side effect having operations to do, and what should be done with the results. The Haskell runtime then takes this monad and unwraps it layer by layer, each layer executing the side effect having instruction and using the results of the side effects as input for unwrapping the next layer. You could argue that because of this system the language itself is pure and without side effects and without mutation. Note that the effect of this system is exactly the same as a language that does have side effects. They are functionally identical. The nicety of Haskell's purity comes from how the monads allow you to organize your code. By the way, not sure if this is controversial but JavaScript's I/O system is a lot like the I/O monad. I/O doesn't really get executed immediately, and you can't directly operate on the results of I/O. The runtime executes the I/O after all JavaScript's been executed, and you use callbacks to tell the runtime to do whenever the I/O data returns. Of course JavaScript does allow for mutation, so it's not pure in that sense.
- lmm 5y ago> instead you're building a container (a monad) of instructions of what side effect having operations to do, and what should be done with the results. The Haskell runtime then takes this monad and unwraps it layer by layer, each layer executing the side effect having instruction and using the results of the side effects as input for unwrapping the next layer. Note that this is the implementation of a specific monad - not all monads are like that. Many monads are not containers, and some can't be "unwrapped" at all. > Note that the effect of this system is exactly the same as a language that does have side effects. They are functionally identical. The nicety of Haskell's purity comes from how the monads allow you to organize your code. You might enjoy "the C language is purely functional": http://conal.net/blog/posts/the-c-language-is-purely-functional http://conal.net/blog/posts/the-c-language-is-purely-functio... . The useful part of purity is that the pure form of Haskell has some interesting algebraic structure (in particular there are a lot of ways to refactor code while preserving its semantics), whereas the pure form of C or javascript does not have many equivalence laws. > By the way, not sure if this is controversial but JavaScript's I/O system is a lot like the I/O monad. I/O doesn't really get executed immediately, and you can't directly operate on the results of I/O. The runtime executes the I/O after all JavaScript's been executed, and you use callbacks to tell the runtime to do whenever the I/O data returns. I think you're conflating interface and implementation here. Javascript I/O is implemented similarly to Haskell I/O in some ways, yes. But the interesting part of Haskell's I/O isn't the implementation, it's the (pure) API that it uses, and Javascript has nothing like that.
- tinco 5y ago> But the interesting part of Haskell's I/O isn't the implementation, it's the (pure) API that it uses, and Javascript has nothing like that. No, I insist JavaScript really has a pure approach to I/O, quite like Haskell. There is literally no side effects except for application state mutation from the perspective of a JavaScript function (if you disregard the few deprecated synchronous API's). And this pure API has proven very useful and distinctive for JavaScript (i.e. through node.js)
- lmm 5y ago> There is literally no side effects except for application state mutation from the perspective of a JavaScript function (if you disregard the few deprecated synchronous API's). That's a pretty big "except" - there are no side effects except for all the side effects (and there's nothing unique about JavaScript there, that much is the same as almost any other language). JavaScript's approach to I/O, like most languages, uses things that look like functions but aren't functions, in that they give different outputs for the same inputs. The whole point of Haskell's I/O system, the thing that separates it from other languages, is that it doesn't do that; in Haskell your functions are actually functions and I/O operations are very clearly not functions.
- tinco 5y ago> That's a pretty big "except" - there are no side effects except for all the side effects (and there's nothing unique about JavaScript there, that much is the same as almost any other language) It's not like Haskell doesn't have those either though, I don't think it's a very big except. Discussing synchronous I/O in JavaScript is not interesting, were not littering our Haskell with unsafePerformIO either. > JavaScript's approach to I/O, like most languages, uses things that look like functions but aren't functions, in that they give different outputs for the same inputs. No, this is just not true, are we using the same JavaScript? JavaScript's functions change only based on modified application state, not on any other side effect. > The whole point of Haskell's I/O system, the thing that separates it from other languages, is that it doesn't do that; in Haskell your functions are actually functions and I/O operations are very clearly not functions. Sure, in the current Haskell that's true. In previous iterations of Haskell this wasn't always the case though, monadic I/O wasn't the first pure system, we also had stream based and continuation/callback based I/O, the latter being basically what JavaScript does.
- lmm 5y agoWhat's state without mutation? If something is immutable then you can't tell the difference between a value and a copy of that value, so the question of whether it's "state" is rather meaningless.
- dataflow 5y agoI think you can define it. You just need to be willing to pin down some definitions so there's something you can formally talk about. For example, let's say an "program" (in whatever abstract sense, whether you want to represent it as DAGs of function compositions or as a sequence of instructions or whatever else) takes, by definition, an infinite sequence of input symbols, and produces an infinite sequence of output symbols. Both sequences start (WLOG) at index 0. Note that for finite sequences, you can represent them by appending an infinite number of symbols that you consider null. Let's say that "running" the program "until step k" (in whatever abstract sense you want to define, whether it's evaluating functions or following a sequence of operations or whatever) corresponds to "producing all outputs up to index <= k". The "state" of that program at step k might then be defined as the amount of information (measured in bits or whatever) that that particular program requires for eventually producing outputs at indices > k, aside from the information needed to represent the program itself. As long as you formally define a way to measure information and a way to represent your program, I think you should be able to come up with a definition as I did above to define "program state" formally. You just need to be willing to do that, is all.
- lmm 5y agoI don't think that captures the aspects people care about most of the "program state" that people care about is unrelated to I/O - particularly in Haskell, people care about programs that are essentially "evaluate this large equation" where there's only one piece of output at the end, but might be a lot of intermediate computation. And the details of what's stored in memory aren't what matters - after all, Haskell very deliberately makes no distinction between a plain value like 3 and a large and complicated thunk that will eventually evaluate to that value - but if you just measure the minimum number of bits required to store something equivalent then you've assumed away the whole evaluation of the program. At least from the functional perspective you're coming at this from the wrong end in the first place - you're looking at it operationally rather than denotationally. The point of saying that there's "no state" or "no mutation" in a functional program is that there is no such concept at the semantic, code level: when reading any given piece of code, you don't need to think about state to understand what it's doing.