55 comments

[ 2.1 ms ] story [ 123 ms ] thread
>...techniques that students will have to unlearn when they encounter a pure language.

Sometimes it is much simpler to solve a problem by passing by reference (a var parameter in Pascal) than using the default of passing by value. Making the choice explicit helps those reading the code see this.

As for "pure" languages? Yikes, no language is perfect nor superior to all others, not even Rust.

Sometimes it is much simpler to solve a problem by passing by reference

Sometimes, but very rarely. I got a long way in Swift before I needed to use inout parameters, or even realised the language had them at all!

The article is specifically talking about teaching beginners; call-by-reference is a risky technique of limited value, and therefore it’s best to delay introducing it. If you introduce it early, students will get into the habit of using it too often, mostly in the wrong places.

Only very rarely because modern languages implicitly pass pointers to objects instead of objects themselves. I agree that at a CS 101 level it's a useless complication, though.
Pascal will pass the value of an object by default, you can use a VAR parameter to explicitly allow side effects, or optionally, you could pass the @address of the parameter for things where you want to play with pointers. Wirth was a pretty smart cookie when perfected the lessons from Algol into Pascal. 8)
Yeah, I never used Pascal (I cut my teeth on C), but it does seem like a nice language, especially for its time.

For something like Java or Python, though, it makes sense that the argument is implicitly a reference to the object it takes (at least as a default). 99% of the times you don't want to pass around stack-allocated stuff.

I started with BASIC, and never liked Pascal at all (I eventually took to C like a duck to water), but it must have something going for it as a systems language because the original Mac OS was largely written in Pascal (I’m not sure of the balance between Pascal and assembly).

I wonder if we’d have fewer memory-related bugs now if the industry had standardised on Pascal strings (length prefix) rather than C strings (null-terminated)?

"Pure" in PL lingo means "no side effects".

And from that pov there indeed are "perfect" languages as much as they can be "superior" to all others. Lambda calculus, for example, is perfect in the sense that it is the smallest, yet Turing-complete, pure language. You might of course start to argue about the evaluation strategy, but that's quickly done because there's only so much you can change ;).

But that's the academic point of view, of course. In that view, every language needs to be defined completely on a few sheets of paper. If you can't do that, or have to use handwaving, you don't have a language, you have a piece of software that approximates a language.

I do not believe that in a programming language there is any need to specify if a parameter is passed by value or by reference, and requiring the programmer to choose this (like in Pascal, C and derived languages) is a misfeature.

The right way to specify parameters appeared in the DoD IRONMAN specifications, which were first implemented by the Ada language.

There are 3 kinds of parameters, in, out and in-out, and this is what the programmer must specify.

Any of them may be passed by either value or reference, this is transparent for the programmer. E.g. when an in-out parameter is passed by value, it is copied to the parameter location (which might be a CPU register or registers, or a stack location) before calling the function and then it is copied back after return. For "in" by value there is only 1 copying, before function call, and for "out" by value there is only 1 copying, after function return. (In many cases the copyings can in fact be no-ops, e.g. for register variables.)

If implemented correctly, there is no observable difference depending on how the arguments are passed, except in timing.

The decision to pass by value or by reference must be taken by the compiler, because it depends strongly on the target CPU, i.e. on the relative costs of copying and indirect addressing. Even for 2 Intel/AMD CPUs from different generations the optimal decision can be different (i.e. the length threshold for the size of a structure/class to be passed by value or by reference can be different).

For external functions, how to pass the parameters should be determined by the ABI for that processor family, also taking into account the typical relative costs of copying and indirect addressing.

There might be some very seldom cases when the programmer could increase the speed by choosing how to pass the parameters instead of allowing the compiler or ABI to decide, but if indeed such a decision could have a measurable effect on performance, it is likely that inlining the function or rewriting that function in assembly language would have an even larger effect on performance.

Even in C/C++, the apparent control of the programmer on how the parameters are passed is just an illusion, because the performance is mainly determined by the ABI, which decides, depending on parameter size, whether the parameters specified as passed by value shall be passed on the stack or in registers and whether the return value shall be passed by value or by reference.

