524 comments

[ 4.7 ms ] story [ 285 ms ] thread
It's probably just me, but I found loops much more intuitive than recursions for those non-recursion oriented problems. That is to say that I prefer to use recursion ONLY for those are un-intuitive to solve by loops in a trivial way.
Generally when you learn recursion you also learn about higher order functions. Most of the use cases of loops get replaced with map/filter/fold. What’s left are typically those “unintuitive” problems that need recursion, such as when you’re dealing with trees.
>What’s left are typically those “unintuitive” problems that need recursion, such as when you’re dealing with trees.

I always prefer to walk thru trees with e.g stack/queue because it's easier to reason about / debug it for me

(comment deleted)
True enough but worth noting that a good tree should also have map/filter/fold-like operations.
I think recursion and loops are hard to read. I rarely even use either anymore.
Loops are intuitive and easy to understand, because they are concrete. They can also be cluttered and hard to read due to all the required bookkeeping.

For simple loops, I prefer something like map/filter/fold when appropriate. I try to avoid chaining them, because that often turns the code impossible to understand and makes me feel stupid. If the loop can't be modeled naturally as a higher-order function, an iterator-based loop is easier to read than an index-based loop, which is better than tail recursion.

With complex loops, there is no way around concrete loops with comments describing the invariants and what each part of the iteration does.

Many problems can be modeled naturally with recursion. Such problems are particularly common with graphs. However, you should never use explicit language-level recursion unless you can guarantee that recursion depth will be small. It's easy to get a stack overflow, so recursion should usually mean a loop with an explicit stack of states.

Loops are nice, but after having written enough Erlang, you get used to using foldl and map when they make sense, and recursing yourself when you need to. I can imagine starting with Erlang and learning about loops later and being weirded out.

I've grown to sometimes enjoy how a recursive loop looks in code. Sometimes you put the end case first, sometimes you put the end case last, depending on which makes most sense while writing (or perhaps while editing while reading later).

With languages with loops and no pattern matching, I'll always write a loop as a loop, if I used one with both, I dunno, there's probably a few loops I'd do as recursion and most would be loops.

OTOH, Erlang is certainly functional, but it's very pragmatic as well, there's not a mention of monads, and one tends not to do a whole lot of weird stuff with closures (although you can). Sure, there's a good number of functions that take functions as arguements (which you can do in C anyway), and there are anonymous functions, but you get better error reporting if you use functions with names, so at least I tend to name anything with more than a few lines. I also tend to have very short functions, but that is somewhat influenced by stack traces only having function names and not line numbers when I started 10 years ago.

Recursion is great for recursive structures, like trees. When you say "a binary tree is either a leaf, or a node which is a value and two binary trees", it's really easy to imagine how you will use a recursive function on it.
I like functional code, except that because the use of lambdas is awkward to "escape" them:

    nums.iter().map(|x| if x > 10 break(?))
This little thing is the major problem of functional chains (this is only a toy version of the issue). Because lambdas are inside the scope is hard to make them truly part of the current execution flow.

So, sometimes I change a chain of iterators to simpler loops for things like this. IF iterators where part of current scope, it will look far more natural, IMHO...

nums.iter().take_while(|x| x <= 10)
Yeah, I'm aware of the other options, just pointing that the use of lambdas break the flow.
Rust just standardized `ControlFlow` which would let you pass equivalents of `break` and `continue` as values.

    x.try_for_each(|y| {...; ControlFlow::Break} )
Having written Haskell for a few years, the concepts are kind of interchangeable in my mind. I think of recursion as being basically a loop, and a loop as basically the same thing as recursion. To the point where it seems natural to me to name a recursive helper function `loop`. Obviously they're not exactly the same, but with familiarity they can be translated trivially.

I think it's really just a matter of prolonged exposure to make recursion seem intuitive.

Functional Programming Is Not-Popular Because It-Is-Weird,

not

Functional Programming Is Not Popular-Because-It-Is-Weird.

I just found the ambiguity amusing, in the title of this particular blog post.

Yeah, I thought the author might mean "some of you may be under the impression that the primary appeal of functional programming is it's weirdness. However, that is not the case." The easiest and clearest way to resolve it would be to say "unpopular" rather than "not popular", although arguably they don't mean exactly the same thing.
can someone explain me the ambiguity, I don't get it
Two ways to interpret it

1. FP isn't popular; this is because it's weird.

2. FP is popular; this isn't due to its weirdness, it's for some other reason.

The English language is popular-yet-weird.

I find it’s not the most straightforward langauge for expressing precise thoughts and logic…

“ The Dog Walking Ordinance *

The following transcript of a Borough Council meeting in England illustrates the difficulties of expressing a simple idea in precise and unambiguous language.

From the Minutes of a Borough Council Meeting:

Councillor Trafford took exception to the proposed notice at the entrance of South Park: "No dogs must be brought to this Park except on a lead." He pointed out that this order would not prevent an owner from releasing his pets, or pet, from a lead when once safely inside the Park. The Chairman (Colonel Vine): What alternative wording would you propose, Councillor? Councillor Trafford: "Dogs are not allowed in this Park without leads." Councillor Hogg: Mr. Chairman, I object. The order should be addressed to the owners, not to the dogs. Councillor Trafford: That is a nice point. Very well then: "Owners of dogs are not allowed in this Park unless they keep them on leads." Councillor Hogg: Mr. Chairman, I object. Strictly speaking, this would prevent me as a dog-owner from leaving my dog in the back-garden at home and walking with Mrs. Hogg across the Park. Councillor Trafford: Mr. Chairman, I suggest that our legalistic friend be asked to redraft the notice himself. Councillor Hogg: Mr. Chairman, since Councillor Trafford finds it so difficult to improve on my original wording, I accept. "Nobody without his dog on a lead is allowed in this Park." Councillor Trafford: Mr. Chairman, I object. Strictly speaking, this notice would prevent me, as a citizen, who owns no dog, from walking in the Park without first acquiring one. Councillor Hogg (with some warmth): Very simply, then: "Dogs must be led in this Park." Councillor Trafford: Mr. Chairman, I object: this reads as if it were a general injunction to the Borough to lead their dogs into the Park. Councillor Hogg interposed a remark for which he was called to order; upon his withdrawing it, it was directed to be expunged from the Minutes. The Chairman: Councillor Trafford, Councillor Hogg has had three tries; you have had only two . . . . Councillor Trafford: "All dogs must be kept on leads in this Park." The Chairman: I see Councillor Hogg rising quite rightly to raise another objection. May I anticipate him with another amendment: "All dogs in this Park must be kept on the lead."

This draft was put to the vote and carried unanimously, with two abstentions. ”

Something I've wondered is why we can't have a language that looks totally procedural but uses monads under the hood to reason about state at any given point in the process. This would mostly be sugar, but I think it could make that stuff much more accessible.

We get small, specialized fragments of this in Rust's borrow-checker and TypeScript's control-flow analysis, but I'm not aware of something that tries to take the whole of IO-monads and reshape them into a familiar format.

