dweb link: > ipfs resolve -r /ipns/nauseam.eth/coding/random/how-side-effects-work-in-fp/: no link named "coding" under QmdzzonFE9eX6FGs8UbCyoC2XS5NQjaG6gaqhgeUyTHnag
How side effects work in FP
91–100 of 107 posts
Re: How side effects work in FP
#92Earlier quoted context omitted.
This sort of thing is where I detach from the idea that functional programming is the ultimate good as some advocates would push it: it's far too obvious that outside of narrow constraints, real hardware just isn't functional. No where was this more apparent in my career then when I was on a team of Scala-using FP programmers who were building part of a real hardware provisioning system (and the org structure tipped…
> FP is terrible at logging Um, what? You can't just drop this without elaborating. I've never seen anybody have problems logging in a functional language.
To log you need IO. To get IO you need to provide it to the function that will do logging. And now your function has to be marked as doing IO. So now you need to thread all that IO through all your functions, and "turn you code into monadic code" (I think that's the term).
Other languages (like Erlang) don't care, and you can log whenever you want.
---
Flamewar off-topic: it looks like hardly anyone is doing any useful logging in Haskell, because if you search for "logging in Haskell" you end up in:
- highly academic discussions on "logging actions" vs "logging of computations"
- extremely convoluted solutions that turn even the simplest examples into a mess
- a couple of libraries whose entire documentation is usually "believe me we're the shit", and if they do have examples, they are an impenetrable mess of custom types and ascii art
For every other language it literally is `logger.info(something)`
Re: How side effects work in FP
#93Earlier quoted context omitted.
> FP is terrible at logging Um, what? You can't just drop this without elaborating. I've never seen anybody have problems logging in a functional language.
They probably mean Haskell. Note: I don't know Haskell, so below is speculation. To log you need IO. To get IO you need to provide it to the function that will do logging. And now your function has to be marked as doing IO. So now you need to thread all that IO through all your functions, and "turn you code into monadic code" (I think that's the term). Other languages (like Erlang) don't care, and you can log wheneve…
Re: How side effects work in FP
#94Well, that's the first time side effects in Haskell made sense to me. Well done. Not that I've ever seriously tried to learn Haskell, but in the past every time I've lazily come across an article about it it's always seemed like a bizarre confusing world, even though I know how functional programming (in the sense of purity) works. Now there's just one thing missing. We all know what this style of programming is. It'…
Re: How side effects work in FP
#95Earlier quoted context omitted.
Should mean the same thing as means has the same result value here. The operational semantics could be different, as you mention: even without side effects, computing x may be very expensive. This isn’t even special to FP, it’s a basic problem when building a compiler, namely whether inlining/common subexpression elimination is beneficial. To drive the point home, even in a language like Haskell these two examples mi…
In theory you're right, but in this specific case they really are the same - the compiler has an optimization pass called "common subexpression elimination" that converts the second to the first in nearly all cases.
Case 1:
x = foo()
y = bar(x)
z = bar(x)
Case 2:
y = bar(foo())
z = bar(foo())
In case 1 the value of x must be kept in memory across the entire execution of bar(x) since you need it for the second invocation of bar. In case 2, the result of foo() can be discarded once bar is done with it.
To use a contrived example, imagine a program that has 100 mb of available memory.
foo() returns a data struct that is 95 mb in size.
def bar(some_big_data_struct)
some_small_data = some_big_data_struct[:some_key]
[A bunch of other code that allocates and then releases 90 mb]
return some_other_small_data
endIn Case 1 I get an OOM error. In Case 2 I (or the runtime) can reclaim the 95 mb that some_big_data_struct uses and the program works.
Clearly that's a contrived situation but it illustrates my original point. There's a huge gap between theoretical pure functions and what we have to deal with in the real world. These sorts of FP tutorials never go into the weeds and explore these problems.
Re: How side effects work in FP
#96So 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
#97Earlier 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.
You missed the point where a program can take inputs over time. In your example, the program takes “step forward” and goes through the flows, eventually mutating state at the end, including redrawing the screen or prompting the user for their next input. The next input is based on the current state of the machine, and there can be as many or few valid state transitions as you, the programmer, would like to manage.
input -> fn1 -> fn2 -> fnn -> mutate!
doesn’t have such a loop.Re: How side effects work in FP
#98The author doesn't mention laziness, which is pretty huge. Laziness is half of immutable data structures! I have a tidbit in the first paragraphs: http://blog.vmchale.com/article/effects
Certainly laziness has influenced a lot of things in the world of FP. But it's really helpful to distinguish laziness from FP, especially considering the problems that laziness introduces.
Re: How side effects work in FP
#99Earlier quoted context omitted.
> FP is terrible at logging Um, what? You can't just drop this without elaborating. I've never seen anybody have problems logging in a functional language.
They probably mean Haskell. Note: I don't know Haskell, so below is speculation. To log you need IO. To get IO you need to provide it to the function that will do logging. And now your function has to be marked as doing IO. So now you need to thread all that IO through all your functions, and "turn you code into monadic code" (I think that's the term). Other languages (like Erlang) don't care, and you can log wheneve…
The thing is, if for some reason you really think you need to log inside a pure function, you either need an intermediate variable or your perceived needs are severely misguided.
Re: How side effects work in FP
#100Earlier quoted context omitted.
A related advantage is expression-orientated code. In ML languages, code forms a beautiful tree structure where you can see the binding definition of each binding by looking down and right. let x = something really big and complicated whereas in a language built around statements it will be scattered all over: let x = null; x = something(); x = somethingElse(x); x = anotherThing(x); In the first case the definition o…
You can get something similar via method chaining: x.something().somethingElse().anotherThing() C# extension methods aren't the perfect solution for this, but I love that they exist to at least make chaining possible without needing to modify the class that I'm applying the function to. D has an even better version of this called UFCS which makes it so `Bar something(Foo f)` can either be called as `something(x)` or…
To illustrate:
let x =
let mutable maxScore = 0
for item in items do
if item.Score > maxScore then
maxScore
Compared to: const x =
(() => {
let maxScore = 0;
for (const item of items) {
if (item.score > maxScore) {
maxScore = item.Score;
}
}
return maxScore;
})();
Not necessarily the best way to write this (you would probably use a sequence library) but hopefully conveys the idea.Extension methods are definitely useful in OOP languages. However, a much cleaner language design is to remove the need for a "class" altogether and just have free-functions, function composition and a pipe operator.
None of this is something you can't simulate in OOP / procedural languages, it's just much more clunky.