Therefore if a parameter is considered by the compiler as too large, specifying that it should be passed by value will have no effect on performance, because it will still be passed as a stack location and addressed indirectly.

The lack of 3-way parameter specification has caused a lot of unnecessary complications in C++, like the distinction between constructors and any other functions (which is needed because in C/C++ output parameters are assumed to be in-out, they cannot be specified as out) and the need for both move & copy constructors/assignments and also the many other tricks that are frequently needed to achieve move semantics.

> 3 kinds of parameters, in, out and in-out

.. or, in C#, (no specifier), "out", and "ref". However there's still a difference between plain types (copied) and objects (mutable).

You are missing "in", "ref structs", "readonly ref structs" and the Span variants.
In C++ return values have exactly the same semantics as your 'out' parameters, so I'm not sure what they would add.

Also pass-by-reference vs in-out has very visible semantic differences on the face of aliasing, exceptions and abnormal termination (when referring to shared memory).

After 2011, they kind-of have it, but not previously. For non-const parameters the compiler normally assumes that they are in-out.

Whether a parameter is out or in-out is very important for types with destructors.

Before calling a function, an "out" parameter is raw memory, on which a destructor should never be called. After the function returns, the "out" parameter contains a new object, possibly with allocated resources.

Before calling a function, an "in-out" parameter contains an existing object. If inside the function a new object is assigned to the parameter, the destructor must be invoked first, usually implicitly, inside an "operator=".

Before C++ 2011, most C++ compilers treated the return value as an in-out parameter, like every other non-const parameter, so they allocated a temporary and constructed a default object in the temporary, before calling the function.

One of the major changes in C++ 2011 was a collection of extra syntax features and library tricks to achieve the so-called move semantics, i.e. to avoid unnecessary constructions and destructions of temporaries, all of which had their origin in the lack of distinction between out and in-out parameters.

Aliasing of "out" or "in-out" function parameters is something that does not have defined behavior in any programming language.

When 2 aliased in-out parameters are passed by reference, the value after the function returns is unpredictable, because it depends on the order of interleaving of the changes to the 2 parameters inside the function.

When 2 aliased in-out parameters are passed by value, the value after the function returns is unpredictable, because the function receives 2 copies, whose values are correct immediately before return, but the final value seen after return depends on the order in which the 2 values are copied over the original aliased location, which is undefined.

Similar effects exist for exceptions and abnormal termination, the details may differ between pass-by-value and pass-by-reference, but the result is always undefined, so it does not matter.

While aliasing of "out" and "in-out" parameters cannot have defined behavior in any language, regardless of the method used for parameter passing, aliasing of "in" parameters is always OK, and here again it does not matter whether they are passed by value (when multiple copies are passed) or by reference (when multiple references to a single immutable object are passed), the behavior is indistinguishable in this case.

[regarding return values, I was mostly referring to RVO that bypasses moves, but close enough].

So you are saying that assignment through out parameters will act as placement new, by constructing a new object? That's interesting, although it would be a big change from the existing language semantics (assignment is very different from initialization at the moment) so it would be very hard to retrofit.

Does that means one wouldn't be able to pass an existing object as an out parameter and would need to pass raw memory? Or the caller is responsible of destroying the object first? That has implications for exception safety.

All this is done differently in C++, due to historical reasons determined by decisions taken between 1980 and 1984, even before the name C++ was adopted (in January 1984).

If objects with constructors and destructors had been introduced in a language with distinct "in", "out" and "in-out" parameters, everything could have done in a much simpler way than in C++ and without needing to introduce a lot of weird syntax and non-intuitive mechanisms.

In a language with distinct "out" parameters, yes the compiler ensures that the "out" parameters are raw memory, i.e. registers or free stack locations, or if the space previously occupied by temporaries is reused, the temporaries are destroyed before invoking the function.

Inside the function, the compiler ensures that any "out" parameters must be initialized somehow, e.g. by a "new", before any kind of access.

