Live data from Hacker News

How side effects work in FP

chadnauseam.com

91–100 of 107 posts

Re: How side effects work in FP

#91
post #2

dweb link: > ipfs resolve -r /ipns/nauseam.eth/coding/random/how-side-effects-work-in-fp/: no link named "coding" under QmdzzonFE9eX6FGs8UbCyoC2XS5NQjaG6gaqhgeUyTHnag

Thanks! I was accidentally pushing garbage to my IPNS name. It should be resolved now.

Re: How side effects work in FP

#92
post #50
post #24

Earlier 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.

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 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

#93
post #50

Earlier 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…

Thanks, but an answer from someone that knows the language that they're talking about would be much more productive.

Re: How side effects work in FP

#94

Well, 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'…

This perspective is interesting to me because I feel like every single comment section on HN about async syntactic sugar has a Haskell programmer complaining that the sugar is only available for 1 single type (the async type) and not ALL THE MONADS.

Re: How side effects work in FP

#95
post #73
post #55

Earlier 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.

They're not exactly the same:

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
end

In 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

#96

So 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…

[deleted]

Re: How side effects work in FP

#97
post #39

Earlier 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.

What you propose requires some arrow leading (directly or indirectly) from mutate! to input. The model I commented on:

  input -> fn1 -> fn2 -> fnn -> mutate!
doesn’t have such a loop.

Re: How side effects work in FP

#98
post #41

The 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

I think laziness would be a distraction here. The idea of encoding effects as "action values" is relevant and interesting, even under eager evaluation.

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

#99
post #50

Earlier 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…

And here we see the damage caused by the modern OOP. People that complain about that want to replicate in Haskell the lob4j philosophy of adding logging into every interface, because with data and IO chunked everywhere inside object interfaces, you never know if you can ever repeat an execution in a development environment to verify it.

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

#100
post #83

Earlier 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…

In F# (the most widely used ML?) expressions can be much more complicated than that, incorporating loops, conditionals etc.

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.

Post reply on HN