Rust's borrow checker (or rather its affine typing, which the borrow checker partly implements - "affine" rather than linear, b/c the compiler is free to drop() objects that are no longer used) actually gets in the way of using monads to reason about state. That's one key reason why the language still lacks a general monad construct.
This might work out well in practice when tracking a single effect with a monad (e.g. with Haskell's `do` notation) but I think it quickly falls apart when working with multiple effects. Monads don't have a general form of composition - at the very least the order that they're composed matters to the outcome of the program. Procedural code is generally flexible enough to allow you to mix-and-match how effects gets handled, which I think would be difficult and impractical to work out in monadic contexts.
If you want to mix and match effects without regard to order of composition, that's what Lawvere theories are for (commonly known as 'algebraic effects'). You're right that monads don't give you this, but there are ways of describing these patterns without resorting to "procedural" idioms.
It seems to me that if it looks totally procedural, then most programmers are going to reason about it totally procedurally. Using monads under the hood isn't going to buy you anything. In particular, it isn't going to make FP any more accessible, because the programmers won't be doing FP. They'll be doing procedural programming. Sure, the language may turn it into FP under the hood, but that's a detail of how the compiler generates its intermediate forms. That's not the claimed magic of FP; it's all in how you think. And if you think you're doing procedural, well... you don't get any FP magic that way.
True enough, when you're writing imperative Haskell, you're not hitting the claimed magic of FP.

But:

1. Haskell lets your write the majority of your code in pure style. And it helps you separate the imperative parts (like pure core, imperative shell).

2. The compiler tries hard to generate the same machine code as C. The difference is that it gives the programmer more options: because the imperative parts are values (actions), it's strightforward to calculate them, which you can't do in C (you'd need the preprocessor or maybe LISP macros).

Some of the weirdness is so completely avoidable that it frustrates me, but languages and libraries dig in on ‘you get used to it.’ I’m talking about the f(g(h(x))) vs x | h | g | f. Right to left chains of functions seem more popular than chains of pipes in FP, but even being someone who has drunk the kool-aid deeply, I always prefer pipes.

‘Take the pan out of the over once it’s been in for 30min, where the pan contains a folded together eggs and previously mixed flour and sugar’ isn’t more FP than ‘Mix the flour with the sugar, then combine with eggs, then fold together, then put it into the oven, then take out after 30min.’ But people new to it so often think it’s an FP thing for no good reason. It’s a new user issue that ought to be totally fixable as a community.

FP has enough great ideas that I’d recommend everyone learn pure functional solutions just to put into their tool belt, but it’s absolutely true that getting up to speed is harder than it needs to be. My hot take: it won’t be really mainstream until someone figures out how to make dependent types really ergonomic, which seems a long way away.

I'm relatively an FP newbie, and I have a few questions.

1. I've never seen the "pipe" notation you talk about in any lang except bash, which is not an FP right? In which languages does "|" denote function application?

2. Aren't dependent types orthogonal to whether a language is functional? Just to make sure we're on the same page on what dependent types mean, I wanna give a example (a contrived one, sorry) of dependent types in Python.

    from typing import overload, Literal

    @overload
    def str_if_zero(num: Literal[0]) -> str: ...
    @overload
    def str_if_zero(num: int) -> int: ...
    @overload
    def str_if_zero(num: int) -> int | str:
        if num == 0:
            return "That's a zero"
        return num
Elixir, Elm, Ocaml, Haskell, and F# all have a pipe like operator for function composition (though the actual operator varies).
R also has it, if you count R as a functional language.
I think libraries are just as relevant as languages, and dplyr’s pipes are wildly popular and (more to the point) even idiomatic R at this point.
Actually base R has a pipe operator now. (I still use the dplyr pipe, just out of familiarity.)
R is a surprisingly functional language!
Clojure has the -> macro:

https://clojuredocs.org/clojure.core/-%3E

It does the same thing, but it's much more awkward to use.

As a daily user of -> and ->>, I am curious what you find awkward about them.
You have to plan to use them in advance. With a pipeline operator you can figure out the command you want at one stage and then tack on another part of the pipeline without needing to go back and alter the way you began the line.
The pipeline operator |> exists in Elm[0]. It is available in bash as & and the popular (in my limited experience) package flow[1] also implements it as |> for readability. It is a proposed addendum to Javascript[2] but I have no idea how seriously the JS powers-that-be are taking that.

The example you gave of Python doesn't have tagged union. I really don't know much at all about Python so maybe the type checker can tell you that you're handling all the cases, but with tagged union types you can have to handle every possible output of `str_if_zero` when calling it. That doesn't have to be an FP thing but I only find it when doing Elm and Haskell in my experience.

[0] https://elm-lang.org/docs/syntax#operators

[1] https://hackage.haskell.org/package/flow

[2] https://github.com/tc39/proposal-pipeline-operator

For some reason I wrote "bash" when I meant "Haskell" in the second sentence. Sorry about that.
outside `|` there's the threading macro in lisps (or else)

(-> x f g h ...)

(comment deleted)
> which is not an FP right? > Aren't dependent types orthogonal to whether a language is functional?

You're correct and I think that's kinda the parent's point. I think what the OP is saying is that the much of the syntax ergonomics are orthogonal to FP, which means they don't have to be as weird/frustrating/unusual/insert preference/unergonomic as they are.

A good example is how piping vs nesting are different syntaxes for function composition: x|A|B|C vs C(B(A(x)))

The former might feel more comfortable, familiar, and left-to-right readable for someone new to FP. That said I think a few language do use piping. F# has |> and I think Clojure used >>> maybe? -- signed, humble java programmer.

Clojure has two primary macros for this, thread-first (->), and thread-last (->>).

They enable you to write pipe transformations and in my opinion make the code much more readable.

> piping vs nesting are different syntaxes for function composition: x|A|B|C vs C(B(A(x)))

> The former might feel more comfortable, familiar, and left-to-right readable for someone new to FP.

C(B(A(x))) is the syntax introduced in school, but note that if you continue on in algebra the notation flips from C(B(A(x))) to x_{ABC}. Everyone agrees that it's easier to list the transformations in the order they happen.

(2) No, dependent types are not the same as overloading. Basically it's when the type depends on the values; so like having a type for odd numbers or having an array that knows its length at compile time (for example to avoid array out of bounds exceptions at compile time)
1. Others have answered it well. Tons of languages have pipe operators. I used | in my example because I figured more readers will recognize bash piping than other notations.

2. Yes that's what I mean by dependent types, where types depend on values, like f(0): string and f(1): int. And yes, they totally are orthogonal. However, pure functional programming tends to lean on its type system more than other styles, in my experience. But then without dependent types, something as simple as loading a CSV gets hairy relative to something like pandas's pd.read_csv. Maybe others can go into why pure FP and static typing go so much together, but I gotta get back to work.

(comment deleted)
Dependent types are not totally orthogonal of FP. They generally require immutability and function purity to work well and become extremely difficult to work with in the absence of those features.

See e.g. https://shuangrimu.com/posts/language-agnostic-intro-to-depe...

Yeah, type checking becomes undecidable otherwise and the compiler can also become vulnerable but Rust says they're going to try, so we'll see.
Well it's luckily a little better than that. Type checking doesn't have to be undecidable and the compiler doesn't have to be vulnerable because you don't have to execute any side effects in the compiler even if the code is meant to execute any side effects at runtime.

It's more that the utility of dependent types is generally restricted to functions that are pure and deal with immutable values, which means one possibility is to specially mark which of your functions are allowed to be used in a dependent type and which aren't. But the fragment of your program that can be used in dependent types is generally going to be a pure FP fragment.

Side effects aren't the only reason the compiler can be undecidable though. It depends on the type system, not just referential transparency. Look at Cayenne or ATS. Also, don't forget it's possible to still possible to DoS the system (although this is already more than possible with C++ and such, so what are we complaining about.)

Yeah, dependent types would be nonsensical for code that isn't functional. Marking which parts are and aren't is a good idea. Idris did something similar for totality checking. I think it'd be nice to have a system which differentiated between functions and procedures though.

> I think it'd be nice to have a system which differentiated between functions and procedures though.

That's essentially what 'IO' types are for (or finer-grained alternatives like 'Eff', etc.). At the value level, we have things like 'do notation', 'for/yield', etc.

  h(x).f().g() 
Is also very common and I would say it counts, especially if the language support something like extension methods.
> FP has enough great ideas that I’d recommend everyone learn pure functional solutions just to put into their tool belt

Completely agree! I got my first taste of FP writing unreadable nests of python list comprehensions and it's shaped my Java tremendously. I've never spent much time in FP dedicated languages, but it's as good a frame to understand as OOP. I find they even mix and match well.

For the order of fn composition, I found the pipe operator found in e.g. Elm really nice.
I think maybe part of it is that most people are introduced to FP through haskell, if I had to guess. And haskell (at least the tutorials) is IMO guilty of the top-down approach where the final value is written first, referencing variables that haven't appeared yet. ocaml, on the other hand, has you define your components before the final value which is more familiar to most programmers

I think haskell doesn't always look super readable, but I can appreciate it at least because it reminds me of math papers where the final theorem is introduced first, and then it's broken into lemmas, and each of those lemmas is proved etc

I concur, in Haskell you have to import the pipe operator (&) before using it, kinda second class, no snark intended.
I’ve “drunk the koolaid” and I mostly went the opposite direction on left-to-right pipes vs. right-to-left compose: with a(b(c(d))), order of evaluation will always be arguments->function call (in most languages) and, so d is the first thing evaluated. Pipes make it seem more consistent, but it also introduces an inconsistency.
isn’t the first thing you are being the first thing evaluated just better? we read ltr, so having to see and remember a before c(d) but then having some of a’s arguments at the very end is just annoying vs pipes
It's hard for me to verbalize, but the rtl composition just sort of makes sense. For one thing, it's the way functions already sort of work:

    a(b(c(d))) === (a • b • c)(d) // the functions are in the same order
And, when you think about the typical order of evaluation, a(b(c(d, e, f))) is evaluated:

    - look up the value of d, e and f
    - evaluate c(d, e, f) (call it g)
    - evaluate b(g) (call it h)
    - evaluate a(h) (call it i)
    - return i
Pipes add an inconsistency between the order of evaluation of arguments vs. function calls and the order of evaluation of functions. (Also, completely separately, I've mostly come around to disliking operators and thinking Lisps are right here: precedence and associativity are "intuitive" for basic math operators because we've spent years studying math and getting used to PEMDAS, but anything beyond this is just asking for trouble: aside, maybe, from APL/J's strict right-associativity without precedence)
I have an algebra book on my shelf* that puts function arguments on the left, as in (x)f instead of the usual f(x). It is definitely more “natural”, as functions compose in the normal reading direction. But even so, it is increadibly hard to read, because the break with the usual convention is so radical. Needless to say, it didn’t catch on.

* In my office, but I am at home. Don’t remember the author; sorry. But it was written back in the ‘60s, I think.

that same logic works for reversed-compose - a|b|c|d = (a)|(b~c~d). It is inconsistent with the normal kind but I think it’s worth it just for the ability to write list |map| func |filter| predicate |group-by| keyfunc |map| .values() |map| sum, which is so nice because you start with the value and as you read the thing that’s happening is precisely the thing you’re reading, and everything’s in one place - it’s
This is only true if your stages don't have arguments: with a | b(something) | c | d, `something` is now evaluated before b(something)
true, although argument evaluation order in a(x,b(y,c(d),z),w) is already somewhat hard to rely on
What I like about pipes it that it allows two ways to write chains of function calls. For calls that are more branch-like, the nested structure of standard function calls makes the most sense to me:

    article = create(author("John"), Page.article())
For cases where you're modifying data sequentially, the pipe structure makes it really easy to read:

    5th_prime = 0.. | filter(is_prime) | nth(5)
Having a way to write function calls forwards and backwards lets you choose whatever syntax feels most appropriate for the job.
(comment deleted)
I’ve hacked pipes in python many times just because it’s so much better. And no, “a=f(a);\n b=g(a);\n c=h(b);\n a=i(c)” isn’t “more declarative” or “more expressive” than a | f | g | h, and even in python a |pipe| b |pipe(blah)| c |pipe(blah, blah)| d is so much nicer than nesting the parens.
This sounds like a debugging nightmare. Is it not easier to name each intermediate result so you can inspect it in a debugger?
In terms of debugging inconvenience for intermediate values, `->(x) | a | b | c` is the same as `c(b(a(x)))`.

I find debugging /w a REPL using functional-esque code much easier. You can play around and exec/inspect lines/snippets at will without changing state & "breaking" the debug session.

Agreed they are pretty much the same inconvenience level, but for me that level is too much either way. Maybe it's just my workflow, but I always preffered step-by-step debugging compared to REPL which is why I like to see many intermediate results being named.
when I hack it into python, it’s slightly annoying. when done properly, no, it’s no worse than f(a+g(b+c())), which is everywhere in code already. And debuggers / error messages should indicate what sub expression caused it, which would clean that up
It's easier, but there are several considerations:

- There's workarounds. Eg. in F# you can redefine the pipe operator in debug mode [0] so it can be stepped into and inspected.

- I think some IDE plugins are also working on allowing pipeline contents to be automatically inspected.

- And of course, there's the good old tee operator for printf debugging. FP style rarely mutates variables, so a few print statements are just as good as a live object inspector.

As for why you actively wouldn't want to have those variables... much like comments, sometimes variable names add precious information and should be typed out, but sometimes they're just unnecessary noise, mental overhead, and scope pollution.

E.g. compare:

    let suppliers = getSuppliers()
    let sortedSuppliers = sortBy getSupplierPriority suppliers
    let bestSupplier = first sortedSupplier
to:

    let bestSupplier =
       getSuppliers()
       |> sortBy getSupplierPriority
       |> first


[0] https://stackoverflow.com/a/4966892
D doesn't have pipes, being a C-like (it probably could, but it's untypical), but it has something almost as good: "uniform function call syntax", meaning that a function that takes X as its first parameter can be called as if it was a method of X. `foo(x)` => `x.foo()`. This makes functional programming in D a lot closer to the pipe example, in that you're usually passing a lazy iterator ("range") through a sequence of chained calls.

Every C-like language should copy this idea, imo.

That sounds amazing, but I wonder if it could get unwieldy as well.

Would have absolutely loved it in Go, where over the course of time I had to build up dozens if not hundreds of validators on strings, maps, etc. Ended up resorting to storing them in `companyInitials-dataType` packages, EG `abcStrings.VerifyNameIsCJK()`, but the unified call syntax would be super tidy.

Nim has this as well, it's lovely.
A lot of the difficulty in learning FP is in unlearning imperative programming. I hypothesise that for someone whom has never programmed before, FP and declarative paradigms are easier to reason about.
Java streams are a (limited) version of what you propose. Something like this is very idiomatic:

    myhashmap.entrySet()
        .stream()
        .map(Map.Entry::getValue)
        .sorted()
        .distinct()
        .toList()
I was going to say C# LINQ, too!
Difference with pipe operator would be

1. can't be extended.

    myhashmap.entrySet()
       .stream()
       .flattenBars()
       .toFoo()
2. (As a result of 1) - applies only to streams
> can't be extended

They can be extended arbitrarily by defining collectors. The Stream interface is very well designed. For example your "toFoo" would be .collect(Collector(<foo-provider>, <foo-accept>, <foo-merge>, <foo-finisher>)). All of them can be arbitrary functions working on arbitrary types. That's the standard library implementation of "toList", in fact!

> applies only to streams

You're exactly correct here, but for legacy reasons Java did not have a choice. I'm the first to admit mylist.stream().map(f).toList() is dumb. A list is a functor, dammit!

It's the best possible solution given the existing constraints, but it's definitely limited in what it can do.

Most FP-heavy languages also have good support for metaprogramming, and have macros that imitate pipes (e.g., all lisps and Julia).
> ‘Take the pan out of the over once it’s been in for 30min, where the pan contains a folded together eggs and previously mixed flour and sugar’ isn’t more FP than ‘Mix the flour with the sugar, then combine with eggs, then fold together, then put it into the oven, then take out after 30min.’

Oddly enough, the language that put the most thought into this problem is Perl, which provided the pronoun variables to represent "whatever I just computed".

Natural languages always offer multiple strategies for ordering the various parts of a sentence, because the order in which things are mentioned is critical for several purposes, from making it easier for the listener to follow a chain of thoughts to building or defusing tension or correctly positioning the punchline of a joke.

I'm always surprised how unpopular perl (and even more, its spiritual successor raku) is with the HN crowd. It does not square with my experience of various languages' ergonomics.

It's not at all surprising that perl put thought into things like this, the entire language concept optimises for elegance of expression.

I can't speak for Perl's general usefulness, but pronoun variables are just not terribly useful, because you can make them up on the spot. For example,

    result = input
    result = transform(result)
    result = transform2(result)
    return result
Perhaps I misunderstood the idea of pronoun variables. A related idea is the idea of `self` or `this`, in that computation is viewed from the perspective of an agent (which is ironically called an object when it is typically a grammatical subject).

Perhaps this is why we like OOP and CSP patterns so much, it meshes well with our social abilities.

> Perhaps I misunderstood the idea of pronoun variables.

Their point is the same as the one in human language. You can't make them up on the spot -- they already exist, with predetermined reference logic that we learn as part of learning a language. All you can do is mention them. This means they often reduce both the reading and comprehension load in comparison with inventing a new name and binding it to some referent.

For example, in this sentence:

> I can't speak for Perl's general usefulness, but pronoun variables are just not terribly useful, because you can make them up on the spot.

Who's "I"? And "you"? What's "them"? These are all obvious. Compare that with:

> I = the person writing this. This = the writing you are reading. You = the person reading this. I can't speak for Perl's general usefulness, but pronoun variables are just not terribly useful, because you... [You = someone writing code] can make them [Them = pronoun variables (though, as noted, you can't make them)] up on the spot.

Consider what it would take to write code displaying the first 10 numbers in the fibonacci sequence in some PL.

Here it is in Raku using pronouns:

    say .[^10] given 0, 1, *+* ... Inf # (0 1 1 2 3 5 8 13 21 34)
I grant that you have to learn this aspect of Raku to be able to read and write the above code. But it only takes a few seconds to learn that:

* `.foo` means applying `foo` to "it".

* `[^10]` means up to the 10th element. That's got nothing to do with pronouns, but whatever.

* The `*` in `*+*` is the pronoun "whatever" and, when used in combination with an unary or binary operator forms a lambda for that operation. So `*+*` reads as "whatever plus whatever" and represents a two argument lambda that adds its arguments.

* `...` is Raku's "sequence" operator, which uses the preceding function/lambda as the generator (which in turn uses the preceding N arguments per the generator's arity). Again, nothing to do with pronouns, and yet more you have to learn, but that's how PLs are.

It will no doubt look awfully weird, almost as weird as, say, Chinese (presuming you don't know Chinese). But that (it looking weird) is something completely distinct from whether it (using it) is extremely pleasant once you just accept it.

With a sufficiently open mind it (accepting using a "whatever" pronoun) can take literally a few seconds. (Thus kids tend to find this notion of "whatever" in Raku easy to grok, whereas adults sometimes struggle.)

Functional programmer here. It's a different way of thinking is all, I don't see it as solving puzzles exactly. It's unfortunate if people see it as being about complexity or being clever. Done well, it should be the opposite. Once you understand the key abstractions, it can make code a lot easier to write and reason about in some cases. That's the joy of it for me. It furthers my day to day software engineering concerns of readability, maintability, testability etc. Nothing more.

It's just another tool though and there are many places where it's not appropriate. It's also a personal taste thing, I get that. It's worth persisting with it on a deeper level though just for the sheer joy of those 'a-ha' moments that come. Even if you don't use it in your day job, just bending your brain with different programming paradigms is well worth it.

I feel like your first and second paragraphs are directly at odds. It's not about "solving puzzles" and you decry cleverness and complexity, but you also think we should persist for joyous "a-ha" moments and brain-bending?
Ha, yes OK that sounds a bit contradictory so let me clarify.

Firstly learning ANY new paradigm is inherently brain bending. By definition right? If you didn't know logic programming or oop you'd have to bend that brain.

Secondly, using FP day-to-day doesn't require any deep insight. You can use various tools, monads and whatnot, and not think too hard about it.

However, when you really grok the paradigm you will get those a ha moments. The same is true of other paradigms too, but there is a particular satisfaction that comes with deep understanding in this domain that I can't explain. It's kind of beautiful to a certain mind I guess...

> However, when you really grok the paradigm you will get those a ha moments. The same is true of other paradigms too, but there is a particular satisfaction that comes with deep understanding in this domain that I can't explain. It's kind of beautiful to a certain mind I guess...

Right but you understand that 90% of developers can't bend their brain that way so does make maintaining functional code very difficult.

It makes expressing some solutions so elegant and easy it’s totally worth using a functional approach. Other things may be more elegant to express using an imperative approach.

Just try to implement recursion in MS 8-bit BASIC.

AFAIK basic doesn't do stackframes. So a recursive function would overwrite itself.

Or maybe it was algol

You can do that, but you need to implement your own stacked states and call the function with recursive GOSUBs.

Doable, but not fun.

Of course you can do your own stack framing in basically any language.But it gets quite unwieldy.
I've seen a handful of articles and conference talks where the main thrust is "why isn't functional programming more popular". Apparently it works well and is "intuitive" for some people. I think the first thing we should do is recognize the amount of neurodivergence amongst programmers.

The WISV-IV GAI tests for verbal comprehension and perceptual reasoning. It provides an estimate of general intellectual ability, with less emphasis on working memory and processing speed. My GAI is 124. Which means I'm fairly intelligent (sic. "superior") when it comes to understanding language and reasoning. It makes me a fairly good problem solver. I'm an ok programmer (judged against the hundreds of programmers I've paired with through my consulting work).

That said, I've tried to learn Haskell three times and given up each time. The functional model of D3js kicks my ass. I cannot grok LISP.

It turns out my WMI (working memory index from Wechsler Adult Intelligence Scale) is BELOW AVERAGE. This makes holding lots of recursion or abstraction in my head at once quite difficult. I also suffer from adult ADHD.

I love long functions in a procedural style. I'm quite proficient working that way. There's not too much abstraction and behavior hiding (why I grew to hate OO). You can see how my neurological makeup impacts what "good" code looks like. I also understand that James Gosling has a kind of synesthesia and that impacts the structure of his code to the point of causing problems for others (see Lex Friedman interview).

I said all that to say this. What makes code understandable/readable/clean is in the eye of the beholder. There's quite a bit of neurodivergence in our field and we need to account for that when deciding on how to critique code. After all shouldn't code be written first and foremost for humans to understand and only incidentally for compilers? (paraphrased from SICP)

"Apparently it works well and is "intuitive" for some people. I think the first thing we should do is recognize the amount of neurodivergence amongst programmers."

Yep that's it! It suits my brain quite well but I totally get that it doesn't suit everyone's. This sort of point is under-appreciated, not just with FP, but with so many things in computing and life more generally.

Thank you for bringing up WISV-IV – it's a fascinating way to categorize intelligence.

My son (11yo) is autism spectrum and recently received a WISV-IV evaluation and found him off the charts for spacial reasoning. It immediately had me thinking about object oriented programming and conceptualizing digital processes spatially.

Perhaps functional patterns are preferred by those with faster "processing speed" of thought or "working memory" – basically thinking primarily of "just before" and "just after".

My anecdotal experience has shown that the coworkers who think "the fastest" prefer functional patterns, whereas those who think slower but more comprehensively prefer object oriented.

Also anecdotally, functional programmers rarely leave the keyboard, whereas OOP spend a lot of time on the mouse – both are equally productive at the end of the day.

Yes. I took those tests (and more as I presume your son did) for my ASD diagnosis. It was very enlightening and I feel like everyone should have a psych profile like that done.
Not sure why you are being downvoted (maybe the "I'm a slightly above average programmer").

I think you are entirely correct, FP is both more suitable to certain type of people and to certain types of problems.

I've used FP in some situations and loved it, and had to grapple with FP code that was utterly incomprehensible because the person that produced it seemed more interested in showing off what he could do with FP rather than actually getting the job done (or producing efficient code, for that matter).

Me not understanding why that would be offensive is probably part of my autism spectrum disorder.
Its not offensive, just any FP post brings out the FP snobs who often dont like criticism.
It depends on the audience background/worldview. From Christian moral tradition: "modesty is a virture". From modern rational thought: a self-assemsments like this are not reliable (see for example Dunning-Kruger effect) and the fact that author does not realize this erodes trust in the remaining of his argument.
Programming is about expressing how to solve a problem. Of course different ways to express that will fit different people. Every time I see people trying to push "that language is the best!", I see people trying to have a language as the only spoken and written language in the world, and I find that incredibly sad. People shape languages, languages shape people. Seeing someone that's very comfortable with a certain programming style that's not the one that you're used too usually means that you can learn a lot by talking to them.

When you look at programming as a form of communication, it's easier to understand a lot of things. You can't build your tower of Babel with thousands of people if you all talk different languages in different ways, you need something that can be understood easily by everyone. But that's also not the language you would use when writing something deeply personal.

Interesting, drawing parllels to religion here. In the end it might be our genes and the inherent tribalism driving us to behave that way, while thinking we are rational agents. There is an essay about it IIRC, the Lambda tribe and the Machine tribe.
Talking about things I don't really understand here, but there's a nice parallel to draw to Gödel's incompleteness theorems. Same thing with the blind men and the elephant, or 2D lizards that see a cube pass through their dimension. Maybe we're cursed to only ever see part of the truth, and only be able to understand part of it. Maybe there's no single "truth" at all and we have to learn to deal with this.
We are not cursed, we just can't see it. Immanuel Kant called reality "Das Ding an sich", which i can try to translate as "The thing in itself". He showed, that since we perceive reality via our senses, which are easily fooled, we can never be sure that we perceive reality as it really is.
(comment deleted)
Fully functional programming (e.g. Haskell) isn't popular because it isn't ideal and computers work imperatively.

Even in a "functional" task (e.g. compiling) sometimes you just want an imperative algorithm. And while Haskell allows this via state monads, they're clunky.

Meanwhile there's practically no tradeoff to writing functional code in an imperative language. In fact there's lots of functional code in most C++/Java/Python programs. Most "imperative" languages are actually hybrid functional and imperative, supporting "functional" constructs like lambdas and pattern matching.

99% of the time a functional program will get executed sequential and strict anyways. The other 1% of the time, in an imperative language you can explicitly encode functionality (e.g. generic stream operators which work concurrently and on lazy streams).

Functional programming absolutely has its use cases, and I love a bit of functional style thrown into imperative language one hundred percent.

That being said, I often end up arguing with 'pure functional programmers' at work - they can be incredibly hard to work with and married to the idea that _everything needs to be functional_ because its their preference, even though your average college student is going to be able to pick up and iterate on code that just uses a damn for loop or whatnot.

I _like_ functional, I find it incredibly useful, but like everything else, its just a tool to solve a problem and there are tradeoffs.

But some of the most annoying and must-do-things-academically-because-look-how-cool-monads-are this-is-the-only-proper-way people are very vocal about functional, which is a pretty big turnoff IMO.

Haskell is a good example. There are totally cases where Haskell will make your life easier. But if you're trying to reinvent the wheel and redefine our architecture by injecting haskell everywhere you can because _you like it_, that drives me absolutely bonkers.

What are the use cases you've seen functional programming in real business?
Precisely modelling the business domain using immutable objects and pure functions is a killer feature of FP for the real world.
I'd imagine one of the more common use cases is data processing.

At my last job I was part of a team that built a data processing platform and we did it in a mostly functional style.

Have you tried to see the pure FP viewpoint, rather than argue?
I have personally been from an FP acolyte and back, there's some great stuff there, it will improve your non-FP code to learn an FP language. That said, bending over backwards to fit the FP mindset is suboptimal, the same way as writing fully OO code is. e.g. IMO some stuff from FP should _generally_ be avoided in favour of making code easier to read and debug - e.g. recursion, maps, monads, generators etc. etc.
"Meanwhile there's practically no tradeoff to writing functional code in an imperative language."

I would have to disagree with you there. It partly depends on what you mean, but I quickly give up on functional solutions in, say, Javascript (despite Javascript technically having all the bits I'd need!) just because of the lack of immutable data structures and cheap copying. Not to mention the constant pain point of asking "does this modify in place, or does it return a new" when dealing even with the standard library, let alone -user- code. I either need a library like Immutable.js, or I need to jump through hoops that are both not ergonomic, AND have performance penalties, or I embrace mutation (and at that point frequently rule out a fully recursive approach because the bookkeeping involved far outweighs any benefit).

Agreed. Constraints (primarily in this context immutability) are invaluable.

Writing functional, immutable style in Python is handy but can be maddening when you never know what to expect from libraries.

> Not to mention the constant pain point of asking "does this modify in place, or does it return a new" when dealing even with the standard library, let alone -user- code

I don’t see how this is a pain point, JS is pass-by-value for primitives and pass-by-reference objects. Ergonomics, sure, but isn’t the lack of pattern matching a much bigger obstacle the lack of immutable semantic sugar?

Immutability is easy to gain, though, just make your variable object writable: false.

Because any recursive algorithm that needs to backtrack requires special care.

That is, let's say I have a sodoku solver. I write a recursive solution wherein I guess the next value on the sudoku board based on the constraints. When an execution path completes and fails to be solved; my board is now populated with that 'bad' state, and I've lost the state I was in prior to the recursive call, at each level of recursion.

I either need to make copies at every step (very unwieldy and inefficient), or explicitly write the 'undo' mechanism for each step. It's doable, but it's extra bookkeeping and logic to keep track of. Whereas immutable data structures give me an ergonomic and reasonably efficient way to maintain the prior state, and to create the next state.

And, the mix of mutation and non-mutation constantly messes with me. Array.concat returns a new array, but Array.push() modifies the array in place. Array.slice returns a new array; Array.splice modifies the array in place. This -constantly- messes with me.

The lack of a feature is just inconvenience that I work around. Having a mix of modalities causes me to trip over myself constantly.

Easy solution: don’t use them, use destructuring.

    // arr.push(item);
    // return arr;
    return […arr, item];
That's a slightly more wieldy way of creating a copy of the array. So still has the inefficiencies. It also is only a syntactical help with adding things onto an array; it doesn't help with removing items.
Why would you worry about low level details for a high level language, optimization is an implementation detail. Javascript in my opinion lends itself functional style programming
Because he cares about his users who might be using his sofware on a smartphone? If you disregard performance completely, your users will either use your app because they have to and hate it or switch to something else if they can.
You also have to focus on the correct performance issues and personally I've never seen writing functional js/ts being the issue.
Define functional js/ts. If it's just "oh, hey, I am using first class functions everywhere" - yes, Javascript is great at that. If it's "I'm writing a bunch of recursive solutions for naturally recursive problems", Javascript is a pretty poor fit for it. You can do it, but it requires extra bookkeeping and care, as I mention on another comment.
I once benchmarked a simple loop, and the same loop done via recursion. The recursive solution was an order of magnitude slower.
I use F# a lot and like it, but imperative code was problematic to do.

Now using Rust, I think I'm even more functional than ever. One key thing is that I can put a simple loop here and there (inside functions) and still the interfaces are functional.

F# has mutable and while loops also.

I’m surprised you are finding Rust good for functional programming to be honest. It lacks a do-notation system, currying by default and guaranteed tail-call optimisation.

> It lacks a do-notation system, currying by default and guaranteed tail-call optimisation.

And instead gives plain structs with separate impls, traits like Into/From that cut tons of custom-made transformation code, more uniform iterators (in F# some things not implement iterators that other things do, because everyone need to implement again all of them) and other idioms that are very practical.

Is a tradeoff, sure.

> isn't popular because it isn't ideal and computers work imperatively.

Eh, I think it has more to do with tooling and framework support.

Imagine if the world were flipped and C++/Java/Python had zero IDE support, slow compilers, and weird/unreliable build and package management tooling, and only third-party bindings to any platform or framework. And Haskell had incredible IDE support, a fast compiler, great build and package management tooling, and official support for stuff everywhere you wouldn't have to think twice about using in a commercial context.

You'd use Haskell.

There are tradeoffs to writing functional code in langauges designed primarily for imperatively. Another comment mentioned efficiency.

Another is that in a pure language, you can look at a function and know for a fact that nothing you're seeing can ever be mutated. This eliminates a massive source of cognitive load when trying to read and understand code, as well as eliminating all sorts of bugs related to the program being in the wrong state. If you're trying to write functional code in a language not designed for it, you're missing out on those benefits.

Now, it's absolutely the case that the tradeoff may be worth making depending on the person and circumstance. But it does exist.

> Another is that in a pure language, you can look at a function and know for a fact that nothing you're seeing can ever be mutated. This eliminates a massive source of cognitive load when trying to read and understand code, as well as eliminating all sorts of bugs related to the program being in the wrong state. If you're trying to write functional code in a language not designed for it, you're missing out on those benefits.

When you write functional code in imperative languages, these constraints aren't enforced by language, but they should be enforced by convention and code review.

Ah, to be reminded of high school, where one could be not popular simply because they are weird.

I think that they've confused functional and declarative?

I'm curious, what is the difference between a declarative programming language and a functional one?
Declarative languages are characterised by having the programmer specify _what they want_ but not _how to get it_ (i.e to get a sorted list, you would specify that the elements are in increasing order, but not the specific algorithm to use). In contrast, functional languages are characterised by treating functions as first class values (amongst other things, but these are harder to summarise).

Prolog for example is a declarative language, Haskell is a functional language.

Someone can correct me on this, but I've never seen this distinction you're making anywhere else. And, it doesn't make sense to me either. The wikipedia page[0] for FP says that FP is a "declarative programming paradigm". Can you give me an examplel of a "functional" piece of code that is "not declarative"?

[0] https://en.wikipedia.org/wiki/Functional_programming

Prolog is a declarative language but not a functional language. Hence functional languages and declarative languages are not equivalent. Functional languages are not just defined by lacking side effects but also by using functions as first class citizens, meaning that SQL isn't a functional language either.
Python and Scheme are the two classic examples of non-declarative functional languages; they are both instructing low-level VMs to mutate machine state, but also both have functional-programming tools and first-class functions. The Scheme (set!) form is a great example of imperative mutation within a functional paradigm.
Your definitions are pretty much orthogonal. I would consider Haskell functional and mostly declarative.
This is about as far from "popular kids make fun of nerds for being weird and nerdy" as you can get. It's more like two different groups of nerds having a nerd-fight about which kind of nerd is the better kind.
I wonder now much C++ matters here. I rarely reach for functional patterns when the language I'm using does not provide proper constructs for readable, declarative code like pattern matching, nominative discriminated unions, and pipe operators.

Sure, you can fake all that with other constructs or patterns. But then it really does feel more like a puzzle.

IMO, good FP is good when it is maximally declarative and organized in the linear way in which humans reason best.

Maybe the author should try F# or OCaml instead of Haskell! I love FP and have also tried and hated Haskell three or four times.

Subversion:

Functional programming is popular because it's intuitive!

Perhaps "immanent" or "diagrammatic" is a better word than functional. I mean that a function _is_ what it does, but not so with a procedure. One cannot bake a value as one does a cake!

See React's dominance. See the usefulness of declarative state machines. See async/await.

Functional programming gets a bad rep. People often fail to see the functional elements in more imperative code and the imperative elements of more functional code. In fact very few language constructs are necessary to open up 99% of functional possibilities in imperative languages (functions as values) or imperative possibilities in functional code (do-notation). Any critique that dismisses one or the other misses the point.

May add more explanation later.

Yes indeed!

How many times have you heard these two statements from the same individual?

“I don’t need all those functional features”

“I don’t need a functional language because X has taken all the important functional features from language Y”

> Functional programming is popular because it's intuitive! I mean that a function _is_ what it does, but not so with a procedure. One cannot bake a value as one does a cake!

I suppose it really depends on what you've learned first. Myself, I started programming (C++) in my early teens, a bit before I was introduced to the concept of a function in math classes. I didn't have much problems with that part of math, but I knew something doesn't feel right about it. Only many years later I realized that I never fully internalized the concept of mathematical function being a relationship and not a procedure. And I had the same issue with "=" symbol. After being exposed to the concept of assigning values to variables in imperative programming, it took me years before my brain fully internalized the concept of equivalence relationship.

Maybe the problem is that we evaluate functional programming as a way to write programs, but it’s real potential is that it gives us more ways to rewrite programs.

That’s more useful long-term.

People always underestimate how much the aesthetics of things actually matter. Language adoption is a numbers game and good marketing matters.

This is why Lisp never won. However powerful and elegant it may be, Lisp syntax is downright ugly.

The exact same thing happens with Haskell. However amazing the language is, its freaking unapproachable and the syntax is - like the OP says, weird.

It's impressive to have people say that Lisp is "downright ugly" and Haskell is "freaking unapproachable and the syntax is weird" on an article that includes C++ templates. It's not the "aesthetics" that matters, it's the popularity and what you're used to. Part of the reason why Rust is popular is that they didn't break too much people's habit, and even with that it constantly gets flak for its syntax.
C++ template programming isn't very popular either so that wasn't a very good counter example.
It is very popular compared to Lisp or Haskell.
I didn't want to comment about looks because it wouldn't add anything to the discussion but since we are talking about looks I just can't get over how ugly c++ templates are.

I will even admit that the only reason I never gave Rust a chance was because it reminded me too much of this type of angle bracket monstrosity.

I find functional programming reasonably straightforward to learn but difficult to master.

For example, this Common Lisp cheat sheets covers a large part of the language and isnt hard to follow

https://github.com/ashok-khanna/lisp-notes

I was doing some Haskell and Racket and I found basics not too hard to learn, but I did then too hard to master, but I think thats to do more with the complexities of the problems I tried to solve and less to do with the languages themselves

CL isn't a functional language though. It's 100% multiparadigm. The lisp-notes page that you linked shows this.
Ah sorry, I always thought of it as a functional first language
It's totally functional first. And it's multi-paradigm.
I'm happy the author wrote this blog post, it very much reflects my take on FP.

And if I had to keep one sentence from it: mutable states are very useful.

FP addicts consider mutable states to be something to be banished at all cost, much like a generation of 70's academics tried to do away with goto's.

The fact is, there are lots of situations in the real world where using mutable state variable is the very best way to model the problem:

   - easier to understand
 
   - faster to execute
The same holds for goto's btw.
I think the big difficulty in trying to bake the functional cake is that the author tried to name every step, when he didn't do this for the imperative cake.
I am not a huge functional programmer, but the big weapon in FP's wheelhouse is that pure side-effect FP is far more suited to multicore/threading/processing, which is where all the future gains in processing will probably come from.
Downvoted for knowing what you're talking about.
Functional programming isn't popular because the talent pool for experts in languages like Haskell is

* tiny

* composed largely of insufferable snobs who will insist on reminding you that they're smarter than you and understand computing on a deeper level

Whereas the talent pool for jalopy languages like Javascript and Python is full of easygoing can do barbarian hustlers who will do everything wrong but make your prototype work well enough for you to get the next round of funding. And if you really need heavy duty people who are still pragmatic, there's always the large and deep C++ talent pool

Optimized for smugness, and often very poorly explained. But there's some interesting ideas there if you get past those barriers.
Pure Functional programming’s cost comes up front when you are trying to solve and express your solution. Imperative programming’s cost comes after you have written code, because it is inevitably buggy and incorrect. Imperative programmers and managers don’t mind because fixing bugs feels and looks like productive work, and they don’t believe bugs are largely avoidable.
I think the actual problem is that a lot of the functional programming discipline is about writing code to be understood by machines (eg: compilers, theorem provers, etc) in ways that can trade off against the convenience of people reading and writing it. When I have to write a program that computes functions of programs, I always reach for my FP knowledge, first thing, and start using the more "theoretical" branches quite quickly.

But program analysis and synthesis are different actual endeavors from writing some bog-standard, everyday code.

Fact: functional programming takes more time to learn. It's not full of people that are "smarter' or "snobs" it's just full of people that have taken the time and invested in educating themselves to get to the point where they can do it well. It's not "better", it's just a useful tool for certain types of problems. I'd advise any young engineer to just learn how to do both "imperative" and "functional" programming well and avoid getting dogmatic about it. The vitriol here is in dogma here to be sure
“Fact: functional programming takes more time to learn”

Is there evidence/studies about this?

I haven’t tracked this down, but I believe the authors of How to Design Programs did some research and basically found that whether FP or OOP is harder depends on which you learned first.
Which kind of makes sense, but then again I learned OOP in college, I always found it really hard to remember all those design patterns they made us learn. Years going on in programming I never really saw any big advantage of OOP hierarchies and spreading one function into 15 different files (yay for the person debugging this).

Then I found out what we call OOP is just a misunderstanding. Erlang kind of did "proper" OOP and that makes sense.

Then I got to functional programming through F# (and Scott Wlaschin's amazing page F# fsharpforfunandprofit.com), and it just immediately clicked. Maybe because Scott's material is so good. But also because there's not so much non-sense boilerplate and extra complexity as you see in every "good" OOP example.

When I was in college, a few decades ago, we had a class on object oriented programming. Out of about 60 people in the class, you could count on the fingers of one hand how many people passed. It was so bad the professor had to offer a redo. (Why yes, of course I passed, duh).

It was a long time ago and in C++, which is a touch trickier than Java or C#, but "traditional" programming isn't particularly simpler. It's just what's taught in school and what all the examples you google show. It's a self fulfilling prophecy.

Procedural vs functional? Yeah, the former is more intuitive to a beginner. But as soon as you go a little further than that, it's basically all about what you've been exposed to.

OOP was still considered a difficult class at my college before I graduated a couple of years ago.
Indeed. Some five years ago, I was explaining the basics of OOP (in Java) to an aerospace engineer, who needed to get into IT. It took him several weeks, and a whole bunch of exercises, to master. He already had experience with FORTRAN.
To add more weight here, when I was teaching programming in collage I found it quite surprising which concepts students found difficult to understand.

They struggled to learn pointers, which makes sense to me. They also struggled to learn OO. And they struggled to learn recursion and state machines. All this stuff seems so simple once you've internalized it.

I think there's something real to the idea that humans have a hardwired instinct for stories. And that makes learning imperative programming easier. But you move beyond your intuitive instincts pretty fast. And when you do, it really matters how well your language or environment helps you to think.

The problem with C++ templates is simply that they aren't very good language for thinking in. They're a mashup of functional concepts in an imperative language with bad syntax. But functional programming doesn't have to be done badly. For a comparison, look at spreadsheets. They're arguably the most popular programming languages in the world. And they're purely functional.

When teaching I think C++ templates should be thought of as a textual preprocessor step. It might be easier to grasp than fancy words.
I disagree. C++ already has this mechanism - the preprocessor it inherited from C. That one is a textual preprocessor step, and it's very important for the programmer to have this as a mental model - then they'll be able to use it in a smart way[0] and avoid dumb mistakes.

I'm not sure if this is the best model, but a good one for C++ templates is that they are tree-level code generators. Kind of like really dumbed down Lisp macros. They don't do textual substitution, but AST-level one. But they generate code nonetheless.

(I suppose exposure to Lisp and Lisp macros may be helpful here too - just to have a comparison with a system that's a proper compile-time AST-level code generator, and that with minimal syntax, you can actually write your code as AST directly and be as readable as syntaxful languages.)

--

[0] - I know the mainstream opinion in application-level C++ is to avoid using it at beyond includes, include guards, and occasional conditional compilation or third-party package configuration. There are good reasons to avoid it - beyond complexity, quite a lot of support tooling like IDEs get confused by more complex macros. But still, there are many instances where even the newest C++20 features won't help you reduce obvious mechanical boilerplate. I've learned to use the preprocessor in those cases - mostly for readability reasons.

I struggled with pointers as a kid until I learned assembly. Then C pointers became trivial.

I think the problem is this: humans love to learn something when it simplifies their lives. That is the spirit of innovation.

A prerequisite for learning is that the student must have already been exposed to the messy complex primeval swamp before they can appreciate what learning has to offer. Where education gets stuck is that they take students with no prior exposure to the problem domain and then teach them the solution as an answer to a set of questions they had never asked.

The mind rebels against pre-canned solutions.

Students who have prior exposure to the problem domain pick it up rapidly. Schools aren’t built to take $$$$$ from students just to show them how not to do things…yet that’s what needed for a student to learn.

In my university, I was a part of an experimental class that did the introduction to programming class in Ocaml (we had to prove that we can write C beforehand).

There weren't issues with it. If functional programming was introduced earlier, people would have less trouble learning it.

Meanwhile Stanford does an analogous class in Python. And all of those alumni go on to define the industry trends.

Define "functional programming", it's a style and you can write imperative Haskell just like you can write functional C, in fact a language that is expressive enough to use different programming paradigm at the same time will be superior to to a single programming paradigm
The classes at CMU in SML provide some alternative "facts". FP != Haskell. If you start with FP, OOP seems weird and vice versa. My guess it is just our wetwares' expectations being violated.
> Fact: functional programming takes more time to learn.

Is this a fact? I was taught scheme at age 12 and in four weeks I had a working compiler. And I have heard that a team taught middle schoolers erlang and got them writing a chat network in 2 days.

Hm, what about a language like Joy or Factor? It is written sequentially, as a recipe, and (in the case of Factor) can have side effects. And yet it is deeply functional, because of the compositionality of words.

I really like Haskell, but I think Factor is going to be my next thing (although it needs better type system, like Kitten has attempted to), for the reason that it further simplifies Haskell's (already simple) syntax.

(comment deleted)
I recently watched this talk-

Why Isn't Functional Programming the Norm? (https://youtu.be/QyJZzq0v7Z4)

Here's the HN thread- https://news.ycombinator.com/item?id=21280429

I learned in that talk, among other things that Oracle spent $500 million to promote and market Java.

- https://www.theregister.com/2003/06/09/sun_preps_500m_java_b...

- https://www.wsj.com/articles/SB105510454649518400

- https://www.techspot.com/community/topics/sun-preps-500m-jav...

C++ and C have spent $0 on marketing and are more popular, so I don't think this is a good indicator of success.
Never heard of any conference starting with Cpp or the like, nor does it have a website I guess..
That you have never heard of things does not mean they do not exist.... https://cppcon.org/
It was sarcasm.
The sarcasm didn't make sense. The conference was started way, way later, by community organizers from all companies. This completely disproves that it was a major money push from one company.
Those exists because the language is popular, they weren't created to market the language before it was popular.
My point is that OP’s comparison is useless because Java most definitely has marketing costs mostly associated with that 8+ million Java developers, the same as other popular languages.
How is this relevant when the topic was that Oracle literally spent $500 million marketing Java? The community of anything popular is marketing itself, yes, but that is a very different thing from having an actual marketing budget to push it to popularity.
The fact that you linked so many different companies is evidence that this wasn't just some push by a single company. Those things happened in parallel because the language was popular and is evidence of a vibrant community more than anything else. Being popular means many will want to make things with it yes, saying that it got popular since many people did things with it doesn't make sense.
Yes, because as I mentioned on the other comment you ignored, thanks UNIX, C, being born at AT&T, and the $0 cost of UNIX tooling up to the mid-80's alongside source code.

Had C++ been born somewhere else, e.g. Objective-C, and its popularity wouldn't exist.

> Yes, because as I mentioned on the other comment you ignored

I am not the other person you responded to.

Anyway, don't you think the fact that so many others decided to copy the language and implement their own versions of it is a testament to its popularity and not just that it got pushed by a single company?

It was pushed by UNIX popularity.
It was, and still proves that a company marketing it with $500M didn't happen. Are you also going to say the same about python, or can we end this discussion?
UNIX was just pushed by AT&T, Sun, HP, IBM, Compaq, Dec.

I let you sum how much money they invested into selling their UNIX workstations.

As for Python, Zope made it during the early days, plus the research labs and companies that employed Guido, and if you want a list,

- DARPA funding in 1999

- Zope in 2000

- Google in 2005

- Dropbox in 2013

- Microsoft in 2020

You can sum up Guido's salary as per those corporations.

Exactly! Thanks for proving the point. It was a collective effort of people/companies pushing a good programming language rather than a single company (Oracle) doing it. Not to mention that they license it in certain cases.
Indeed, C came for $0 with UNIX that AT&T wasn't allowed to sell and provided source tapes alonside a symbolic price, in a time where systems cost several hundreds $$$$.

C++ came on the same package as C compilers, some of which it was a compiler switch away.

Both were picked by OS vendors that tried to cater on top of UNIX clones.

Yep, zero marketing.

I thought Java was once the most popular language before Oracle bought it from Sun Microsystems.
They bought the whole of sun!

And yes, it was, though in the intervening years they've improved the language a lot IMHO.

It's way simpler actually.

Functional programming isn't the norm because — while it's extremely good at describing "what things are and how to describe relationships of actions on them" — it sucks at "describing what things do and describing their relationships to each other". Imperative programming has exactly the opposite balance.

I find the former to just be more valuable and applicable in 80% of real world business cases, as well as being easier to reason about.

Entity Relationship Diagrams for example are an extremely unnatural match to FP in my eyes, and they're my prime tool to model requirements engineering. Code in FP isn't structured around entities, it's structured in terms of flow. That's both a bug as well as a feature, depending on what you're working on.

Most of the external, real world out there is impure. External services, internal services, time. Same thing for anything that naturally has side effects.

If I ask an imperative programmer to turn me on three LEDs after each other, they're like: Sure, boss!

for led in range(3): led.turn_on(); time.sleep 1; led.turn_off()

If I ask an FP guy to turn me on three LEDs after each other, first they question whether that's a good idea in the first place and then they're like... "oh, because time is external to our pure little world, first we need a monad." Whoa, get me outta here!

Obviously with a healthy dose of sarcasm.

Don't get me wrong, for the cases where it makes sense, I use a purely functional language every day: it's called SQL and it's awesome despite looking like FORTRAN 77. I also really like my occasional functional constructs in manipulating sequences and streams.

But for the heavy lifting? Sure give me something that's as impure and practical as all of the rest of the world out there. I'll be done before the FP connaisseur has managed to adapt her elegant one-liner to that dirty, dirty world out there.

there are FPs that have and embrace side effects, not everything is haskell.
Your LED example is an interesting one. In the basic model of a computer architecture, the screen is abstracted as a pixel array in memory - set those bits and the screen will render the pixels. The rest is hand waved as hardware.

A pixel array can be trivially modelled as a pure datastructure and then you can use the whole corpus of transformations which are the bread and butter of FP.

A screen is as IO as it comes for the most average consumers of a screen, we aren't peeking into its internals.

And for me, that's the point of FP - it's not that IO is to be avoided, it's about finding ways of separating your IO from the core logic. I loosely see the monad (as used in industry) as a formalised and more generic "functional core imperative shell"

Now when it comes to pure FP languages, they keep you honest and guide you along this paradigm. That said, it's perfectly possible to write very impure imperative Haskell - I've seen it with my own eyes in some of the biggest proprietary Haskell codebases

But imperative languages don't generally help you in the same way, if you want to do functional core imperative shell, you need a tonne of discipline and a predefined team consensus to commit to this

q.e.d.
Would love to know what has been proved? Very up for an open and honest discussion.

I'm back to writing imperative after years of functional. I think it is a very pragmatic choice today to go with an imperative language but I find class-oriented programming to be backwards and I think functional code will yield something more robust and maintainable given how IO and failure are treated explicitly. I'm not quite sure where the balance tips between move fast but ship something unmaintainable vs moving slower but having something more robust and maintainable.

Programming in a pure language is quite radical, it's a full paradigm shift so it feels cumbersome especially if you've invested 10+ years in doing something different. I'd liken it to trying to play table tennis with your off hand in terms of discomfort. There are plenty of impure functional languages around - OCaml, Scala, Clojure, Elixir.... And Javascript (!?!?)

FP is relatively new as a discipline and still comparatively untrodden. What if equal amounts of investment occured in FP - maybe an equivalent of that ease of led.turn_on will surface.

And tbh it probably just looks like a couple of bits - one for each LED and a centralised event loop. Which so happens to have been a pattern which works quite nicely in FP but emerged in industry to build some of the most foundational things we rely on...

> In the basic model of a computer architecture, the screen is abstracted as a pixel array in memory - set those bits and the screen will render the pixels. The rest is hand waved as hardware.

It was. I still remember the days.

It was nice to be able to put pixels on the screen by poking at a 2D array directly. It simplified so much. Unfortunately, it turned out that our CPUs aren't as fast as we'd like them at this task, said array having 10^5, and then 10^6 cells - and architecture evolved in a way that exposes complex processing API for high-level operations, where the good ol' PutPixel() is one of the most expensive ones.

It's definitely a win for complex 3D games / applications, but if all you want is to draw some pixels on the screen, and think in pixels, it's not so easy these days.

Screen real estate size in memory increased by square law while clock speed and bus speeds increased only linearly, it was pretty clear that hardware acceleration was the way forward by the mid-eighties when the first GDPs became available. I even wrote a driver for one attached to the BBC Micro to allow all of the VDU calls to be transparently routed to the GDP for a fantastic speed increase.
I don't think you could have made the GPs point any better for them.
I don't know. What was the GP's point? That FP people like to think too much and sometimes you just want to get stuff done?

Or that FP purists don't know how to actually build useful things? Trololol it took Haskell until the mid 90s to figure out how to do Hello World with IO

To be honest FP is a moving target but I see it as one of the mainstream frontiers of PLT crossing over into industry.

I can accept that to some, exploring FP is not a good for their business requirements today but if companies didn't keep pushing the boat with language adoption, we'd still be stuck writing fortran, cobol or even assembly.

Once upon a time lexical scoping was scoffed at as being quaint and infeasible.

Ruby and Python were also once quaint languages.

Java added lambdas in Java 8.

Rust uses HM type inference.

So what was their point? That FP people spend too much time thinking and don't know how to ship? In which case - I'm grateful that there are people out there treading alternative paths in the space of ways to write code in search for improvement.

In any case their example was pretty spurious, anyone who's written real code in production knows IO boundaries quickly descend into a mess of exception handling because things fail and that's when patterns like railway oriented programming assist developers in containing that complexity

I have my share of gripes about Haskell (which I'm assuming is the language you have in mind when you're talking about a pure FP language), but even with the sarcasm disclaimer, this is a pretty extreme strawman.

This is the equivalent Haskell.

  turnOnThreeLEDs = for_ [1..3] (\i ->
    do
      LEDTurnOn
      threadDelay (10^6)
      LEDTurnOff
  )
or all in one line

  for_ [1..3] (\i -> do { LEDTurnOff; threadDelay (10^6); LEDTurnOff })
It looks basically the same.

EDIT: I would also strongly dispute the idea that FP is structured around flow instead of data structures. In fact I'd say that FP tries to reduce everything to data structures (this is most prominently found in the rhetoric in the Clojure community but it exists to varying degrees among all FP languages). Nor is SQL an FP language (the differences between logic programming a la Prolog and ultimately therefore SQL and FP is very very different).

FP's biggest drawback is that to really buy into it, you pretty much need a GC. That also puts an attendant performance cap on how fast your FP code can be. So if you really need blazing fast performance, you at least need some imperative core somewhere (although if you prefer to code in a mainly FP style, you can mainly get around this by structuring your app around a single mutable data structure and wrapping everything else in an FP layer around it).

What you wrote is imperative despite the fact that it’s written in Haskell.
I mean if pure FP is enough to write imperative code as well then the distinction between the two doesn't seem all that important to me. What would be a non-imperative equivalent to illustrate your idea?
Small examples of mutating state in Haskell are about as meaningful as small examples of pure functions in Java. Small examples are really easy to do and not that ugly, bigger more complex examples don't look so nice. The whole reason people want to use Haskell is because doing complex state mutations is horribly ugly and unergonomic so people don't do it, that is a feature of the language.
So, you recognize imperative is a subset of functional? :-P

do-blocks have perfectly functional semantics, so if you consider that to be imperative as well, this means that a sequence of instructions changing state is both imperative and functional, as long as you declare where the state is being handled in your code.

And yes, of course functional code can handle state. The good thing about this 'Haskell imperative' style is that it doesn't fall prey of side effects, the bane of imperative programs (uncontrolled side effects are NOT a good thing). In Haskell, you control why and where you allow them.

One could also make a language that has exactly the same visual syntax that C where ; is specified as a functional composition operator instead of a separation of instructions. These kind of mindgames are pointless - if your code is sequencing instructions, it's imperative ; if it's denoting it's functional
If you do that, then you have to admit different kinds of imperative code: C-imperative style that can modify any state in the application as side effects, and Haskell-imperative where you can only modify state explicitly declared as input to the procedure.

It's not just mind games, the difference has very real implications to the architecture of the whole program and the control you can exert over unpredictable side errors.

Well, you DID sneak a monad in there :-)
Rather unavoidable when turnOnLED's likely type is IO () ...
Rather unavoidable when you have types like IO() in the first place.
Use of that type is easily limited in Haskell code. For instance, in my chess counting project [1] only a few lines in Main.hs use IO (), while the other approximately thousand lines of code have nothing to do with IO ().

[1] https://github.com/tromp/ChessPositionRanking

So what?

The point is to make it easy to program imperatively (with effects, where relevant) while simultaneously reclaiming the ability to check for correctness and maintain laziness by default.

What's so good about implicit sequential evaluation? Shouldn't the effect ordering be explicit? Isn't explicit better than implicit?

> Isn't explicit better than implicit?

That's the point, isn't it. No, explicit is not always better than implicit.

(comment deleted)
Without knowing Haskell, it looks like there are bugs in the code. Specifically, there's no delay after LEDTurnOff in the first example, and you have the same function name twice in the second example.

If those are bugs, I'd forgive that. If those AREN'T bugs, then keep me far, far away from FP!

Also, what is the point of i? Clearly, each LED should have its own index, but then i is never used again. (I understand this could be pseudocode or there's a lot of other code not included.)

And the ranges are inclusive in Haskell? I feel like a lot of friction between Matlab and Python involves how each language's indexing/slicing/ranges are represented, so it's interesting to see each language's approach (indenting like Python, lower camel case, delays in us, etc.) --- but with every language difference, I'm personally less inclined to want to learn something new without a great reason.

Ah yes you are totally right.

I misread the initial example:

  turnOnThreeLEDs = for_ [1..3] (\led ->
      do
        turnOn led
        threadDelay (10^6)
        turnOff led
  )
It should be the above (i is changed to led), where I thought the original was automatically going to a new led and didn't realize that `led` was actually an integer and `turn_on` and `turn_off` are basically pseudo-methods (or extension methods). (The original code also only sleeps after turning an LED on, not off)

Indeed the second example is a typo that should have on vs off.

  for_ [1..3] (\led -> do { turnOn led; threadDelay (10^6); turnOff led })
The joys of writing code on mobile and too much copy pasting.

`i` is the same thing as `for i in...`.

Also yes ranges are inclusive.

Thanks for all the info. I want to give FP a proper try one day, and there are many different roads, but it's always a rocky start for me with a new language. Having a clear translation from one to the other is important, so I'm glad you updated this.

True that this matches the original example. I guess my mind filled in the second delay automatically when it noticed, "this isn't gonna blink to the naked eye!"

I'm at a very similar place to you at this point. It make sense for FP to be good at "describing relationships of actions" since the base unit of reasoning is a function, or an action.

The beauty of modern programming is that we don't have to stick to a pure example of either paradigm. We can use FP techniques where it makes sense and turn to imperative otherwise.

In you example, we could have a nice, purely functional model of an LED that enforces the invariants that make sense. We could then "dispatch" the updated led entity to an imperative shell that actually took the action. All without using the M-word!

I'm probably - unfairly - treating your example more seriously than you intended it, but I think I'm leading to the same conclusion as you at a slightly different place. I want to have a purely functional domain that I wrap in an imperative shell. Trying to model side-effects in a purely functional manner using something like applicative functors just doesn't give the productivity boost that I want.

> I use a purely functional language every day: it's called SQL

This is my favourite way to annoy FP advocates (despite probably being one myself). Every one is a closet mathematician in FP-land but no one wants to admit how beautiful relational algebras are.

> I want to have a purely functional domain that I wrap in an imperative shell. Trying to model side-effects in a purely functional manner using something like applicative functors just doesn't give the productivity boost that I want.

Funtional Reactive is a very good way to create that mix. Web Front developers have realized that, and that's the reason why most modern frameworks have been veering towards this model slowly, with Promises and Observers everywhere.

When you represent state as an asynchronous stream that you process with pure functional methods, you get a straightforward model with the best of both paradigms.

I like FRP, but prefer to imitate it in a synchronous manner now - I mainly work on the JVM and I've personally found debugging to be too painful when working asynchronously. If I need async then FRP is definitely the first tool in the toolchest that I'd reach for.

Elixir's pipe operator is a brilliant tool that I wish every language had. I mainly use kotlin day-to-day and definitely abuse the `let` keyword to try to get closer.

True, FRP doesn't need to be asynchronous; it's just a very good paradigm to support multi-process computation and module composition.

As I said above, it just happens to also be a very good at handling state without a fear of side effects.

> I want to have a purely functional domain that I wrap in an imperative shell.

Like you, I too think the ML-side of functionnal programming got it right. Sadly, their most popular language commited the unforgivable sin of not being written by Americans and is therefore condamned to never be as popular as Haskell. I console myself by using F# when I can.

My FP experience has mainly been with Scala (with haskell on side projects).

Is OCaml the ML language de jure?

Yes, it's the most actively developed and the most featurful.

F# is another interesting ML. It has less of the features which makes Ocaml interesting but it runs on .NET so you have access to a ton of library.

SML seems more niche. I don't think it sees much use outside of academia.

Interestingly, a lot of very popular languages are not authored by Americans:

- Guido of Python fame, is Dutch

- Stroustrup (C++) is Danish

- Lerdorf (PHP) is Danish

- Anders Hejlsberg (author of both C# and TypeScript) is also Danish

- Ruby is not as popular as it used to be, but Matz is Japanese.

- Java's Gosling is Canadian, but I'm not sure if that is the kind of American you had in mind

That covers a big chunk of Tiobe top 10. If anything Denmark is over-represented!

edit:

- Wirth (too many languages to list) is Swiss

It's not about the nationality of author. It's about where they worked from and with whom. Except Ruby which failed, all the languages you are talking about where developed in the USA.

Van Rossum moved to the USA in the 90s, got funds from DARPA and went to work at Google quite quickly. Stroustrup developed C++ while at Bell Labs in New Jersey. Lerdorf moved to Canada as a teenager before going to work in the USA. Hejlsberg made C# and TypeScript at Microsoft in Seattle. Yukihiro Matsumoto could be an exception but as you rightfully pointed Ruby always remained somewhat niche even after its move to Heroku in San Francisco. James Gosling is Canadian but did his PhD in the USA before developing Java at Sun. Wirth did its PhD at Berkley before moving to Standford where he did most of the work on ALGOL W what would become Pascal and did multiple sabbaticals at Xerox PARC.

Of that I agree. As discussed else thread this suggests that having a platform or sponsor is a strong contributor to a language success.
> Yukihiro Matsumoto could be an exception but as you rightfully pointed Ruby always remained somewhat niche even after its move to Heroku in San Francisco.

I'm not sure what you mean in terms of "its move to Heroku in San Francisco". Also, Ruby didn't "fail" and it's not niche (GitHub is written in RoR, as well as discourse). However, I would argue that Ruby remained relatively niche outside Japan until it was discovered by DHH and used for the Ruby on Rails framework (to this date, it's somewhat hard to find work in Ruby outside of RoR). DHH lived in Denmark at the time but moved to the US shortly thereafter.

When I checked, it seemed that Yukihiro Matsumoto moved to San Francisco to work for Heroku but that's after developing Ruby while in Japan.

> Ruby didn't "fail" and it's not niche (GitHub is written in RoR, as well as discourse).

Ruby definitely is a niche language. I have never seen used outside of the web and it's pretty much always mentioned with RoR. That doesn't preclude success stories developed with Ruby to exist.

It failed in the sense that it has little momentum and didn't gain much traction if you compare it to something like Python. In a way, it's somewhat comparable to Ocaml which was the "failure" I was mentioning initially despite being a nice language itself and seeing interesting development right now.

> When I checked, it seemed that Yukihiro Matsumoto moved to San Francisco to work for Heroku but that's after developing Ruby while in Japan.

I didn't actually know that, so fair enough. Still, I think DHH probably had a larger impact in popularising Ruby in the US (and, by extension, other parts of the world).

> Ruby definitely is a niche language. I have never seen used outside of the web

That's only if you consider the web to be "niche" and if you do that, then JavaScript is "niche" too.

It's true that Ruby outside of Ruby on Rails is somewhat rare, but several other successful technologies are other written in Ruby, for example:

- Homebrew (macOS package manager)

- Chef (server provisioning software)

- Vagrant (VM provisioning software)

- Cocoapods (iOS package manager)

> It failed in the sense that it has little momentum and didn't gain much traction if you compare it to something like Python. In a way, it's somewhat comparable to Ocaml [...]

I think you're way off base.

Yes, Python is extremely popular and Ruby can't compare overall - although I have a feeling that Ruby still overtakes Python when it comes to web dev, but obviously Python is huge in other areas and is also not exactly niche in web either.

But Ruby is #13 on TIOBE, while OCaml doesn't even feature in the top 50. Github and Discourse are only examples, we could also mention Airbnb, Shopify, Kickstarter, Travis CI and many others. I've personally worked at several Ruby companies, in fact I maintain a small Ruby codebase even now at my current company (although it's not our main language), etc.

Ruby had huge momentum in the 2000s and even early 2010s. It didn't catch on in the enterprises much, true, but it was the cool thing back when everyone was annoyed at the complexity of Java EE or the mess that was PHP back then. Ruby was also the language Twitter was originally written in before they migrated to Scala. It lost a significant amount of momentum since then and basically all of the hype (people migrated to Node, then later to Elixir, Clojure and co. and some like me jumped back to statically typed languages once they became more ergonomic), but it's still maintained by quite a sizeable number of companies.

More than that, RoR had an outsized influence on the state of current backend frameworks to the point where I claim that even one of the most heavily used frameworks today, Spring Boot, takes a lot of inspiration from it (while, of course, being also very different in many areas). I would also argue that Ruby inspired Groovy, which in turn inspired Kotlin, and that internal DSLs such as RSpec also were emulated by a number other languages later.

I'd also add Roberto Ierusalimschy (Lua) and José Valim (Elixir) to this list, both from Brazil. But as a fellow commenter points out, place of birth is less important, when compared to how well the author is integrated into the anglophone old boys network of computer science.
People need to be familiar with both approaches. And there's sillyness on both sides. I've seen influential imperative OOP programmers on stackoverflow model a bank account using a single mutable variable, even when they surely know accountants use immutable ledgers.

Most imperative languages since Fortran contain declarative elements, otherwise we'd be adding numbers with side-effects. Similarly most FP languages offer imperative programming. But the real power from FP comes from it's restrictions and yes, query languages are one such (excellent) application. Config languages and contract languages are others.

I agree with you but see another relevant reason: in FP, you HAVE to consider side effects 1) from the beginning and 2) completely, which as anyone can guess is quite a task.

In imperative you can just ignore it and produce objectively worse code, as you are not even aware of all side effects possible. And sure, for the LED project it wouldn't even matter, but the decision FP vs imperative is then more of a design / quality criterion in general - the notion of one being better than the other is just wrong.

Also a monad is much more complicated if you don't really understand it which makes judging it a bit unfair

What is a side effect? Getting the time? Pushing a result to an output channel? A debug printf? Setting a flag to cache a computation as an optimization? Is it not: evaluating a thunk? Implicitly allocating some memory to store the result of a computation?

Haskellers are trained to have a very inflexible view of what a side effect is. It is dictated by the runtime / the type system. In my views, there are lots of things that Haskellers call "side effects" that I would just shrug my shoulder on, and also lots of things that they do not call side effects but I care about them. It really depends on the situation.

This fixed dichotomy imposed by the language does more harm than good in my experience. NB: I'm aware that running a computation that for example gets the system time will get a different time it runs. That does not mean that I _have_ to consider it a side effect. I usually do not have a good reason to run the procedure multiple times and expect the runs to be totally identical by all means. In an imperative language, I have very precise control when this procedure runs.

Apart from language-imposed limitations (haskell is nowhere near the theoretical completeness of category theory, e.g. bottom-type), the "pure" nature of FP forces the use of abstract structures able to handle it (e.g. Monads), thus, wanting to write code, you first need to think even possible side effects trough to be able to write code containing them, which by definition is a stronger criterion of catching unwanted effects compared to imperative, where you can produce whatever you want. And sure, it is in no way a guarantee to produce good code, it is just a stronger condition. It effectively boils down do "assembly is just as good as C", and we see where it took us.

Anyone telling me to think it through as rigorous in imperative is lying to him/her self practically, unless they're actually verifying their code.

I don't know, I'm positive I'm not part of the sacred circle, but just a data point. The Haskell applications that I've managed to produce were all uniformly slow-compiling and unmaintainable. And I promise it wasn't for lack of thinking about "side effects".

In my view, the problem is that functional languages give you a toolset to compose functions (code) by connecting them in structures. In Haskell, that is made harder by the restricting type system (very limited language to do type computations) that you must champion, including a myriad of extension, which invariably lead me down to dead-end paths that I didn't know how to back out of without starting all over.

But Haskell's restricting type system aside, every programmer that I consider worth their salt has understood that it's not about the code. Good programmers worry about the data, not the code. Composing code is not a problem for me; I just write one code after the other, there isn't much else that is needed. I just think about aligning the data such that the final thing that the machine has to do is as straightforward as possible. Then the code becomes easy to write as a result.

The possibilities to design data structures in Haskell are obviously limited by its immutability. Which is, quite frankly, hilarious. "State" is almost by definition central to any computation - and Haskell tries to eliminating it (which of course is only an illusion; in practice we're bending over backwards to achieve mutability). For Haskell in particular, which does not even have record syntax, basic straightforward programming is often just not possible in my perception. I refuse to reduce a hard to use library like "Lenses" to do basic operations thing that should be _easy_ to code.

Even though Haskell is popular, and many programmers (including me) go through a Haskell phase, I haven't seen many large mature Haskell codebases (I know basically about Pandoc; and ghc if a Haskell compiler counts). Why is that?

As a concrete example, here is a video (and github link) of a concrete program that I'm currently working on, and that I think is not a bad program.

https://vimeo.com/605017327

I already have plans for improving it (especially the layout system), but overall it works pretty well and is reasonably featureful with little code. It's not perfect but "state" is certainly not a problem at all.

I can't tell you something like this can't be coded in maintainable Haskell, but I can tell you that _I_ wouldn't have managed, and googling around it doesn't seem like there are a lot of people who can do it.

I think trying to eliminate state and replicate the unnecessary state with getter is generally a good thing. One of biggest bug category programmers encounter `forgot to sync XXX` can be totally eliminated by this if you don't copy these state at first place.

But eliminate all of them... just looks silly to me. You need state anyway, why not just write them in a sane way?

> If I ask an FP guy to turn me on three LEDs after each other, first they question whether that's a good idea in the first place

a proper FP engineer would model the problem of turning on LEDs one ofter another as a set of states. A simple way would be a bit set of the LEDs, in an array where each element is the LED's on/off state, like ['000', '100', '110', '111'].

Then, the problem decomposes into two, simpler problems: 1) how to create the above representation, and 2) how to turn the above representation into a set of instructions to pipe into hardware (e.g., send signals down a serial cable).

The latter problem is imperative by nature, but the former - that of the representation of states, is very pure by design! So the FP model provides a solution that solves a bigger, more general problem of turning LEDs into patterns, and this solution is just one instance of a pattern.

So if your boss asks you in the future to switch the bit patterns to be odd/even (like flashing christmas lights), you can do it in 1 second, where as the imperative version will struggle to encode that in a for-loop.

I promise you, a "proper" C programmer will do the same thing, faster. And I say this as an FP fan!

A bad C programmer will write horrible spaghetti code, but it will probably be enough to do the job. A bad Haskell programmer will get absolutely nowhere.

If you think pure FP is great for this stuff, I think you need to explain why imperative languages are regularly used to win the ICFP programming contest (https://en.wikipedia.org/wiki/ICFP_Programming_Contest#Prize...).

> A bad Haskell programmer will get absolutely nowhere.

that's a feature, not a bug in my books! Maintaining code written by other bad programmers is the bane of my life (despite getting paid to do it, so i can't complain).

Heh, there’s something in that.

Now I’m wondering what the worst mainstream language is for maintaining somebody else’s legacy code. C can definitely be pretty bad... but I’m thinking maybe Perl?

you don't maintain perl - it's a write-once language. Every time you need to change it, you rewrite a new perl program to do exactly what you need ;D

the title of course, goes to javascript imho.

> So if your boss asks you in the future to switch the bit patterns to be odd/even (like flashing christmas lights), you can do it in 1 second, where as the imperative version will struggle to encode that in a for-loop.

I guess you are talking about embedded so I'll concentrate in the LED example. In embedded code size and performance matter, so you try to be as straightforward as you can be. And I think applying "your boss might ask you in the future" to every piece of code is what drives some development far beyond the complex point.

Should I spend a week creating a super-complex infrastructure for turning on/off some LEDs just in case my boss asks my to change the pattern? Should I spend a week thinking the right code-pattern, or trying to "solve a bigger, more general problem"? It's just 3 LEDs blinking... just write the damn for-loop!

At the end of the day, my microcontroller only "digests" sequential instructions. So the simplest thing (for embedded) is to think and feed the microcontroller with sequential instructions. All the rest is just ergonomics for the sake of programmer's comfort or taste.

I'll do the sequence. If my boss asks my to change the sequence, I'll change the sequence. It's not a big deal.

I don't know if in this case one would "struggle" modifying this particular for-loop. And I can think at least 3 five-minutes solutions in C that doesn't require FP to structure a program to quickly change the pattern if required.

I think functional programming is less popular simply because people just aren't good at it.

Functional programming is a good way to describe how a system works. By describe the input, the processing, the output. You describe the whole diagram.

However, in reality. People just sucks at thinking systematically. It's likely that the whole education system never taught you about how to do it.

Everyone was taught to do things step by step and smash the results together to see if it works instead of prepare everything before doing any actual work most of time. And if it actually need preparing, there is usually a pre made checklist for you to do it easily.

That type of thinking process isn't that common in our daily live. And of course no one is used to it.

But I think people should at least do it once. Even you are still programming interpretively afterwards. It could benefits you very much and make you a better programmer.

It's been ages since I've used a "real" functional language but wouldn't it be nice to parameterise externalities like time, and have events occur "spontaneously" that force application state to update? Kind of like interrupts. Or now that I think about it... it sounds a bit like React where DOM events etc force application state to update (in a pure fashion)

Has this been done before?

So your LED function in pseudocode looks like

  ToggleLeds(leds, t): 
    for each LED
      LED.power = (LED.start + 1s) > t ? ON : OFF
And this is invoked from main() as follows

  main():
    ToggleLeds(this.LEDs, Events.Time)
Where Events.Time is some kind of event stream which allows the runtime to reevaluate main() and any other dependent functions each time it's updated

edit: And to sidestep the obvious performance issue with the function being reevaluated every few microseconds :D you would implement something like this

  main():
    ToggleLeds(this.LEDs, Events.Time(ms=1000))
this is also a good talk in a similar vein, although aimed at haskell programmers, is really about any technology looking to grow into the mainstream

Gabriel Gonzalez – How to market Haskell to a mainstream programmer - https://youtu.be/fNpsgTIpODA