In a language with "out" parameters, assignment would be defined as having an "out" left parameter, so there would exist no copy constructor or move constructor and "operator =" would be what in C++ is named now as copy constructor.

The compiler would take care to destroy any left-hand operands of assignment before the assignment, when necessary.

The same for out parameters, if they have been declared but not initialized, they are raw memory so they are passed as they are. If they have already been initialized, the compiler ensures that they are implicitly destroyed.

All these rules are obvious enough, and they are much simpler than the very complex rules that you must take into account to understand what a C++ compiler does with temporaries and what happens differently when you add either "&" or "&&" to the type, and so on.

Some people believe that having to write 4 member functions, copy constructor, move constructor, copy assignment and move assignment, as it is mandatory in C++, is an opportunity to sometimes make a performance optimization, but I do not agree.

I believe that the case when a worthwhile optimization can be made in a copy assignment function (e.g. by reusing some allocated memory, which slightly bypasses the same reusing that would have been done in the memory allocator otherwise) are far to rare to justify the waste of time with extra function definitions for every class.

Initially C++ did not have a copy constructor, but only an "operator=", which mistakenly assumed a pre-existing object as its left-hand side.

Initializations by copy were done by the default constructor followed by "operator=".

This was inefficient, so the copy constructor was added, but the right solution would have been to redefine the semantics of "operator=", to match that what is now called copy constructor.

Then initializations would have been done with "operator=" and for assignments the compiler would have invoked implicitly the destructor. The programmer would have always written only 1 "operator=" function (and a simpler function, corresponding to the current copy constructor) and there never would have been any need for the other 3 functions.

This would have almost always resulted in the same performance as now, but with a far less burden on the programmer.

Agreed, that is something that I always disliked in Java.

We had so nice alternatives like Eiffel, Oberon descendents and Modula-2 descendents with some form of GC, value types, and reference parameters, and thanks to it, we had a tour of 20 years, where the .NET languages were the managed exception to that.

Finally this low level control is becoming mainstream, while Java tries to fix those design decisions.

Better later than never, I guess.

> I do not believe that in a programming language there is any need to specify if a parameter is passed by value or by reference, and requiring the programmer to choose this (like in Pascal, C and derived languages) is a misfeature.

I think direction is orthogonal to this.

... but i would like to be explicit in telling a function to not copy 2Gb of data on each call when a reference will do.

> As for "pure" languages? Yikes, no language is perfect nor superior to all others, not even Rust.

When they say "pure", they mean functional (no side effects, no mutations etc). They aren't meaning "pure" as in some kind of judgement...

