>In banking, telecom, and payments, reliability is not a nice to have. It is table stakes.
This reliability isn't done by being perfect 100% of the time. Things like being able to handle states where transactions don't line up allowing for payments to eventually be settled. Or for telecom allowing for single parts of the system to not take down the whole thing or adding redundancy. Essentially these types of businesses require fault tolerance to be supported. The real world is messy, there is always going to be faults, so investing heavily into correctness may not be worth it compared to investing into fault tollerance.
Agree with the framing: in payments/telecom, reliability is often achieved via fault tolerance + reconciliation more than “perfect correctness.”
My point is narrower: those mechanisms still benefit from making illegal transitions unrepresentable (e.g. explicit state machines) so your retries/idempotency don’t create new failure modes. It’s not correctness vs tolerance, it’s correctness inside tolerant designs.
Not if proving so is more expensive to do than not. Reliability is only a means. Not the end. Also the human parts of the business would need to be simplified in order to model them. If deviate from the model that could invalidate it.
Agree on the economics. I’m not arguing for full formal proofs; I’m arguing for low-cost enforcement of invariants (ADTs/state machines/exhaustiveness) that makes refactors safer and prevents silent invalid states. Human processes will always drift, so you enforce what you can at the system boundary and rely on reconciliation/observability for the rest.
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.
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'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!
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.
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.
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.
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.”
Wait. This doesn’t make sense to me. Statically typed programming languages cannot be deployed nor can they run with a type error that happens at runtime. Untyped languages CAN run and error out with a type error AT runtime. The inevitable consequence of that truth is this:
In the spectrum of runtime errors statically typed languages mathematically and logically HAVE less errors. That by itself is the definition of more reliable. This isn’t even a scientific thing related to falsifiability. This comes from pure mathematical logic. In science nothing can be proven, things can only be falsified. But in math and logic things can be proven and it is provable that static types are more reliable than untyped.
It is definitely not vibes and feels. Not all of banking uses statically typed languages but they are as a result living with a less reliable system then the alternative and that is a logical invariant.
There are many reasons why someone would choose untyped over typed but reliability is not a reason why they would do this unless they are ignorant.
I don't consider a human subjects study to be "hard evidence".
So, we can safely disregard these papers. They got exactly the result that they sought out to get, and the papers were published because they confirmed the preexisting groupthink.
You mean psychology? There’s no hard evidence there. The papers you’re citing are using human subjects in that sort of way. It’s pseudoscience at best
Medicine that involves testing human subject response to treatments is very different from the papers you’re citing and does involve falsifiable theses (usually, definitely not always).
Yep, in practice a lot of orgs treat reliability as a cost center until an outage becomes a headline or a regulatory incident. I’ve seen the same tension in payments/banking: product pressure wins until the risk is visible.
Part of why I like “make invalid states unrepresentable” approaches is exactly that: it’s one of the few reliability investments that can pay back during feature work (safer refactors, fewer regressions), not only during incidents.
Honestly, as someone else who does a lot of data plumbing, there is so much FTP servers with excel sheets being used as the means for official clearance processes.
There are constant data bugs in the feeds provided by major exchanges, market makers, etc, and so many iffy business rules that are basically all encoded in 100+ tab excel sheets.
Maybe this article focuses on a very specific niche of banking, but most of it is tied together with FTP and excel sheets.
I think the author would be shocked just how flaky a fundamental banking protocol like SWIFT is.
I’ve worked in Brazilian banking stacks that were literally FTP + spreadsheets for years. So yes, the ecosystem is often messy and protocols can be flaky.
That’s exactly why I argue for stronger internal modeling: when the boundary is dirty, explicit state machines/ADTs + exhaustiveness + idempotency/reconciliation help ensure bad feeds don’t silently create invalid internal states.
All the line items are decent things, worth doing, but the claim about how much following the line items would improve reliability is super exaggerated.
> [Most production incidents] are due to the code entering a state that should never have been possible.
I have never seen evidence that this is even remotely true, and I've been looking at software reliability research in the last few months.
Instead, it is more true that most production incidents are due to the system entering into one of thousands of unsafe states which were possible and latent in production potentially for years. In a sufficiently complex system—all interesting and important software projects—functional programming is not strong enough a tool to prevent even a sliver of potential accidents.
> Arguments that these degraded conditions should have been recognized before the overt accident are usually predicated on naïve notions of system performance. System operations are dynamic, with components (organizational, human, technical) failing and being replaced continuously. — https://how.complexsystems.fail/
A few mention on tests, but I expected more. The main value of pure functions is that now their behavior is representative in tests. In fact, I'd argue that all you need for reliability is determinism and tests of all equivalent scenarios. functional programming (and immutability) are only helpful to the extent that it's easier to have representative tests, but not necessarily required.
Agree, I didn’t give testing enough space. A proper treatment would’ve doubled the post, so I’m writing a separate follow-up on testing.
Pure functions/immutability help a lot because tests become representative and cheap. I’d only push back on “tests of all equivalent scenarios” being sufficient, the space explodes and many real failures live at I/O/concurrency/distributed boundaries. My intended claim is that FP/ADTs/types reduce the state space and improve the ROI of tests, not replace them.
I want to be a contrarian and argue with this, but my daily praxis is generally to take a betteridges law approach to most argumentative absolutes and also false dichotomous headlines and question them. Reading the other comments to the effect that the conferences are now strong typing gabfests and insufficiently about FP per se reinforced this feeling.
Reliability should be simpler with FP but so much depends on correctness of the runtime and IO.
Erlang and the "run correctly or die" comes to mind as well. The system is either working or is off. When being off is fatal, Erlang seems to shrug and say "maybe next karmic cycle" maybe this too is a better approach?
Functional programming: no, functional programming as in: the final program consists in piping functions together and calling the pipe. In my opinion, that tends to get in the way of complex error handling.
The problem being that raising Exceptions at a deep level and catching them at some higher level is not pure functional programming. So your code has to deal with all the cases. It is more reliable if you can do it, but large systems have way too many failure points to be able to handle them all in a way that is practical.
> that tends to get in the way of complex error handling.
Agree. In Java, Streams allow you to process collections in a functional style. This feature enables concise, expressive data manipulation with operations like map, filter, and reduce.
Some people point out that Java's checked exceptions spoil the simplicity and elegance of Streams by forcing you to handle exceptions.
But that's not a reason to not have checked exceptions, it is a reason to not do functional style composition when methods can throw exceptions. Streams was invented for collections, which tend not to throw exceptions. If proper error handling is important don't do Streams.
The Java streams are cool and I like them, but they're not a replacement for a functional type system or a functional language.
`map` is a lot more than a fancy for-loop for lists and arrays; it's about abstracting away the entire idea of context. Java streams aren't a substitute for what you have in Haskell.
If you have strong types, it is still possible to make a mutable thing, that will be mutated from the other end of the program and that will introduce bugs, that can be hard to find. If you are doing FP on the other hand, at least change always results in new objects, with structural sharing at most. This excludes a whole category of bugs.
> If you are doing FP on the other hand, at least change always results in new objects, with structural sharing at most. This excludes a whole category of bugs.
Not if you program it with a mutable god object to mimic creating a new big state, then you have exactly the same kind of issues.
The issue is if you try to program a transaction flow using object oriented programming, that is not very good, and most work programmers do revolves around involves flows. But when it doesn't then functional programming isn't a very good or reliable solution.
I like good type systems, too, but they won't save you from bugs that are better addressed by fuzz testing, fault injection testing and adversarial mindset shifts.
100%. Types don’t replace fuzzing, property tests, chaos, or adversarial thinking. They just move one slice of bugs from runtime to compile time and make refactors safer.
In hindsight I should have positioned types/ADTs as one layer in the reliability toolbox, not the toolbox.
I think there is a strong case that ADTs (algebraic data types) aren't so great after all. Specifically, the "tagged" unions of ADT languages like Haskell are arguably pretty clearly inferior to the "untagged" unions of TypeScript or Scala 3. Because the latter actually behave like a logical "or" rather than an artificial construct that needs to be wrapped and unwrapped.
I at first thought it was nice to save the wrapping, but you save yourself sooo much pain, ugliness and mistakes pattern matching trying to distinguish the types once you just use tagged unions.
I think you’re both pointing at the same tradeoff: “untagged” unions feel lighter, but you often pay it back in ad-hoc narrowing (shape checks/heuristics) and ambiguity once variants overlap.
Tagged unions/ADTs make the discriminant explicit, which is exactly why they tend to be reliability-friendly: exhaustive matches + explicit constructors reduce “guessing” and refactor breakage.
That said, I agree the ergonomics matter, TS-style discriminated unions are basically “tagged” too once you add a kind field, for example.
> "tagged" unions of ADT languages like Haskell are arguably pretty clearly inferior to the "untagged" unions of TypeScript
dude .. wut?? Explain to me exactly how this is true, with a real world example.
From where I stand, untagged unions are useful in an extremely narrow set of circumstances. Tagged unions, on the other hand, are incredibly useful in a wide variety of applications.
This reliability isn't done by being perfect 100% of the time. Things like being able to handle states where transactions don't line up allowing for payments to eventually be settled. Or for telecom allowing for single parts of the system to not take down the whole thing or adding redundancy. Essentially these types of businesses require fault tolerance to be supported. The real world is messy, there is always going to be faults, so investing heavily into correctness may not be worth it compared to investing into fault tollerance.
My point is narrower: those mechanisms still benefit from making illegal transitions unrepresentable (e.g. explicit state machines) so your retries/idempotency don’t create new failure modes. It’s not correctness vs tolerance, it’s correctness inside tolerant designs.
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.
In dynamic languages, you are the type system.
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!
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.
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.”
But this isn't a falsifiable claim. We cannot possibly know if this is true or not.
- Not all of banking and telecom use functional programming or even static typing.
- Functional programming often leads to write-only incomprehensible code; the exact opposite of what you need to have a reliable system.
- There's no hard evidence that static typing improves reliability. Only vibes and feels.
In the spectrum of runtime errors statically typed languages mathematically and logically HAVE less errors. That by itself is the definition of more reliable. This isn’t even a scientific thing related to falsifiability. This comes from pure mathematical logic. In science nothing can be proven, things can only be falsified. But in math and logic things can be proven and it is provable that static types are more reliable than untyped.
It is definitely not vibes and feels. Not all of banking uses statically typed languages but they are as a result living with a less reliable system then the alternative and that is a logical invariant.
There are many reasons why someone would choose untyped over typed but reliability is not a reason why they would do this unless they are ignorant.
I'm curious how you came to that conclusion?
https://pleiad.cl/papers/2012/kleinschmagerAl-icpc2012.pdf
https://www.deepdyve.com/lp/springer-journals/an-empirical-s...
So, we can safely disregard these papers. They got exactly the result that they sought out to get, and the papers were published because they confirmed the preexisting groupthink.
Medicine that involves testing human subject response to treatments is very different from the papers you’re citing and does involve falsifiable theses (usually, definitely not always).
Haha as someone who has worked in one of these domains using FP even - I wish the people in charge agreed with you!
Reliability is a cost center and Product-oriented Builders treat it as such.
Part of why I like “make invalid states unrepresentable” approaches is exactly that: it’s one of the few reliability investments that can pay back during feature work (safer refactors, fewer regressions), not only during incidents.
There are constant data bugs in the feeds provided by major exchanges, market makers, etc, and so many iffy business rules that are basically all encoded in 100+ tab excel sheets.
Maybe this article focuses on a very specific niche of banking, but most of it is tied together with FTP and excel sheets.
I think the author would be shocked just how flaky a fundamental banking protocol like SWIFT is.
That’s exactly why I argue for stronger internal modeling: when the boundary is dirty, explicit state machines/ADTs + exhaustiveness + idempotency/reconciliation help ensure bad feeds don’t silently create invalid internal states.
> [Most production incidents] are due to the code entering a state that should never have been possible.
I have never seen evidence that this is even remotely true, and I've been looking at software reliability research in the last few months.
Instead, it is more true that most production incidents are due to the system entering into one of thousands of unsafe states which were possible and latent in production potentially for years. In a sufficiently complex system—all interesting and important software projects—functional programming is not strong enough a tool to prevent even a sliver of potential accidents.
> Arguments that these degraded conditions should have been recognized before the overt accident are usually predicated on naïve notions of system performance. System operations are dynamic, with components (organizational, human, technical) failing and being replaced continuously. — https://how.complexsystems.fail/
Pure functions/immutability help a lot because tests become representative and cheap. I’d only push back on “tests of all equivalent scenarios” being sufficient, the space explodes and many real failures live at I/O/concurrency/distributed boundaries. My intended claim is that FP/ADTs/types reduce the state space and improve the ROI of tests, not replace them.
Reliability should be simpler with FP but so much depends on correctness of the runtime and IO.
Erlang and the "run correctly or die" comes to mind as well. The system is either working or is off. When being off is fatal, Erlang seems to shrug and say "maybe next karmic cycle" maybe this too is a better approach?
Functional programming: no, functional programming as in: the final program consists in piping functions together and calling the pipe. In my opinion, that tends to get in the way of complex error handling.
The problem being that raising Exceptions at a deep level and catching them at some higher level is not pure functional programming. So your code has to deal with all the cases. It is more reliable if you can do it, but large systems have way too many failure points to be able to handle them all in a way that is practical.
Agree. In Java, Streams allow you to process collections in a functional style. This feature enables concise, expressive data manipulation with operations like map, filter, and reduce.
Some people point out that Java's checked exceptions spoil the simplicity and elegance of Streams by forcing you to handle exceptions.
But that's not a reason to not have checked exceptions, it is a reason to not do functional style composition when methods can throw exceptions. Streams was invented for collections, which tend not to throw exceptions. If proper error handling is important don't do Streams.
`map` is a lot more than a fancy for-loop for lists and arrays; it's about abstracting away the entire idea of context. Java streams aren't a substitute for what you have in Haskell.
Not if you program it with a mutable god object to mimic creating a new big state, then you have exactly the same kind of issues.
The issue is if you try to program a transaction flow using object oriented programming, that is not very good, and most work programmers do revolves around involves flows. But when it doesn't then functional programming isn't a very good or reliable solution.
In hindsight I should have positioned types/ADTs as one layer in the reliability toolbox, not the toolbox.
Tagged unions/ADTs make the discriminant explicit, which is exactly why they tend to be reliability-friendly: exhaustive matches + explicit constructors reduce “guessing” and refactor breakage.
That said, I agree the ergonomics matter, TS-style discriminated unions are basically “tagged” too once you add a kind field, for example.
dude .. wut?? Explain to me exactly how this is true, with a real world example.
From where I stand, untagged unions are useful in an extremely narrow set of circumstances. Tagged unions, on the other hand, are incredibly useful in a wide variety of applications.