3 ms·
This is a common misconception, functional code is order independent in its evaluation model, but if you are modeling order dependent operations, it still lets
by didibus 5y ago
This is a common misconception, functional code is order independent in its evaluation model, but if you are modeling order dependent operations, it still lets you specify order for them, and if you specify the wrong order for what you're doing it'll do the wrong thing.
So you can think of it as it solves accidental complexity, but leaves essential complexity still for you to solve.
A very simple example is:
30 - 2 * 10
This is order dependent, but it's part of the essential complexity of the problem at hand.
Now where accidental complexity would creep in is with something like:
position - 2 * position + 10
Now first we have the essential complexity, what order is the behavior we want? Let's say here we want:
(position - 2) * (position + 10)
In imperative we might do:
position = 10
position.minus(2)
position.plus(10)
position.times(position.value)
And hopefully you're already seeing the accidental complexity created by the imperative approach. This doesn't work, and makes no sense. It isn't just that the order of the operations we're modeling is defined wrongly, in fact you could say it's defined correctly, we want to substract, add and then multiply. But in this case it's the mutable state itself which makes things more complex then they need to be.
So in imperative you'd have to do:
position = 10
firstPosition = position.value
secondPosition = position.value
firstPosition.minus(2)
secondPosition.plus(10)
position = firstPosition.times(secondPosition)
position.value
That's a lot of added complexity due to us having to manage the fact that the state is mutable. We need three memory locations, we need to make sure that we copy the state values at the right times and we need to make sure we are mutating the right memory locations and than adding them back and all that ourselves.
In functional you'd just do:
position = 10
times(minus(position, 2)
plus(position, 10))
All you need to model is just the essential ordering inherent to the behavior you want.
Not surprisingly, most modern programming language actually use pure functions for their math operations. That's why in Java:
int position = 10;
(position - 2) * (position + 10)
will work, because minus, plus and multiply are implemented as pure functions, even in Java. That said, if you use the imperative ones instead it won't work:
int position = 10;
(position -= 2) * (position += 10)
or if you went all imperative you'd have what I showed before:
int position = 10;
position -= 2;
position += 10;
position *= position;
Now using math operations I think shows very clearly the accidental complexity that imperative has over functional in this case. What happens is all your other operations that are not math, but related to your business logic or program logic that you also model using the imperative style suffers from this added complexity which is removed if you move to the functional style.
Now back to the article, they're expecting functional programming to somehow know the behavior they want, and magically figure out the essential complexity of it. It won't give you that, but it will get rid of a large amount of accidental complexity, and that was the point of the "No Silver Bullet" paper, that since the amount of essential complexity is fixed and inherent to your problem, only accidental complexity can be simplified when implementing said problem. Functional programming argues to dramatically simplify your accidental complexity, letting you focus all your attention on the essential parts.
Having said that, there are things that tries to tame essential complexity as well and functional programming tends to mix with them very simply and easily. For example, the ideas around declarative programming, metaprogramming, interactive programming, static code analysis, and testable code all touch on the essential complexity parts of a problem.
If you have more independently reusable pieces, that you can simply declare compositions of, or rules around them, it allows you to tackle some aspects of essential complexity. Metaprogramming lets you abstract things behind code-gen so you can more quickly reuse parts of each essential features that is the same. Interactive programming (like a REPL in Lisps or smalltalk systems) will let you have a quicker feedback loop to evaluate the effect of your essential problem and validate if it's correct letting you more quickly implement the essential complexity. Static code analysis can validate assertions of your essential complexity and quickly tell you if you might have made a mistake in it, it can also help you see a clearer mental map of how the essential complexity is modeled which can help you understand it better. Finally testable code, which functional code often is by virtue of its style, similarly lets you assess the validity of your essential behavior, and thus you can more quickly iterate over your essential complexity.
- z3t4 5y agoThrow in in scale, dot, angle etc and the functional style would be hard to reason about for me, I think your imperative example is more explicit and you can sprinkle a bunch of console log's between the lines. I think pure functional programming works best when there is no state. So move the state out from the program. Only use local buffers if you need for performance... The order of things is very important so make your coding life easier by making sure the are no concurrent state - like TCP for networking and single threaded business logic. For example in a chat app, buffer text in a textarea, but when a user hit "send" don't render the message before it has been received by the server, that way everyone connected to the server will see the messages in the same order. The more possible state a program can have the more you will benefit from testing.
- didibus 5y agoYou find this: position = 10 firstPosition = position.value secondPosition = position.value firstPosition.minus(2) secondPosition.plus(10) position = firstPosition.times(secondPosition) position.value easier to reason about then this: position = 10 times(minus(position, 2) plus(position, 10)) ? Are you sure? I think maybe you're just conflating syntax and semantics. Syntax can be made in different ways either functional or imperative, the semantics are what matters here. Like would this be better for you (still functional, but different syntax): position = 10 firstPosition = position.minus(2) secondPosition = position.plus(10) result = firstPosition.times(secondPosition)