1. Write a bunch of functional code.
2. Marvel at how clean and neat things are.
3. Get customer reports of performance problems.
4. Spend a lot of time ripping out FP components and replacing them with imperative code.
1. Write a bunch of functional code.
2. Marvel at how clean and neat things are.
3. Get customer reports of performance problems.
4. Spend a lot of time ripping out FP components and replacing them with imperative code.
An engineer uses what is available to produce an economically balanced solution. Done well, that may involve a few OO-ish bits, some functional bits, data-oriented bits, reactive bits, plus hybrid combinations of those and others. The problem dictates the form of the solution.
Every big-enough problem decomposes into a collection of very different subproblems, each of which so decomposes further. The right approach for the top level rarely matches that for most of its parts, nor each for the others, and likewise down the line.
Unless you want to use a different language for each level and subproblem, you will want support for all "orientations" in your language. Thus, a "functional" or "OO" language is an absurdity; likewise, a "functional", "OO", or what-have-you program. They make as much sense as a saw-oriented carpenter's toolbox or table, or a screwdriver-oriented machinist's toolkit or engine.
Programs should be like water, fluidly matching the shape of the problem solved. If that is 20% functional, 10% O-O, and the rest imperative, so be it. Specialization is for insects.
Everything is Turing complete: you can code a solution in any restricted vocabulary. But that means succeeding proves nothing and benefits nobody, but wasted your time and others'.
Use a language that does it all, and use it all. Not necessarily all on every program; but all styles get consideration for each part.
> The right approach for the top level rarely matches that for most of its parts, nor each for the others, and likewise down the line. Unless you want to use a different language for each level and subproblem, you will want support for all "orientations" in your language.
It is grossly impractical to use a different language for each fragmentary part of a problem. People do not, as a rule, do that. Trivial exceptions include Python and niche-language programs calling out to C-ABI libraries, programs sending SQL to databases, and web servers sending Javascript to browsers. But those are not about "orientation".
You might use two or more different general-purpose languages for different, independent problems, but that does not contradict my remark about the folly of languages that attempt "purity".
For example, consider GOTOs. If 99% of your functions don't use any GOTOs but they other 1% does, you can't be sure how the flow of logic lead to any one function. Immutability works in a similar way. I have a far easier time debugging in concurrent code I'm unfamiliar with if it's written in Elixir than if it's written in JS/TS, even if the project in question uses Immutable.js heavily.
How much of that comes from belief rather than reality, and potentially some room to grow on your end? I can debug well written code in any language I jump into, but I've found poorly written and hard to debug morasses can be written in any language.
Poor naming, branch/loop structuring, scoping, data modeling/normalization, module structuring, logging, and dependency selection/integration have all contributed to debugging hassles far more in my production experience than any particular language.
You know… suggesting that someone that they’re delusional and uninformed isn’t a very charitable way to discuss anything online.
The belief came from personal experience from 2016-2018 while I was much more experienced with JS/TS and relatively new to Elxir. It definitely influenced me to keep exploring and learning things outside the JS ecosystem I’d previously focused on. I’ve grown as a dev since and so has my appreciation of guaranteed immutability.
Also working with other languages, particularly Rust, in the past few years has only further underlined the point. It makes a strict delineation between variables, references and arguments that can be mutated and those that can’t. The language went with 100% guarantees instead of 95% guarantees for a good reason.
> I can debug well written code in any language I jump into
The phrase “well-written” may be doing a lot of lifting here. Different languages and ecosystems make different practices easy and tend to nudge users in different directions. People who don’t like learning often enjoy the idea that any Turing complete language is equivalent from the programmer’s perspective but it’s just not true.
I think there's no reason to exaggerate what I said just because I don't love your pet principle as much as you do. Here's what I explicitly describe as being part of well-written (or poorly written) code, which has very little to do with FP vs OOP vs procedural:
"Poor naming, branch/loop structuring, scoping, data modeling/normalization, module structuring, logging, and dependency selection/integration"
>The phrase “well-written” may be doing a lot of lifting here
I think if you read the entire comment, you'd see it doing a lot less lifting because I explicitly detail what "well-written" is. And I think it is cross-language.
https://en.m.wikipedia.org/wiki/Total_functional_programming
Quick, does this program terminate:
int main() {}
? Can't prove it?Add to it, and show that it still terminates, uses finite memory, introduces no data races or undefined behavior, and maybe computes a step toward something useful.
Repeat until done.
Everything else is syntactic sugar.
A language meant for use by professionals should get powerful features, without regard to any spurious notions of "purity".
It seems to me all useful programs are terminating (or, we truncate some non-terminating procedure by some convergence criteria). The question is only whether we have the logical tools to prove termination at compile time.
You seem to confuse algorithms, which by definition must terminate, with programs, which do whatever the hell you like (and sometimes things you don't).
> 4. Spend a lot of time ripping out FP components i'm less familiar with improving the performance of and replacing them with imperative code I have years of experience improving the performance of.
The cause of mist if these kinds of things will be dominated by familiarity for a long time.
Let’s be honest, most typical software written by the industry is not the like which has a single hot loop. They do a bunch of different things, usually with small number of entries. I don’t see how FP would be a hindrance to this typical workload.
Or use the functional code as a form of tests?
The most common issue is hidden O(n) situations, which don't show up except in production. So what we end up with is a mix of various paradigms worthy of Dr Frankenstein, and a debugging nightmare.
I've been in engineering long enough to see most trends repeat at least twice (Javascript UIs are ALMOST back to the point of the 90s for example). After awhile you learn to distrust anyone who evangelizses a "new" way that was actually invented in the 60s (and is likely being introduced as a panacea instead of with the original caveats).
Now that I'm a greybeard myself, I finally understand why greybeards were always so grumpy.
How are these faster in imperative code?
Imperative non-OO codebases become swamps of maybe (hopefully?) layered code, where each layer contains its own sinkholes and completely bizarre structure (or lack thereof). It's basically a bunch of mini-codebases that meet in an ad-hoc manner, making it very difficult to understand what the hell is going on.
OO codebases encapsulate a LOT better, but working around its straitjacket design becomes harder and harder as you go through rituals like dependency injection, interfaces, adapters, facades, class clusters, and byzantine inheritance hierarchies. Now, rather than a swamp of despair, you have pebbles of functionality. And while these were great when your codebase was 50k LOC, it's now a gravel pile where you can't figure out where anything starts or ends.
FP codebases like to push the details out of the way, making it easier to see at a high level what's going on. But as the codebase grows, you now have multiple iceberg problems, where the devils in the details begin to derail your code in production, and suddenly you find yourself needing to delve down rabbit hole after rabbit hole, chasing issues that weave and bob in utterly chaotic ways due to the lazy evaluation. Reasoning about what it will actually do in what time becomes more and more difficult (putting it in vulgar terms: You're basically exchanging topical complexity for temporal complexity).
TANSTAAFL
OTOH isn't that just - you need to know what the function you are calling is doing and how, and read the doc. Whereas in imperative you would implement it yourself, with the pitfalls of that.
If you are coding in Python and you are using a lot of numpy you need to know how to feed data in, how to get data out, etc. so maybe FP programming should treat the STD-lib more like an external library because the building blocks are a bit more high-level?