2. no side effects
And you get most of the benefit of FP in a way that everyone can understand and can use any language.
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.
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-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.
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)
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.
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.
If you read some file (or remote URL) multiple times, you may get different data each time. Haskell makes "functions" with this property clearly distinct from regular functions. Most languages, including JavaScript, don't. That's what matters, not whether I/O is synchronous or not.
> Sure, in the current Haskell that's true. In previous iterations of Haskell this wasn't always the case though
That may or may not be technically true but it's not really informative or relevant - Haskell has had monadic I/O for over 20 years and the overwhelming majority of Haskell users have never used any other kind.
True, but just like in Haskell that data is not returned by the function, JavaScript functions are pure for I/O.
I do agree that Haskell is even stronger because it doesn't just guarantee the I/O has not yet been executed yet, it also reveals the intent of I/O through the use of the I/O monad.
Are you just talking about the absence of shared-memory threading (which you also get in, say, OCaml, or even Erlang or idiomatic Python)? I can understand the value of not having to reason about arbitrarily interleaved executions, but not how that particular kind of sequencing of I/O specifically is more useful than that.
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequ...
Certainly it's not idiomatic but that's not lmm's point. The point is that it's possible. In Haskell it's not possible.
Anyway, we've wandered pretty far off the main thread, which wasn't mine to begin with, so I'll let lmm take up the discussion again if he wants.
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.
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.
The thing is though, the fact that you don't doesn't imply state is meaningless or useless. After all, you're still going to face the fundamental limits on computation that are out there, regardless of the model of computation. It's not like switching to Haskell and beta-reductions suddenly lets you solve every problem instantaneously, right? You're still going to have just as hard of a time (if not harder!) solving a problem like TSP efficiently in Haskell, as you would in Python or Prolog.
Look at it this way: the only reason you wouldn't "care" about state would be that you already know how things are going to turn out, not because the whole notion is itself meaningless. If tomorrow you wrote a Haskell program that factored integers in time & space linear to the input size, you wouldn't toss it into the trash thinking "whatever, time and space are meaningless concepts in FP". You'd absolutely care. Not only would you care, but you'd safely assume everyone else would too. You'd either find a way to profit off those results, or you'd publish them with formal definitions and tell everyone and their grandmother how awesome and meaningful they are. You'd find them as far from meaningless as anything could be. Of course, you're probably not going to get that lucky, but your lack of luck or care for the concepts doesn't render the concepts themselves meaningless.
I'm not at all convinced. The thing that actually determines the performance of real-word programs these days (after algorithm choice) is cache efficiency, and no mainstream programming model gives you a "state" model that actually lets you reason about that.
> Look at it this way: the only reason you wouldn't "care" about state would be because you already know how things are going to turn out, not because the whole notion is itself meaningless. If tomorrow you wrote a Haskell program that factored integers in time & space linear to the input size, you wouldn't toss it into the trash thinking "whatever, time and space are meaningless concepts in FP". You'd absolutely care. Not only would you care, but you'd safely assume everyone else would too. You'd either find a way to profit off those results, or you'd publish them and tell everyone and their grandmother how awesome and meaningful they are. You'd find them as far from meaningless as anything could be. Of course, you're probably not going to get that lucky, but your lack of luck or care for the concepts doesn't render the concepts themselves meaningless.
Today's programmers put way too much focus on performance given that we as an industry haven't even mastered producing the correct output for any given input. Do I care about how much memory my program will use at runtime? Sure, at some level. Is it something I want front-and-centre in my programming language? No; language features that help me write correct programs are a higher priority.
> Today's programmers put way too much focus on performance
So statefulness is a meaningless concept in functional programming because... programmers put too much focus on performance? and cache efficiency is what matters?
Is there a logical chain of implications I'm missing here? I feel like we're not even in the same conversation at this point.
https://en.m.wikipedia.org/wiki/Monad_(functional_programmin...