I just did some PHP the other day and I stumbled on a structure I was happy to have forgotten:
something(x, result);
somethingElse(result, result2);
anotherThing(result2, result3);
with the occasional surprise mutation when you just have to do
something(x)
and x contains the result.
I also got caught recently with MomentJS with that one when doing mydate.add and discovering it also mutated the original instead of just returning the result.
I'm a very average programmer and far from a FP purist (I mostly use JS and Rails) and I'm surprised how much I now use some FP principles and how it feels very natural to me.
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 `x.something()` without needing any special annotations like C# requires.
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 <- item.Score
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.
That's worse because now you have to find the innermost function and then walk back outwards. It gets especially ugly when the calls require other parameters
let x = anotherThing(
somethingElse(
something(
x, y, z
), 1, 2, 3
), "a", "b", "c"
);
Or maybe this looks cleaner?
let x = anotherThing(
somethingElse(
something(x, y, z),
1, 2, 3
),
"a", "b", "c"
);
In ML languages, code forms a beautiful tree structure where you can see the binding definition of each binding by looking down and right.
whereas in a language built around statements it will be scattered all over: In the first case the definition of x is in one indented block; you can read it and move on.