4 ms·
>You seem to think that FP is the one right answer. You are mistaken. There are places where it's the right answer, or at least a very good one - the best defin
by nendroid 6y ago
>You seem to think that FP is the one right answer. You are mistaken. There are places where it's the right answer, or at least a very good one - the best definition I've seen is when your program looks like a pipe.
All programs are pipes from IO to IO or from one state to another state. Purity lives in the pipes, impurity lives in the IO nodes between the pipes. The goal is to keep the impurity as small as possible and have most of your logic live in the pipe, as combinators are the most composeable primitive. I also never said FP is the one right answer. Most programs need to modify state. In FP everything is immutable so your program cannot be pure FP in most cases. What I am saying is to segregate state and IO from logic. Example:
void addOneToState()
can be broken down into
int addNTo(int n, int m)
void changeStateTo(int x)
I have split the code above into a method that is devoid of logic and only updates state and logic that lives as a stateless combinator. All programs are made from a series of pipes and tubes. You just need to find the pipe and modularize it and seperate it from the parts that can't be pipes.
>At that point, bundling the data to the functions is warranted.
It's never warranted. There is no benefit. Whether you couple it or uncouple it from data the logic still exists, the only difference is coupling. Adding coupling does not improve your code in any other way other than adding coupling.
>And if you're going to say "don't structure the data that way", well, there are times when that's the nature of the problem, not just the nature of the program to solve the problem.
There is no data structure that has to be coupled with logic. However you structure your data it can always be decoupled from logic. Always.
>where your program has state, and there are multiple parts to the state, and those parts have to be kept in sync with each other.
This doesn't change anything.
State makeNewState (State oldState) {
newY = addNTo(oldState.y, 1)
return New State {
x = oldState.x
y = newY
somethingToBeInSyncTo = newY
}
}
void updateState(State x)
y is still in sync with somethingToBeInSyncTo.
- AnimalMuppet 6y ago"I also never said"? Yeah, I suspected that you and lerptime were the same person. > It's never warranted. There is no benefit. When you make such a statement, you sound like someone with fairly limited experience. In particular, from your example, I suspect that you've never worked in a multi-threaded embedded system. somethingToBeInSyncTo, done that way, means that any other thread holding a reference, holds a reference to the old value, while this thread has the new one. And updating running threads with new values on the fly is... problematic. There's a reason why some programs are not written the way you propose...
- nendroid 6y ago>Yeah, I suspected that you and lerptime were the same person. to get around the post limit. Keep it on the DL. >When you make such a statement, you sound like someone with fairly limited experience. In particular, from your example, I suspect that you've never worked in a multi-threaded embedded system. somethingToBeInSyncTo, done that way, means that any other thread holding a reference, holds a reference to the old value, while this thread has the new one. And updating running threads with new values on the fly is... problematic. I find that you're the one who seems inexperienced. My example is thread safe ... makeNewState is a combinator. It makes no mutations. Like IO, and mutations, the problems with threading happen with mutation. Thus, locks and other procedural commands used to deal with threading live in the void IO function. We're more referring to coupling functions with external data and mutations via objects. If you're talking about synchronizing two threads, this again needs to be handled with parent IO function outside the logic of your pipeline combinators. You can still segregate logic away from threading and IO. My combinator in this case is useable for your example case of synchronizing two threads. If you organized your code in ways where my combinator will be unusable than you have failed to decouple your logic away from threading, IO and mutation.
- AnimalMuppet 6y ago> I find that you're the one who seems inexperienced. 35 years professional software engineering, 30 years in embedded. I'm going to guess that you have less. Your way of organizing code works great, on paper. Maybe on a small project. For a real embedded system, with multiple threads, with state data being used everywhere... your approach doesn't make much sense. It makes a much worse design than shared mutable state. (Yes, shared mutable state is in fact as evil as you have think it is. It's still better than trying to make your alternative work in that environment.) Why is state data used everywhere? Because embedded systems often respond in different ways depending on the state of external inputs, and don't go read the state at the time they make the decision on how to respond. If those decisions are spread through the bulk of the code, there's really not much left to put in your combinator. And there's no point in trying to add that paradigm to a system that's going to have tons of shared mutable state anyway.