This article seems to conflate strong type systems with functional programming, except in point 8. It makes sense why- OCaml and Haskell are functional and were early proponents of these type systems. But, languages like Racket don’t have these type systems and the article doesn’t do anything to explain why they are _also_ better for reliability.
Thank you for saying that. I regularly attend the International Conference on Functional Programming, which grew out of the LISP and Functional Programming conference. Except for the Scheme Workshop, which is the reason I attend, it might as well be called the International Conference on Static Types. Almost all of the benefits of functional programming come from functional programming itself, not from static types, but one would never get that impression from the papers presented there. The types are all that anyone talks about.
I get your point about ICFP drifting into “types, types, types.” I don’t think FP benefits are only static typing or immutability, pure-ish core/imperative shell, and explicit effects matter a lot even in dynamic languages.
My angle was narrower: static types + ADTs improve the engineering loop (refactors, code review, test construction) by turning whole classes of mistakes into compiler errors. That’s not “what FP is”, it’s one very effective reliability layer that many FP ecosystems emphasize.
Static types and ADTs are orthogonal to being FP, as Rust clearly shows. But to speak in terms of FP when those are the important things for you is just wrong since even non FP languages now have ADT, including also mainstream languages like Java, Kotlin, Dart, C# and more.
Even purity is not something exclusive to FP, D and Nim also support separating pure from impure functions. And if you ask me, the reason not many other languages have support for that is that in practice, it has been demonstrated again and again that it’s just not nearly as useful as you may think. Effects, as in Unison and Flix, generalizes the concept to include many more concepts than just purity and may perhaps prove more useful in general purpose programming, but the jury is still out on this.
I worked through https://htdp.org (which uses untyped Racket), and funny enough, that's what really for me thinking about type driven development. The book gets you to think about and manually annotate the types coming in and out of functions. FP just makes it so natural to think about putting functions together and thinking about the "type" of data that comes in and out, even if you're using a dynamically typed language.
You don't need a strong type system or even really ANY compile-time type system for this strategy to work! I use all these techniques in plain JS and I can still get the benefits of correct-by-construction code style just by freezing objects and failing fast.
I agree. So I write tests. I use architecture to defend against the risk of super-rare code paths where I wouldn't rapidly notice if they broke. I dogfood so I find prod bugs before users do.
None of this seems that new. Even people who write TS code still write tests and still ship bugs and still have to think about good architecture patterns, like the ones in the linked post.
It's a bit ironic that now we went down the static rabbit hole so much that we don't realize it's more the opposite: by adding more to your language and compiler to enforce static checks, you turn the compiler itself more and more into a runtime that you are just going to run ahead of time, according to a potentially vastly different set of rules compared to your real runtime. There is no reason why you couldn't do something like this by will but it's just not worth it after a certain point clearly.
I'm not personally aware of any companies doing this in plain JS aside from my own (I am co-founder/CEO of a two-person startup). I really like working in plain JS. It feels malleable where TS code feels brittle, almost crystalline. Even though I don't have compile-time types there's still only a small handful of different shapes of objects in the core of my software (far fewer than the average TS codebase, I'd wager), and it shouldn't take long at all for people to learn the highly consistent naming conventions that tip you off to what type of data is being handled. The result is that I'd expect that it would only be a handful of days learning the mental model for the codebase before the average person would find it far easier to read the JS code as opposed to TS code, thanks to the lower amount of visual clutter.
I also ship code super fast. When I find bugs I just fix them on the spot. When I find variables named wrong, I just rename them. The result that I often smash bugfixes and features and cleanup together and have a messy git history, but on the flip side you'll never find bugs or naming deceptions that I've left sitting for years. If something is wrong and I can reproduce it (usually easy in functional code), the debugger and I are going to get to the bottom of it, and quickly. Always and only forward!
> […] it shouldn't take long at all for people to learn the highly consistent naming conventions that tip you off to what type of data is being handled.
I’ve used languages with an approach like this. The difference in what I’ve used is that you separate the conventional part from the rest of the name with a space (or maybe a colon), then only refer to the value by the non-conventional part for the rest of the scope. Then the language enforces this convention for all of my co-workers! It’s pretty neat.
Sure! Let’s say I want to enforce that a variable only ever holds an integer. Rather than put the conventional prefix and the name together, like this:
var intValue = 3;
…I separate the conventional prefix with a space:
int value = 3;
…so now my co-workers don’t need to remember the convention – it’s enforced by the language.
I wasn't talking about Hungarian notation. I meant more like if you see a variable named `user` or `activeUser` you know that it's going to contain a predictably-shaped data object that describes a user. E.g. it will always have a `user.id` property. I would never call an string-ish ID a user, then. I would call it `activeUserId` or `userId` or just `id` if the distinction between those was already obvious from context... But that's very different from writing `strUserId` which I never do: I try to make sure my names always convey semantic distinctions.
Mhm! Exactly! In the system those other languages use, once you see the variable’s declaration:
User activeUser
…you’ll always know that `activeUser` contains a User value – something that might have an `Id` property. And the convention is enforced by the language, so it’s easy to communicate. These semantic distinctions are very useful, I agree.
Haha I knew you'd say that. I'm not pretending there aren't advantages to strict systems of declared types. There are many! But my point is simple to the point of stupidity: there's just more stuff on screen when you have to write `User` twice. In this simple example it looks trivially simple to write the word "user" twice, but in a reasonably-complex real example the difference will be far more noticeable.
I should add a few more things: much of how I got here was exposure to Facebook's culture. Move fast and break things. React with prop types. Redux. Immutable.js. I did UI there on internal tools for datacenter operators and it was a drinking-from-the-firehose experience with exposure to new programming philosophies, tools, and levels of abstraction and refactoring velocity beyond anything I had previously encountered. Problems which in other companies I had learn to assume would never be resolved would actually consistently get fixes! Well, at that time. This was before the algorithm was fully enshittified and before the disastrous technopolitical developments in the way facebook and facebook messenger interact with each other.
Perhaps the most direct inspiration I took from there though was from the wonderful "opaque types" feature that Flow supports (https://flow.org/en/docs/types/opaque-types/) which for reasons known only to Hejlsberg and God, Typescript has never adopted; thus most people are unfamiliar with that way of thinking.
Yes, I am wondering if opaque types would be difficult to implement somehow in TypeScript? It should really be part of TypeScript if at all reasonably possible.
I'm not that familiar with the TS internals. They'd have to add a keyword to the language which could break stuff. The smart move would be to reserve the `opaque` word a few versions in advance of introducing the feature that gives it a meaning
I've seen it pointed out that the main point of functional programming is immutability, and that the benefits mostly flow from that. I haven't really learned much of any lisp dialect, but my (admittedly fuzzy) general perception is that this is also the preferred way to work in them, so my guess is that's where the benefit in reliability might come from.
Correct. If things are mutable, then in most languages, there can be spooky action at a distance, that mutates some field of some other object or does so indirectly via some calls. This then can change how the thing behaves in other circumstances. This style of programming quickly becomes hard to fully grasp and leads to humans making many mistakes. Avoiding mutation therefore avoids these kinds of faults and mistakes.
Agreed, I conflated FP with “typed FP.” My claim is mainly about static types + ADTs/exhaustiveness improving refactors/review/tests. Racket can get FP benefits, but absent static typing you rely more on contracts/tests (or Typed Racket), which is a different reliability tradeoff.
The term "functional programming" is so ill-defined as to be effectively useless in any kind of serious conversation. I'm not aware of any broadly accepted consensus definition. Sometimes people want to use this category to talk about purity and control of side effects and use the term "functional programming" to refer to that. I would advocate the more targeted term "pure functional programming" for that definition. But in general I try to avoid the term altogether, and instead talk about specific language features / capabilities.
> The term "functional programming" is so ill-defined as to be effectively useless in any kind of serious conversation.
This is important. I threw my hands up and gave up during the height of the Haskell craze. You'd see people here saying things like LISP wasn't real FP because it didn't match their Haskell-colored expectations. Meanwhile for decades LISP was *the* canonical example of FP.
Similar to you, now I talk about specific patterns and concepts instead of calling a language functional. Also, as so many of these patterns & concepts have found their way into mainstream languages now, that becomes even more useful.
to add a grain of salt, some of the lisp world is not functional, a lot of code is straight up imperative / destructive. but then yeah a lot of the lisp culture tended to applicative idioms and function oriented, even without the static explicit generic type system of haskell.
Sure, but that's part of my point in agreeing that definitions of "functional programming" are muddy at best. If one were to go back to say 1990 and poll people to name the first "functional programming" language that comes to mind, I'd wager nearly all of them would say something like LISP or Scheme. It really wasn't until the late aughts/early teens when that started to shift.
maybe FP should be explained as `rules not values`. in scheme it's common to negate the function to be applied, or curry some expression or partially compose / thread rules/logic to get a potential future value that did nothing yet
I usually define functional programming as "how far away a language is from untyped lambda calculus". By that definition, different languages would fall in different parts of that spectrum.
Yeah, I know Rust isn’t everyone’s favorite but I’d expect at least some awareness that we’ve seen a lot of reliability improvements due to many of these ideas in a language which isn’t focused on FP. I ended up closing the tab when they had the example in TypeScript pretending the fix was result types rather than validation: that idea could be expressed as preferring that style, an argument that it makes oversights less likely, etc. but simply ignoring decades and decades of prior art suggests the author either isn’t very experienced or is mostly motivated by evangelism (e.g. COBOL didn’t suffer from the example problem before the first FP language existed so a far more interesting discussion would be demonstrating awareness of alternatives and explaining why this one is better).
Sure, my point was simply that it’s not as simple as the author assumes. This is a common failure mode in FP advocacy and it’s disappointing because it usually means that a more interesting conversation doesn’t happen because most readers disengage.
I get why it reads like FP evangelism, but I don’t think it’s “ignoring decades of prior art.” I’m not claiming these ideas are exclusive to FP. I’m claiming FP ecosystems systematized a bundle of practices (ADT/state machines, exhaustiveness, immutability, explicit effects) that consistently reduce a specific failure mode: invalid state transitions and refactor breakage.
Rust is actually aligned with the point: it delivers major reliability wins via making invalid states harder to represent (enums, ownership/borrowing, pattern matching). That’s not “FP-first,” but it’s very compatible with functional style and the same invariants story.
If the TS example came off as “types instead of validation,” that’s on me to phrase better, the point wasn’t “types eliminate validation,” it’s “types make the shape explicit so validation becomes harder to forget and easier to review.”
I would keep in mind how much the title communicates your intentions on future posts. The conversation about preventing invalid states has to be somewhat inferred when it could have been explicitly stated, and that’d be really useful comparing other approaches - e.g. the classic OOP style many people learned in school also avoid these problems as would something like modern Python using Pydantic/msgspec so it’d be useful to discuss differences in practice, and especially with a larger scope so people who don’t already agree with you can see how you came to that position.
For example, using the input parsing scenario, a Java 1.0 tutorial in 1995 would have said that you should create a TimeDuration class which parses the input and throws an exception when given an invalid value like “30s”. If you say that reliability requires FP, how would you respond when they point out that their code also prevents running with an invalid value? That discussion can be far more educational, especially because it might avoid derails around specific issues which are really just restating the given that JavaScript had lots of footgun opportunities for the unwary developer, even compared to some languages their grandmother might have used.
Once you accept Curry-Howard, untyped FP languages are hard to take seriously as a foundation for reliability. Curry-Howard changes the entire game. FP and strong types were clearly meant for each other.
Untyped FP languages can be productive, flexible, even elegant (I guess) but they are structurally incapable of expressing large classes of correctness claims that typed FP makes routine.
That doesn’t make them useless, just, you know. Inferior.