So your solution to changing 5 lines out of 7 was to do the refactor I wrote about and change 7 lines :)
I agree it's prettier. But it's objectively a larger change than the 3 lines you'd do in the imperative code. And it's pretty much how adapting to changes usually goes with FP. You constantly have to change the outermost structure of the program even if the change in the requirements is localized to one specific corner case.
> What if I want something that does the EXACT same thing as doABCD() but now without counting the amount of times something was squared and greater than 1000 but now instead I want it for the amount of times the total was greater than 3?
> You can't reconfigure the code. You have to duplicate the code now.
I could, but at this point refactoring is warranted.
y = 1;
y2 = 1;
count = 0;
_ = 0;
count2 = 0;
void doABCD(int &y, int &count, int &count2) {
y += 1;
y *= 2;
if (y > 1000)
count ++;
y *= y;
y -= 4;
if (y > 3)
count2 ++;
}
doABCD(y, count, _);
doABCD(y2, _, count2);
8 changes. 11 in total for both modifications.
In FP you had 7 lines of code changed for the first refactor
y = 1
a = (x) => x + 1
b = (x) => x \* 2
c = (x) => x \* x
d = (x) => x - 4
count = 0;
firstPart = a . b
secondPart = c . d
countIfGreaterThan1000= (x, prevCount) => x > 1000 ? prevCount + 1 : prevCount
n = firstPart(y)
newCount = countIfGreaterThan1000(n, count)
result = secondPart(n)
and now you'd have sth like
y = 1
y2 = 1
a = (x) => x + 1
b = (x) => x \* 2
c = (x) => x \* x
d = (x) => x - 4
count = 0
count2 = 0
firstPart = a . b
secondPart = c . d
countIfGreaterThan = (x, target, prevCount) => x > target ? prevCount + 1 : prevCount
n = firstPart(y)
newCount = countIfGreaterThan(n, 1000, count)
result = secondPart(n)
result2 = (firstPart . secondPart) (y2)
newCount2 = countIfGreaterThan(result2, 3, count2)
That's 7 + 6 = 13 lines for 2 changes if I'm counting correctly.
What FP buys you is not deduplication (you can do that in any paradigm) - it's easier understanding of the code.
Let me emphasize it’s not about prettier. Prettier doesn’t matter.
The key is that the original code is untouched. I don’t have to modify the original code. Anytime you modify your original code it means you initially designed poor primitives. It means you made a mistake in the beginning and you didn’t design your code in a modular way. It’s a design problem. You designed your code wrong in the beginning so when a new change is introduced you have to modify your design. This is literally a form of technical debt.
Do you see what Fp solves? I am not redesigning my code. I made the perfect abstraction from the beginning. The design was already perfect such that I don’t have to change anything about the original primitives. That is the benefit of Fp.
Nirvana in programming is to find the ultimate design scheme such that you never need to do redesigns. Your code becomes so modular that you are simply reconfiguring modules or adding modules as new requirements are introduced. Any time you redesign it means there was technical debt in your design. Your design was not flexible enough to account for changing requirements.
Stop looking at lines. In the real world if you modify your code that usually cascades into thousands of changes on dependent code. In Fp I simply link modules together in a different way. The core primitives remain the same. The original design is solid enough that I don’t change code. I just add new features to the design.
Also for your example you misinterpreted what I said. I don’t want to change the original signature of doABCD because it’s already used everywhere in the application. I want a new doABCD2 that does exactly the same as the original. Remove the side effect from the original and add a new side effect to the new doABCD of counting something else.
Do it without duplicating code or refactoring because duplicate code is technical debt and refactoring old code is admission that old code was not the right design. Be mindful that refactoring the signature means changing all the thousands of other code that depends on doABCD. I don’t want to do that. I want new features to be added to an already perfect design.
FP in my opinion, ironically is actually harder to read.
> You designed your code wrong in the beginning so when a new change is introduced you have to modify your design. This is literally a form of technical debt.
Yes. And just like in real life if you want to do business - you have to accept some degree of debt to get anywhere. Trying to predict the future and make the perfect design upfront is almost always a mistake.
> Stop looking at lines
We can't communicate without establishing some objective measures. Otherways we'll just spew contradictory statements at each other. These toy examples are bad, obviously, but the fact that there's basically no big functional programs speaks for itself.
> refactoring old code is admission that old code was not the right design
And that's perfectly fine.
> I want a new doABCD2 that does exactly the same as the original. Remove the side effect from the original and add a new side effect to the new doABCD of counting something else.
According to your definition of "code changed" if I duplicate everything and leave the old lines there - no code was changed which means the design was perfect :)
I don't think we'll get to a point where we agree about this. One last thing I'd like to know is why do you think nobody writes big projects in functional languages?
>Yes. And just like in real life if you want to do business - you have to accept some degree of debt to get anywhere. Trying to predict the future and make the perfect design upfront is almost always a mistake.
And I'm saying FP offers a way to avoid this type of debt all together. You can accept it if you want. I'm just telling you of a methodology that avoids debt: A perfect initial design that doesn't need refactoring.
>We can't communicate without establishing some objective measures. Otherways we'll just spew contradictory statements at each other. These toy examples are bad, obviously, but the fact that there's basically no big functional programs speaks for itself.
Sure then I'm saying lines of code is not an objective measure. Let's establish another objective measure that's more "good": The amount of lines of structural changes made to the original design. It's universally accepted that lines of code aren't really a good measure but it's one of the few quantitative numbers. So I offer a new metric. How many lines of the original design did you change? In mine: 0.
I don't want to write the psuedocode for it, but let's say doABCD() is called in 1000 different places as well. Then in the imperative code you have 1000 lines of changes thanks to a structural change. Structural design changes leads to exponential changes in the rest of the code hence this is a better metric.
That's an objective measure showing how FP is better. I didn't take any jumps into intuition here and I am sticking with your definition of an "objective measure"
>And that's perfectly fine.
That's just opinion. Surely you see the benefit of a perfect initial design such that code never needs refactoring. It happens so often in business that it's normal to refactor code. But I'm saying here's a way where you perfect your design in the beginning. That's the whole point of modularity right? It's an attempt to anticipate future changes and minimize refactoring and FP offers this in a way Objectively better than imperative. If your always changing the design when a new feature was added what's the point of writing modular and well designed code? Just make it work and forget about everything else because it's "okay" to redesign it.
>According to your definition of "code changed" if I duplicate everything and leave the old lines there - no code was changed which means the design was perfect :)
But then you introduced more technical debt. You duplicated logic. What if I want to change the "a" operation. Now I have to change it for both doABCD and doABCD2. Let's assume I have doABCD3 and 4 and 5 and 6 all the way to 20 who all use operation "a" and now they all have to be changed because they all used duplicate code.
Let's not be pedantic. Refactoring code is a sign of technical debt from the past. But also obviously duplicating code is also known to be technical debt.
>I don't think we'll get to a point where we agree about this.
Sure but under objective measures FP has better metrics. Opinion wise we may never agree, but objectively if we use more detailed and comprehensive rules for the metric, FP is better.
>One last thing I'd like to know is why do you think nobody writes big projects in functional languages?
Part of the reason is because of people with your mentality who don't understand. It's the same reason why the US doesn't use metric. Old cultural habits on top of lack of understanding.
I agree it's prettier. But it's objectively a larger change than the 3 lines you'd do in the imperative code. And it's pretty much how adapting to changes usually goes with FP. You constantly have to change the outermost structure of the program even if the change in the requirements is localized to one specific corner case.
> What if I want something that does the EXACT same thing as doABCD() but now without counting the amount of times something was squared and greater than 1000 but now instead I want it for the amount of times the total was greater than 3?
> You can't reconfigure the code. You have to duplicate the code now.
I could, but at this point refactoring is warranted.
8 changes. 11 in total for both modifications.In FP you had 7 lines of code changed for the first refactor
and now you'd have sth like That's 7 + 6 = 13 lines for 2 changes if I'm counting correctly.What FP buys you is not deduplication (you can do that in any paradigm) - it's easier understanding of the code.