4 ms·
I dont understand this. This has the same disadvantages as global variables: every function (that has the state) can change it.
by andi999 6y ago
I dont understand this. This has the same disadvantages as global variables: every function (that has the state) can change it.
- IshKebab 6y agoRight but global variables are implicit. Function inputs/outputs are explicit. Another advantage is testing. That said, I still think passing the world in and out of every function is too onerous for most real world programs, and functional programming definitely takes a too-extreme stance to be pleasant. I think a better approach is a traditional language but with a functional subsystem where you can mark functions as pure.
- Twisol 6y ago> That said, I still think passing the world in and out of every function is too onerous for most real world programs [...] This is a bit of a strawman. At least in my limited experience, functional programmers think so too. Part of what monads let you do is a kind of inversion of control: instead of changing the world directly (as you would if you were seriously threading the world through your domain logic), you return a description of what you're doing, and let the runtime (in the case of IO) or the interpreter for the monad (in other cases) actually perform the changes. Similarly, lenses allow you to describe local mutations and lift them to the level of a whole structure. Explicitly threading your state is the first refactoring, not the final destination. If you have a code smell, the first thing to do is draw it out into the open so you can wrangle it. In languages that don't conveniently support functional strategies like monads or lenses, it's tempting to stop there. But that doesn't mean FP is about stopping there.
- voodootrucker 6y agoThanks, your comment caused me to learn what a lense is.
- IshKebab 6y agoYou still have to pass your logging code to every function that does logging right? And if you add logging to a function deep in your call stack you have to go all the way up the stack adding it as a parameter everywhere. It's rigorously correct, but I feel like it's a step too far for most programs.
- Twisol 6y ago> You still have to pass your logging code to every function that does logging right? Yes, but no. Again, there are functional patterns you can apply. First, functional programs tend to be more horizontally composed than vertically. Side effects usually happen at the same "level" of the application, rather than occuring at multiple depths. So the need to thread logging apparati is already reduced, simply because logging only happens higher up in the application. Second, because side-effects only really occur at this thin layer, you can capture logging as a monadic action, avoiding the need to explicitly thread the logger state around. (This is the same situation as the "thread the world" problem we discussed earlier.) Third, because so much more of your program is pure, it is much easier to perform unit tests and check assertions in other ways. At that level, logging is a bandaid over poor testing and assurance, not a goal in itself. Finally, if you do find yourself deep down-stack with a need to perform some kind of side-effect, you can apply logical methods (a la LVars, CRDTs) to give a stateful interaction between separated parts of your program. The Haxl paper [0] is a good example of this: most of the design is pure functional, but for the part that involve an interaction not easily captured by the fundamental call-return / request-response pattern, they utilize monotonic state to maintain a registry of fulfilled and unfulfilled requests. [0] https://simonmar.github.io/bib/papers/haxl-icfp14.pdf https://simonmar.github.io/bib/papers/haxl-icfp14.pdf > It's rigorously correct, but I feel like it's a step too far for most programs. I feel you're being too dismissive. There is an entire community of developers -- many of whom operate in industry -- who can and do effectively solve the problems you are raising. "Rigorously correct" is one of those things non-FP folks think FP is about. You can write buggy code in any paradigm. What people like about FP is that it's easier to reason about, and it's generally easier to find precise abstractions that support that kind of reasoning. "Rigorously correct" is missing the point.
- 6y ago
- eyelidlessness 6y agoThe point is that you start with your program as a function of the entire state tree, but as you break down the problem into smaller functions they only use or need the subset of the state they intend to “mutate”. In a dynamically typed language, doing this safely requires selecting a subset at the call site (at either the parameter or from the return value). In statically typed languages this is more easily expressed with interfaces(/types/protocols/contracts/whatever the language calls them) and you can still safely pass the full state if that’s more convenient.
- oxymoron 6y agoI wrote a game in that style, once: http://emilhernvall.github.io/bubblespinner/bubblespinner.html http://emilhernvall.github.io/bubblespinner/bubblespinner.ht... (repo at https://github.com/emilhernvall/bubblespinner https://github.com/emilhernvall/bubblespinner) It’s in Javascript, so not all of it is functionally pure, but the core game logic follows that pattern. The code is heavily commented, and hopefully somewhat readable. I think the main benefit I found was that it was trivial to snapshot states and replay as needed. That made debugging easier, and might have simplified something like a save/reload feature. Although I didn’t go there for this toy project, I’d argue that testability might be another consideration. More generally, I’ve seen over the years that global state tends to be painful in unforseen ways. At one point, I was considering a fork of putty to add support for tabs. It has a lot of global state, so I had to abandon that idea. If the same state had been wrapped up in a state struct and passed around, it’d have been easy.