i.e. a "pure function" is a function that always returns the same output for the same input and has no side effects (it's essentially just a map from input->output).

https://en.wikipedia.org/wiki/List_of_programming_languages_...

I hate statically-typed languages. They always give me a painful shock.
As opposed to dynamic languages that give that shock to people running your code.
Dynamic languages are dangerous toys...until someone straps a chastity belt on them.
To go lower philosophically programming is the art of providing ordered instructions that automate and which may or may not apply to a computer.
I don't think so. Declarative programming is all about describing what you want. The order of instructions required to accomplish this is, for lack of a better work, undefined. Simplest example is SQL.
I may be biased due to familiarity with imperative programming, but I think it is natural to think in a way that mutate states, as we live in a mutable world. I agree that functional programming might be easier to compose and reason about in a high level but it may be hard to teach beginners these abstractions, e.g., consider how many monad tutorials out there. Concepts that experienced users think are trivial may actually be confusing to beginners, especially if they are abstract...

I do agree that C++ is not a good language for beginners. I think a language similar to Python but with static type checking will be a good one.

It's natural to think in terms of "mutating the real world", but I don't think it's natural to introduce artificial mutable state at all.

You say "clean all those dishes", not "let x=0, then while x<N, clean the x-th dish from the top, then let x = x+1"

Ask someone without a scientific background to solve (a * b) + (c * d) with a (physical, non-software) calculator. The "correct" answer is to do something like: a * b, M+, c * d +, MR. What they will do is: solve a+b, write down the result, then do the rest. They don't think of a calculator as something stateful.

I'm not following here - if it is not a typo, why would you expect someone to solve a+b here?

But if we are using calculators, then depending on the make and model, you might do a b * c d * +, Which achieves the goal your second paragraph is aiming for: getting rid of (or at least abstracting away) any explicit use of incidental or intermediate state (which takes the form of memory operations in conventional calculators.)

RPN calculators (and programming languages), however, never became popular, and I strongly suspect that is because their expressions are often objectively harder for humans to comprehend, no matter how elegant RPN seems.

It's a typo, I meant a * b.

I agree on RPN, that was the point I was trying to make. RPN is more powerful, but is stateful and uses a rather complicated and unintuitive form of state (a stack). In my experience it's not true that people find it more intuitive to work with arbitrary mutable state of the kind imperative programming languages traditionally have.

The difficulty with using M+/MR is that the value does not remain visible or at hand. It's like the calculator loses its state. So people write the state down instead. This suggests they know it is stateful, they're just not familiar with managing it.
I don't think in your dishes example that `x` is introduced complexity. The mutable `x` is used here to reflect the mutable state of the physical world, so it'll come naturally to anyone capable of thinking symbolically and want to describe how to clean. If you did that functionally, the mutability would still need to be reflected somewhere, only now it might be implicitly in the runtime.
You can apply some level of pure function in whatever langues, and it not that hard and make thing easy to test and think about it

Monad is like highly effective and complicate patch to handle thing who couldn't be pure function. i don't like Haskell because of that is not natural force that much the langues for me(opinion), but is like try to tech Rust as first langues you don't have the grasp in the limitations to understand why ownership matters, and why all the langues need to be kind a weird, is still weird but you understand the advantage and grasp at least the syntax and basic patters make it more pleasure to learn it.

Monads are pure expressions/patterns. It's only impure and have side effects in the final evaluation, for a few monads like the IO Monad. It's just using monadic expressions for tricking side effects into the language in a cleaner way, thus considered beneficial.

Haskell is hard for programmers, because it forces them to truly think. Haskell grows on you when you get to know how it evaluates and forces you to separate concerns structurally. For that reason, it's said to be a language that teaches you how to program better.

Well, we used to teach it to first year university students back in the 1990's pre-standard.

C was already taken out of curriculum (students were expected to learn it from the C++ learnings), and we made use of our own collection classes with bounds checking.

The secret was teaching the language on its own, and not as C with extras.

Let me ask you a question: is using numbers natural?

Apparently it is not, because otherwise it wouldn't have taken so long for the concept to spread, and folks like the romans would have used 0 from the beginning.

But just because something is not natural does not mean it is not more productive than the alternative solutions, given that one spends the time to learn it.

So, maybe mutating state is natural - but I would object the claim that this means it is more productive.

To be pedantic, the world is not mutable, rather, it is a process undergoing a continuous transformation.

Also, machines deal with data - which is encoded information about the world.

So when we say that a thing 'changes' in the world, what we're actually saying is that at time T, something happens to the world and from then on, the data we read back is different.

'Something happened' is new information which is added on top of the state of the world at time T-1.

Eg. the user e-mail does not 'change' to be come X, but rather, it used to be Y, then the user changed it to X on 1st of May.

So we can generalize that the world is a function `f` of some state combined with extra data of what happened at time T, giving us the state at T.

The Grand Unified Programming Theory: The Pure Function Pipeline Data Flow with Principle-based Warehouse/Workshop Model

My theory based on the simple, classic, vivid, and widely used in social production practice, elementary school mathematics "water input/output of the pool" as a mathematical prototype, Therefore, it is scientific.

The "von Neumann architecture" used by computers now has no mathematical model support. So it cannot prove its scientificity.

My theory rebuilt the theoretical foundation of the IT industry, The IT industry is fundamentally associated with mathematics. Solved the most fundamental and core major problems in the IT industry.

Therefore, my "warehouse/workshop model" will surely replace the "von Neumann architecture" and become the first architecture in the computer field, and it is the first architecture to achieve a unified software and hardware.

Software and hardware are factories that manufacture data, so they have the same "warehouse/workshop model" and management methods as the manufacturing industry.

In the IT field, only it and binary system fully comply with these 5 aesthetics: Simplicity, Unity, order, symmetry and definiteness.

It has a wide range of applications, from SOC to supercomputer, from software to hardware, from stand-alone to network, from application layer to system layer, from single thread to distributed, from general programming to explainable AI, from manufacturing industry to IT industry, from energy to finance, from the missile's "Fire-and-Forget" technology to Boeing aircraft pulse production line technology, from myth to Transformers.

Link: The Grand Unified Programming Theory: The Pure Function Pipeline Data Flow with Principle-based Warehouse/Workshop Model

https://github.com/linpengcheng/PurefunctionPipelineDataflow

This chap sounds like an outstanding functional programmer.

I am not a functional programmer. I'm also not an imperative programmer. I'm more of a "craftsman," and I use a lot of tools (including the dreaded OO) to write shipping software that ends up in the ungentle hands of The Great Unwashed. I've been doing that my entire adult life, and I'm 59, so that's been a minute. I've outlasted most of the tools and fads that have come and gone, during my time, and I've learned not to let the tools dictate the craft.

It's a bit discouraging to see how some people look down on folks like me, but I really like getting my hands dirty, and making stuff that people use.

In the book named Binding Time, Mark Halpern wrote of Backus's Turing Award lecture ("Can Programming Be Liberated From the Von Neumann Style")that Backus seemed to be embarrassed that a computer worked by changing its internal states.
If computations are not instantaneous then how else would it work? I thought his thesis was rather the same as this blog post- that programs should be composable mathematical expressions rather than bit-twiddling word-shoveling statements- but it's been forever since I read that and I probably didn't appreciate it at the time.
We should yell the computer what we want, and let the computer figure it out. This is the promise of declarative languages
Why do you feel that OP is looking down on you? That's not at all what I took away from his post.
Well, it describes people like GP as "writing a recipe for a horrendously unimaginative cook." It's telling them that they're writing clunky, minutia-obsessed instructions, instead of elegantly orchestrating a collection of mathematical functions.
Not me, specifically, although it does, occasionally, get personal.

It’s basic human nature. We always reinforce that the way we do things (in this case, FP), is The One, True Way, and all who do otherwise are to be pitied/scorned.

I’m a real slob. I mix my peanut butter and chocolate all the time. I may use 35-year-old techniques, and mix them with just-published-brand-spanking-new stuff.

I get stuff done. It may be a sausage-and-laws thing, but the end results seem to make people fairly happy.

A great deal of the time, I’m constrained by the context of my work. For example, I write software for Apple devices, and am still one of the poor, unenlightened that writes using UIKit. I haven’t written my first shipping product with SwiftUI.

If we write in UIKit, we can use different patterns, but UIKit was designed to be implemented, using classic MVC, and lots of OO. Attempting to circumvent these design considerations is a “kludge,” at best. If we want to write good, performant, maintainable software for Apple devices, we need to hold our noses, and use the tool the way it was designed.

Apple is refining SwiftUI, which I’m really looking forward to learning and shipping, but I am not there, yet, personally.

I assume that SwiftUI will also end up using a pattern that will become “obsolete,” even while SwiftUI is still the dominant development tool, much as is the case with UIKit.

Also, SV (or it could just be this community, specifically) seems to be focused on “going big.” I think there was a post, a day or two ago, entitled something like “I don't know how to count that low,” or whatnot.

The work I do is “small.” It may actually be fairly intense, “under the hood,” but I write stuff for small demographics, and spend a great deal of time, “polishing the fenders.” This is doing things like ensuring a smooth and intuitive UI workflow, presenting data in as simple and attractive a manner as possible, affording localization and accessibility, etc.

These are all about the individual end-user. They are not “big picture” things, and I have limited tools and venues at my disposal. I need to be practical, and make compromises and concessions to achieve my goals.

I have nothing but awe for some of the technology and technologists out there. It’s a great time to be alive. Cool stuff is happening.

It’s just that a lot of it doesn’t really reach me, down in the trenches, and it’s discouraging to see the mindset that what folks like me do, “doesn’t matter.”

> We always reinforce that the way we do things (in this case, FP), is The One, True Way

Just curious... have you played with functional languages before? It may feel like snobbery to you, but once you start thinking functionaly, it really does feel like you're painting a house with rollers and a spray gun rather than a wet noodle.

How many Apple apps have you delivered, and how many of those were written in a functional language (Swift can act as a functional language, but it is not a "pure" one.)?

Seriously. We all have our specialties. It is, indeed, "snobbery," when we rubbish the experience and expertise of folks that choose different paths. I won't rubbish some of the truly marvelous stuff I see, coming from people that think what I do is "quaint." It would be nice if the favor of respect were returned.

Delivering ship software is quite valuable; especially at the Quality and Usability bar that I set for myself. I suspect that you may not be aware of the Discipline, creativity, patience, and just plain old-fashioned Hard Work that goes into moving a piece of software out of the "toy" arena, and into the "ship" arena. The difference in project lifecycle times is quite sobering.

I can write a fairly full-featured app, in a couple of days, but it takes months to make one that I consider "ship-worthy," and I'm really at the top of my game, in my sandbox.

I have been delivering ship software for my entire adult life. I know that it isn't world-changing, prize-winning, theoretical marvels, but it has had direct and meaningful impact on thousands of lives.

I really appreciate it when the work of theorists makes it to my level. The Swift language is an object example. I really like it, and I know that one of the big reasons is because they cribbed from the FP workbook.

I think you're taking my comment too personally. I'm not attacking what you're saying. I completely understand where you're coming from, and that's from a Perl developer of 13 years.

But what I'm trying to say is that this isn't about quantity shipped or how many users you have made happy - what I'm trying to convey is a measure of programming artistry and helping you see and maybe appreciate it.

Maybe a better analogy - I understand you like Rock music and that it's the only thing you play in your car, you grew up with it and it's all your friends ever talk about... but maybe if you looked at Jazz, you may be able to enjoy it as well.

... sorry if it came out as condescending, that wasn't my intention.

Yah, you’re right. I am sensitive. Comes from being primarily self-taught, and spending a lifetime, staring up noses.

I also have problems with “purism,” when it comes to tools and techniques. Purism, by definition, eschews orthogonal viewpoints, and I feel that’s quite destructive. I also feel that it is anti-intellectual. One thing I’ve learned, over the years, is that I was definitely right, yesterday, but I’m definitely wrong, today. Happens regularly, and that can get tiresome (but it’s unavoidable -part of the price I pay, for a career in tech).

I really like the ideas behind FP. It’s just that it is not a practical tool, for me to use in anything close to pure form, in the drive towards realizing my goals. I do not have the luxury of purism. I’ve always had to play in someone else’s sandbox, and by their rules. Believe it or not, that can be quite exhilarating.

A house framer uses drills, saws, and nailguns. They can’t make these tools, but they use them, to do what the people that designed those tools, cannot do.

> C++ is a pedagogically worthless language. It bogs a budding student down with historical baggage like header files and cryptic imports whilst drowning said student in the complexities of an abuse and archaic syntax, with nothing but an error message that’s as clear and useful as a lead-filled balloon used as a flotation device. It’s impossible to get a pleasing, mathematically-sound model of your domain in C++. Heck, you can’t even model something in an OO way whilst following the literature on that. Almost all effort is consumed in attempting to appease a persnickety compiler.

You may not learn "programming" this way, but all of these are going to be useful skills when you work as a programmer.

> Since getting the syntax and the ceremony right is so much of C++, it turns into a guess-and-check game, where the student keeps tweaking things until it works. This is not the way. You don’t learn anything about why things are the way they are. This is similar to one argument I’ve heard about how Java suffers from a similar problem. We shouldn’t be teaching students how to solve programs in a given language. Rather, we should be giving them tools to think about the problems they face and how to solve them.

And here's the part where you learn to deal with office politics.

I guess my point is that I'm a bit skeptical about "good learning techniques" when there isn't any data to back it up and just the reasoning of the author. All of this "feels" true to me, in a way that "let the students figure out C++" doesn't, but I don't know if this theory is true.

> You may not learn "programming" this way, but all of these are going to be useful skills when you work as a programmer.

It seems my life as a programmer is fighting with tools, fixing bugs in legacy code in a myriad of languages (including C++), reviewing code, resolving merge conflicts, configuring CI/CD, writing tests, benchmarking, monitoring... And it's not like we can choose the programming languages we want anyway.

Learning different paradigms and languages is all good, but it's really a tiny part of our job.

Imperative ≠ low level. That’s the fundamental issue with this essay.

>One way of thinking about programming is that you are ordering a computer to do your bidding: you, the programmer, sit at the helm of your CPU, afloat on a sea of data, and you have various levers and knobs that you can pull and twist to make the CPU get from point A to point B: load this value into memory slot i. Now add five to it. Now print that back out. Etc. This is called imperative programming, because you tell the computer every step it should take.

I can tell the computer complex, high-level things too, not just this pseudo assembly.

It is a nontrivial fact that both these ways of modeling computation are valid.
functional programming paradigm is boringly honest, you have functions chained together, and you can see exactly how your inputs get transfromed.

You use wire instead of radio wave to design your world.

People with high skill and experience levels often are not well-suited to designing introductory courses for beginners in their field, and this seems to be an example:

> "My hope is that programming courses in higher-education settings (and high school settings!) will move away from imperative and even object-oriented programming towards a more functional approach."

It reminds me of past debates over how to teach mathematics - maybe start with set logic and group theory instead of addition, subtraction, multiplication, division? Clearly set logic and group theory underlies those subjects so the students should start there...

It's a bit over the top. Should welders have to be applied physicists? Some knowledge of arc plasma might be helpful, but at the introductory level, certainly not. If you want to devise a new method for welding titanium or something, well maybe you need that level of knowledge.

I also tend to turn off when people talk about how much they love or hate this or that language. Languages are just tools, and selecting the right tool for the job is what experience teaches you - although a new tool might be better for a certain task, so experience alone is not enough. Also, I started programming learning C++, it worked out well enough, even if the first six months were a struggle. From there you can easily look at assembly or look at languages without direct memory access via pointers, and you've at least got a grasp of why memory management is important. I feel lucky for having started this way, vs. starting with a higher-level abstraction language like Python, JS, etc. I can't even imagine starting with a pure functional language like Haskell, that would have been pretty hopeless for a beginner without any experience with recursion.

As far as object-oriented vs. functional, hybrid approaches seem useful, for example classes with methods that avoid side effects, etc.

I often wonder if there could be some minimized instance of both practical, engaging exercises that are also aiming at abstracting principles (like teaching numbers not only with tables but also trying to break them down into bases, showing different gearing mechanisms to count in base 2,3,5,9 .. just to open the mind)
Reminds me of another paradox. Often the people who struggle the most with Haskell, are those with extensive background in programming in non-functional languages. Those without prior experience may grasp the concepts faster and better, while also being less confused. This is incidentally why someone would recommend learning Haskell first, because after that you really can learn anything, but maybe won't have to.

I personally tend to err on the right tool for the job, or just avoiding programming when not strictly necessary.

I wish we could get away from OOP. "Be careful what you wish for" dances around in my head as I think about how badly literally everyone misinterpreted OOP, which ended up creating the OOP most of us deal with on a day-to-day basis. Whatever the initial understanding of any new paradigm, this will influence the implementation across industry as the inventors watch with their mouthes hanging open at the absurdity.