Hacker News new | ask | show | jobs
by uuvs8 1266 days ago
And suddenly you care a lot about the "how", not just the "what". Voiding the whole "but it's declarative!" argument.

At least in imperative code the "how" is explicit. In functional code it's implicit and you need intimate knowledge about compiler and/or runtime to know what's going to happen.

2 comments

I kind of agree that it's possible to slightly overstate the declarativeness argument. Functional-style JavaScript is not that declarative. Not in the way SQL is.

But also, writing imperative code doesn't guarantee explicit performance characteristics. Whether you mutate references or not, you still need to know which operations are fast and which are slow.

In JavaScript, concatenation, [...array1, ...array2], is non-mutating and slow. Adding an element to the end, array.push(x), is mutating and fast. But adding an element to the beginning, array.unshift(x), is mutating and slow. So even if you're sticking to mutation, you still need to know that push is fast and unshift is slow.

And yeah, sorry, "in JavaScript" is not quite right. I meant in my browser. This is not part of the spec, and it's not mentioned in the documentation. Is it the same in other browsers? Who knows. To me, this is just as much "you need intimate knowledge about compiler and/or runtime to know what's going to happen".

> array.unshift(x), is mutating and slow

This year it's slow. I wouldn't count on that being true in five years. I mean, it might, it might not. There's a risk in optimizing too much for current conditions. You can easily end up over-fitting for the current set of coincidences, and end up with less readable code that's actually slower in the long run.

But by all means, measure and improve until it's fast enough.

You never know what the compiler is going to produce until you look at what it actually produced, whatever language you are using.

Unless there is clear evidences upfront that the project will be a piece of software where local performance is highly critical, it makes sense to favor code readability and maintainability over optimality.

Of course, you can have different level of code quality whatever the paradigm retained. Most languages out there will allow you to mix different paradigms anyway.

Given this fact, we would surely better served with an article like "When to favor expression in each paradigm available in your favorite language".

> Unless there is clear evidences upfront that the project will be a piece of software where local performance is highly critical, it makes sense to favor code readability and maintainability over optimality.

Sure, but let's also not confuse "optimal" with "reasonable". One of the major challenge of modern programs is how slow they are at every level. Very often, this bad performance can be attributed to a style of programming that tries to completely ignore that the program will run on a real machine with real hardware. A little bit of mechanical sympathy (e.g., operations that make good use of the CPU caches or don't confuse the branch predictor) can yield a program that is 10x faster than a naive implementation, with little to no loss of readability or maintainability. (In fact, as noted by Nelson Elhage [1], faster programs enable simpler architectures, which helps make them more readable and maintainable.)

In FP languages, programmers face an extra difficulty: the distance between the code they write and the machine code that will be executed is greater than in languages like Rust or Go. They will need to be knowledgeable about the language, its libraries, and its compiler to avoid the pitfalls that could make their programs slower than would be reasonable (e.g., avoiding unnecessary thunking in Haskell).

[1] https://blog.nelhage.com/post/reflections-on-performance/#pe...

> the distance between the code they write and the machine code that will be executed is greater than in languages like Rust or Go.

First of all, one of those languages is not like the other (Go is closer to JS than to Rust) - second, we really can’t reasonably guess at the produced assembly, even C compilers do some insane transformations leaving the code nothing alike the original, let alone more expressive languages.

I completely agree. That functional style actually favors readability and maintainability is a quite strong claim which I read often but it's usually lacking evidence.

In my experience, software engineers "think" imperatively. First do this, then do that. That's what we do in everyday life (open a random cooking book..) and that's also what the CPU does, modulo some out-of-order and pipelining tricks. A declarative style adds some extra cognitive load upfront. With training you may get oblivious to that, but in the end of the day, the machine does one thing after the other, and the software engineer wants to make it do that. So, either you express that more "directly" in an imperative style, or try to come up with a declarative style which may or may not be more elegant, but that this ends up more readable or maintainable is on the functional proponents to prove.

Maybe we have different mental models and that’s what drives this conflict? I certainly wouldn’t say that first do this then do that is my primary mental model, in small blocks yes, but once you get past even 1 file that breaks down. Once you introduce threads or the network these assumptions have to go out the window anyways.

It’s funny you mention recipes, because i’ve always been frustrated by traditional recipe descriptions that muddle concurrency and make it difficult to conceptualize the whole process. E.g. the table structure here is superior to step by step http://www.cookingforengineers.com/recipe/158/Dark-Chocolate...

The network is the prime example for forcing serialization of events.

Tbf, I agree with the recipe criticism. Would be neat with a dependency graph instead of a step-by-step list of things to do when baking a cake. Would have saved me a lot of headache in the past. (The table in your link expresses a tree, which is probably sufficient for most purposes.)

> In my experience, software engineers "think" imperatively

I hear this often. In the past the claim used to be that they "think" object-oriented. This is a thinly veiled argumentum ad naturam.

> ... on the functional proponents to prove

Prove your own claims before you demand proofs from other people. And by prove I mean really rigorous thinking, not just superficially seeking confirmation for the things you already believe either way.

Not necessarily. If the current "default" is imperative, then the burden of proof is on the functional advocates, because they're the ones advocating for change.
Burden of proof is stupid if the only argument for the other is being the status quo.
Burden of proof is very relevant if neither side gave an argument.

People are doing A. Someone says "Do B instead!". "Why should we do B?" "Well, why should you do A?" At the end of that extremely unproductive exchange, what are people going to do, A or B? They're going to keep doing A, because they were already doing that, and nobody gave them any actual reason to change.

So "burden of proof" isn't meant in the sense of this being a formal debate, with rules. It means that, when ahf8Aithaex7Nai said "Prove your own claims before you demand proofs from other people", that ahf8Aithaex7Nai is wrong. OOP is the current default in terms of the bulk of professional programming; if FP advocates want that to change, it's on the FP advocates to provide reasons, not on the OOP advocates to prove the correctness of the status quo.

A: This thing! It's true!

B: Prove it!

A: No, you prove first! With really rigorous thinking, please!

Bad faith
I mean, OOP is still a very great model for plenty of programs.
In my experience, people think and explain their ideas in natural language and sometimes pictures. So that is the best way to program computers.
Imperative is good (perhaps even better) on a local, small scope.

It is terrible on a system level with concurrent execution, there you really need all the safe guards.

As weird as it sounds, but even when performance is not a deal breaker for individual users, on high-traffic sites the sum of all energy wasted on unnecessary/unoptimized computations can become so large that it becomes an actual ecological concern. It's a completely new thing that we should keep an eye on.
Is it really when we literally waste countries’ energy usage on bitcoin and such? Computers are surprisingly low in energy consumption on the grand scale of things, especially compared to their usefulness.
Really? Only one application of computers --- Bitcoin --- wastes countries' energy usage and yet it doesn't make sense to optimize software for ecologic reasons?
No, but there are plenty much higher targets before going after computers, especially compared to the insane value they produce. Like, is that single server running all the logistics of a country’s train network “inefficiently” really significant compared to.. the inefficiency of even a single train?
What about those billions of devices running software in gadgets, phones, TVs, routers, dishwashers, etc ?
Crypto uses vast energy as a feature, a side effect of maintaining a competitive environment as part of proof of work (other staking mechanisms are different). You could not optimize this to use less energy.