Earlier quoted context omitted.
I mean, as long as you have a flatMap/concatMap function JavaScript arrays are technically a monad too. But that is a practically meaningless thing to say. What we talk about when we have about monads is the highly generic interface coupled with the highly generic combinators. This is not something shared by arrays and promises. In other words, none of them are monads in any practically meaningful sense.
> I mean, as long as you have a flatMap/concatMap function JavaScript arrays are technically a monad too. But that is a practically meaningless thing to say. It's not "practically meaningless"; it's depth-first-search logic programming (as per How to Replace Failure by a List of Successes https://rkrishnan.org/files/wadler-1985.pdf )
How side effects work in FP
61–70 of 107 posts
Re: How side effects work in FP
#62FP explanations like this annoy me: That is, this code: x = foo() y = bar(x) z = bar(x) Should mean the same thing as: y = bar(foo()) z = bar(foo()) Because it's obvious to anyone that has written any sort of complex program that those are not the same thing. foo() can be an expensive operation like an HTTP call. Or it might depend on a database which can change state underneath it. I assume FP has answers for these…
> Because it's obvious to anyone that has written any sort of complex program that those are not the same thing. They are the same thing in Haskell (except for when forcing the thunks into eager values happens, due to the weirdness of laziness, but that has nothing to do with purity). > I assume FP has answers for these things but the tutorials never cover them. Except this one does: > The
That just raises a different problem:
y = bar(generateUUID())
z = bar(generateUUID())
>Except this one does:
It doesn't explain it in the context of real world programming.
Re: How side effects work in FP
#63So this is basically Eternalism[1]? Instead of writing a program that reads inputs, experiences a sequence of state mutations and takes actions, you generate the graph of every possible state and path between states annotated with inputs on the edges and outputs on the vertices, and let the runtime pick the actual path through it. And this works because you're generating it lazily, but that's just an implementation d…
Re: How side effects work in FP
#64Earlier quoted context omitted.
A bunch of those mentioned don't address laziness and how the IO monad deals with it: http://blog.vmchale.com/article/effects
Actually the most important part of monads in Haskell is the do-notation. Without that, monadic code would look like JS' callback hell (although composition of monads using monad transformers/monad stacks isn't much better). Haskell without laziness is Purescript, btw.
Re: How side effects work in FP
#65Earlier quoted context omitted.
That model fits functional reasoning. It doesn’t fit all programs, though, as it doesn’t allow the input to depend on the _execution_ of the mutation instructions. Example: input “I step forward”, output “monster appears”, next input “I try to shoot it”. That second input wouldn’t be there if the output weren’t executed.
Outputting and manipulating side-effects can be useful even in imperative code. I had real performance problems a while back attempting to build a very large file, my code could only produce the write instructions out of order and the file was too big to hold in memory, so what I ended up doing was writing writing instructions in radix-grouped batches to a bunch of temporary files, and then reading and evaluating the…
For example, it took me a while to realize that most of the it's actually faster to read/write compressed data overall - you'd think that reading from a disk and decompressing the data would be slower than just reading uncompressed data from a disk directly, but due to the vast difference in disk IO performance and CPU decompression performance it's almost always faster to perform disk IO compressed. I'm writing almost always since I'm not sure how the tradeoff looks for current high performance PCIe SSDs (or other storage devices with very fast IO).
Re: How side effects work in FP
#66So this is basically Eternalism[1]? Instead of writing a program that reads inputs, experiences a sequence of state mutations and takes actions, you generate the graph of every possible state and path between states annotated with inputs on the edges and outputs on the vertices, and let the runtime pick the actual path through it. And this works because you're generating it lazily, but that's just an implementation d…
The laziness allows the pure language constructs like "if" to be lifted up into the IO value. That is, if you embed an if statement into one of the (pure) functions used in an IO value somewhere, only the branch that the "if" would take is ever evaluated. However, technically speaking, you can also look at the branches happening for all inputs; if you input a true or a false from the user, you can look at that as a true branch, a false branch, and some additional error branches, even if the code doesn't look like it. A strict language would not permit this. (Though even in a strict language, real-world useful IO values end up with enough function calls in them that a strict language wouldn't manifest the full tree either.)
If you input a full Int32, you can look at that as defining 2^32 possible branches in the IO value, and the execution will take one of them. In reality, the program does not do 2^32 different things, and a better view of what the program is doing is the more conventional one for almost every purpose. (Although that view will still pass through a lot of possible states!) But in terms of understanding how the IO value is "pure", this view is momentarily helpful.
The only operation in a Haskell program that violates this is unsafePerformIO, which really does penetrate this abstraction and work like a "normal" program. Otherwise, no matter how much Haskell code you write, technically all you're doing is making a bigger and more complicated pure IO value, which defines how to have an effect on the world but does not itself have an effect on the world. Executing the program is what finally puts the two together.
To put it another way, Haskell has a completely clear separation between being a program and executing a program. If I say to someone, "pick up that glass, fill it with water, and water this plant", that statement is itself a "pure value". The execution of that statement is where things get impure. Some languages do not have this clear separation, most notably Perl but the dynamic languages in general don't. Many others do, as having a compilation step all but forces this sort of separation, they just don't think of it that way and there can be leaks here and there, and features that may blur the line deliberately. Haskell does have a very clear separation, and in that separation, with laziness, it is almost like IO is just one big macro language for putting together programs, in some sense beyond what even Lisp would dream of.
And in another sense it's just a funny way to write conventional programs with really weird pretensions, and there's a certain value to that point of view too, which is that when you're done blissing out on the hippie math juice, when you actually sit down and write code in Haskell you're doing much the same thing you do in any other language and treat IO just like any other source code. But, as with the story of enlightenment... "first it was a mountain, then it was not a mountain, then it was a mountain again"... where you end up on this journey is not quite the same as where you started.
Re: How side effects work in FP
#67So this is basically Eternalism[1]? Instead of writing a program that reads inputs, experiences a sequence of state mutations and takes actions, you generate the graph of every possible state and path between states annotated with inputs on the edges and outputs on the vertices, and let the runtime pick the actual path through it. And this works because you're generating it lazily, but that's just an implementation d…
I like to explain functional programs like this. Functional programs don't _do_ anything, they are a list of commands some external interpreter (what you call the runtime, but doesn't have to be a runtime) will execute or compile. Functional programs only contain descriptions of the various commands, but it is an external interpreter executing the commands. Functional programs return "recipes". "recipes" are picked u…
But why would someone want to do this? It feels like mental overhead. So what advantages are there to a system like this?
Re: How side effects work in FP
#68Earlier quoted context omitted.
A bunch of those mentioned don't address laziness and how the IO monad deals with it: http://blog.vmchale.com/article/effects
Actually the most important part of monads in Haskell is the do-notation. Without that, monadic code would look like JS' callback hell (although composition of monads using monad transformers/monad stacks isn't much better). Haskell without laziness is Purescript, btw.
actA >>= \a ->
actB a >>= \b ->
pure doSomethingWith b
is a lot better (to my eyes) than: actA(a =>
actB(a, b =>
doSomethingWith(b)
)
)
and it gets worse from there (blocks to have statements to declare variables, or to call multiple functions. pre ES6 those would've also been functions rather than lambdas. Admittedly monad transformers aren't that great, but that's mostly because of the nxm amount of instances (to avoid explicit lifting).Re: How side effects work in FP
#69Earlier quoted context omitted.
> Because it's obvious to anyone that has written any sort of complex program that those are not the same thing. They are the same thing in Haskell (except for when forcing the thunks into eager values happens, due to the weirdness of laziness, but that has nothing to do with purity). > I assume FP has answers for these things but the tutorials never cover them. Except this one does: > The
>They are the same thing in Haskell That just raises a different problem: y = bar(generateUUID()) z = bar(generateUUID()) >Except this one does: It doesn't explain it in the context of real world programming.
y = bar(generateUUID())
z = bar(generateUUID())
You can't write that in Haskell! Instead you write something like. yuuid Re: How side effects work in FP
#70Earlier quoted context omitted.
> Because it's obvious to anyone that has written any sort of complex program that those are not the same thing. They are the same thing in Haskell (except for when forcing the thunks into eager values happens, due to the weirdness of laziness, but that has nothing to do with purity). > I assume FP has answers for these things but the tutorials never cover them. Except this one does: > The
>They are the same thing in Haskell That just raises a different problem: y = bar(generateUUID()) z = bar(generateUUID()) >Except this one does: It doesn't explain it in the context of real world programming.
generateUUID :: IO UUID
which means that those two lines don't _really_ make sense: you'd get a compile error. to use side effects (which in this case, IO, mostly means opting into sequencing of actions) you'd have to write uuidY
in which case, equational reasoning still holds.
Side-note: in haskell, you could write it while avoiding the middle line using either the left or right bind operators, so you could write it as y = bar =>= bar
(mnemonically: the first one runs an action and pipes it to the left, the second one does the same but pipes to the right)Side-note #2: that's actually not too far away from how random number generators work in haskell: either you pass the state around, or you do it in IO.