HACKER Q&A
📣 behnamoh

When is pure functional programming beneficial?


The way I think about Haskell programs is locked-in statements that tightly sit next to each other (i.e., function compositions) and nothing "leaks", as in no mutations are allowed (except in monads, like IO).

But I wonder if this is mostly a matter of taste. In small programs, the end result of a Haskell program is the same as a Python program. Is there a threshold after which Haskell's purely functional paradigm shines the most?


  👤 myaccountonhn Accepted Answer ✓
I wouldn't say there is any threshold where purely functional programming shines less. Fewer regressions and the system being more likely to "just work" makes it more fun to develop. So for interactive programs, servers, CLI tools, parsers et.c. purely functional programming is amazing. An elm developer reported that the prototype they wrote in elm ended up with less bugs than the actual production system. I personally experienced building a system and after I had written a few thousand lines, fixed all compiler errors and then it compiled and was just... done. No bugs where found in production.

In F#, an experience report came out where 350k lines of C# were rewritten in 30K of F# code. They also went from 3k null checks to 15 lines of null checks (Plus much more). Zero bugs were reported in the newly deployed system.[0]

Now with that said, there are exceptions where purely functional programming languages shines less:

- Places where the ecosystem is not quite as mature. If you're building a server and have to interact with Cloud services in Haskell, you'll have a bad time.

- Any kind of system where you need to do manual memory management, so systems programming, is badly suited for purely functional programming.

[0] https://www.youtube.com/watch?v=MGLxyyTF3OM&t=863s


👤 PaulHoule
Systems that have clear inputs and outputs. Parsers, compilers, stuff like that. SAT Solvers. Anything you could unit test without mock objects.

Mixed initiative systems (say UI code where you call into the framework and the framework calls into you) can go either way. Sometimes you can formulate the part of your code in a pure functional way which tames the chaos, if you can’t or that formulation is unnatural the chaos tames you.

System w/ immutable data structures lose a factor of two or so in performance in some cases. FORTRAN programmers won’t accept them, distributed “big data” systems will spend a lot of time marshaling and unmarshaling data and won’t accept any slowdown in their parsing code. The same could be true for something like that SAT solver.


👤 siraben
Purely functional programming in package management and system configurations is next-level.[0] When your package installation scripts no longer are represented as actions from one state to another but as a state that is either realized completely or not, things become a lot more deterministic, manageable, and compositional. In Nix, a package is a pure function that takes in inputs and produces outputs. Given the same inputs, we have the same outputs, and so caching becomes easy, for instance, along with extensive sharing.

As an active user and package maintainer I can't count the number of times I've decided to rebuild an old project that I haven't touched in years and it still working, while in the same session working on another project using up to date libraries.

There's also straight.el for Emacs[1] which has finally made Emacs config maintenance far better for me.

[0] https://nixos.org/

[1] https://github.com/radian-software/straight.el


👤 stiiv
It's all about minimizing cognitive overhead. This happens in a few ways when you use strict functional patterns and a tight domain model:

- You can safely make more assumptions about how your program works - You can rely on your type checker like a to-do list when extending your program

These two benefits require more discipline, but they make extension and support brain-dead easy.

For the record, you don't need Haskell to enjoy these benefits, you can get them in Python or TypeScript too, as long as you're disciplined in how you design your system.


👤 ashton314
Functional programming enables local reasoning about your program: since effectful code is clearly signaled with various monads, you can be sure that some pure code is such just by looking at the type. This lets me think less about the surrounding context do you understand a given block of code.

So, I would say the advantage of a pure functional programming language is that it enforces and encourages writing pure code. You can still write in the style to some degree in python, but you don’t get all of the idioms language support to make functional programming really nice.

I notice a difference in my code when I write in a pure language.

There are some cases where you really want in place mutation— graph algorithms are good example—but for most other things, I feel like functional programming wins because of how you can think about your code, and less about code actually can or cannot do.


👤 easeout
Structure and Interpretation of Computer Programs section 3.1, Assignment and Local State, gives an overview of the costs and benefits of adding the concept of mutation to an otherwise referentially transparent language. It's from the opposite perspective: we know functional programming, now what's so great and so dangerous about imperative?

http://sarabander.github.io/sicp/html/3_002e1.xhtml


👤 Leftium
"FP works great for distributed systems, and most software written today is distributed."

According to Grokking Simplicity[1], good functional programming isn't about avoiding impure functions; instead extra care is given to them. These functions depend on the time they are called, so they are the most difficult to get right.

Compare to pure functions: given the same input, the results are always the same regardless of when the function was called. So they are easier to reason with.

There is actually a level that is even easier to reason about than pure functions: plain data.

So functional programmers prefer data over pure functions over impure functions.

The reason to avoid "leaks" is because impure functions cause everything that calls them to also become impure. However, it is OK to use local mutation contained to the function itself. Sometimes mutation is simpler and doesn't affect the purity of the function that contains it.

Another good book on practical functional programming is Domain Modeling Made Functional[2]. Actually all of Scott Wlaschin's material[3] is very good!

[1]: https://www.manning.com/books/grokking-simplicity

[2]: https://pragprog.com/titles/swdddf/domain-modeling-made-func...

[3]: https://fsharpforfunandprofit.com/


👤 kristiandupont
Mutable State is to Software as Moving Parts are to Hardware :-)

https://kristiandupont.medium.com/mutable-state-is-to-softwa...

I find personally that there are some areas where FP shines -- anything that is or could be a CLI that takes some input and returns some other output is well suited. A GUI program is the manipulation of state, and it is poorly suited, though many sub parts might not be.


👤 Arubis
I consider taste a benefit, regardless of what your taste may be. There’s other things to weight, too, of course! But if the end result is that you enjoyed your time more than you would have if you were slinging Java, that’s not without merit.

👤 EdSharkey
Imperative shell, (ever-expanding-through-relentless-refactoring) functional core seems to work pretty well for practical applications. Modern Java gets the job done in that regard.

What I don't like is the concentration level required for functional productions over equivalent imperative routines. I have found it difficult to impress upon junior developers the testing advantages of functional programming and in refactoring pairing sessions with them, they quickly lose the thread and go all doe eyed.

I feel like the current functional programming paradigms favor terseness that completely obliterates the thought process that led to the production. So, even if junior has the aha moment when we reach the end of the production, and understands what we just achieved there in 6 lines of dense code, they'd be at a loss to retrace it themselves in the future because the thought processes of each operator in the production require too much simulation space in the head.

That, and libraries like RxJS that layer in concepts of time and observables and higher-order observables with sneaky completion states that only stretch the mind further because the true semantics of the program are coupled with under-documented quirky edge cases of the operators. Running into one of those while pairing with junior is not exactly confidence building.

Long-form programming with named pure functions might help, but then I suppose you can lose the terseness and can get lost in a sea of 1-line named functions.


👤 graypegg
You can write a confusing unreadable Haskell script, just like you can write a confusing unreadable Python script. There’s nothing magic about functional paradigm programming.

It does, IMO, give you less foot guns related to state though. While I still actually like other paradigms, functional programming really limits how clever you can be with state. You can only pass things around immutability via parameters (At least with Haskell specifically) instead of making a confusing taxonomy of objects with unique APIs, for example.


👤 pyinstallwoes
I mean, there's different ways to describe this but it comes down to how you want to think about state. The more pure you get the more you can focus on operations or transformations, as functional programming is really about pipelines of transformation of code & data. By having such a constraint over state it allows one to potentially find it more easier to reason about whatever domain they are solving for. However in order to achieve that there is a certain set of hoops one has to mentally go through and maintain in order to think clearly about said problems. For some that is worthwhile for others not. I generally trend towards code that is functional regardless of the language.

Related concepts that are relevant at a high level is the comparison between declarative code and imperative. Code that is functional tends to be declarative and in a way "is what it seems to be." There is also a transitive property that allows the makeup of a thing to also be its own runnable representation which makes portability or idempotent things easier to achieve.

I dare say that original "OO" in terms of "message passing" is functional in nature, but object-oriented somehow became something it is not.

Erlang is probably the best representation of a functional language ideal in my own personal opinion when weighing in the ecosystem and capabilities of the language and tooling.


👤 brudgers
a Haskell program is the same as a Python program

  Beware of the Turing tar-pit 
  in which everything is possible 
  but nothing of interest is easy.
      -- Perlis:54
https://web.archive.org/web/20120806032011/http://pu.inf.uni...

Haskell is syntactic sugar on top of the Von Neumann architecture (likewise Python, Rust, COBOL, Smalltalk, etc.)

The Von Neumann architecture is inherently stateful. Anything with volatile memory is.

Functional programming is an idiom, not a technology. To the degree functional programming is a technology, it is one for analog computers.

Good luck. My time has been wasted trying to be clever.


👤 mattlondon
There is probably as much value in just disciplining yourself to try and write mostly in pure functions (regardless of language - I'd recommend Javascript or Typescript as it is a joy to use and highly expressive).

This has benefits when working in large, long-lived systems where engineers can't keep everything in their head, and people come and go over the years. It is easier to reason about, to refactor, and to test, when you know that the "blast radius" of your function is just the return value.


👤 taeric
Consider your program a model of the problem that you are automating. Depending on what you are modeling, different paradigms and styles of program will help in different ways.

What we call functional programming today is often reducing the problem down to the easy parts. Which is great and should not be discounted. But don't think of it as any more valid of a solution than anything else. And pure numerical models of the problem can often be just as fruitful in solution speed, while being utterly foreign to most programmers.

OO gets lambasted as you try and model each individual thing in a problem. In my mind rightfully so. That said, it is very common for us to model things in a way that is very "object" based. Consider the classic model of an elevator system and how that is coded by most people. You will have a set of elevator objects with states that they currently represent.

But, you could also do this fully numerically with a polynomial representing things. Expand your toolset to use generating functions, and you can even start building equations that can count the number of solutions at any given state. Still just a symbolic model, but very very different from the OO or even FP style of model that most programmers will write.

Better for the problem you are solving? Probably not, during the exploration side of things. But get the problem into a formulation that you can translate into a SAT style, and you can feed it to SAT solvers that are far more capable than programs most any individual can write. Translate the solution back to your representation for display to the users. (Or just general use in your program, as you move to the next thing.)


👤 solomatov
Systems where you need to prove correctness at least informally. It’s much easier to prove correctness of a functional program than imperative one.

👤 gilmi
A different thing to look at is what enforcing referential transparency in your program means.

Referential transparency means that when we bind an expression to a name (e.g. `y = f x`), the two are interchangeable, and whenever we use `y` we could just as well use `f x` and vice versa, and the meaning of the code will stay the same.

Enforcing referential transparency in a programming language means that:

- We need to have more control over effects (purity)

- We can use substitution and equational reasoning

The value of referential transparency for me is that I can trust code to not surprise me with unexpected effects, I can use substitution to understand what a piece of code is doing, and I can always refactor by converting a piece of code to a new value or function because referential transparency guarantees that they are equivalent.

Because of that I feel like I can always understand how something works because I have a simple process to figure things out that doesn't require me keeping a lot of context in my head. I never feel like something is "magic" that I cannot understand. And I can easily change code to understand it better without changing its meaning.

Referential transparency is freeing, and makes me worry less when working with code!

---

The other part that is very notable about Haskell is one of its approaches to concurrency - Software Transactional Memory. Which is enabled by limiting the kind of effects we can perform in a transaction block:

https://www.oreilly.com/library/view/parallel-and-concurrent...


👤 chriswarbo
It's nice to have things pure-by-default; and sprinkle in whatever effects, etc. as desired. (Like Haskell, Idris, Agda, etc.)

For example, I've worked on a lot of Scala code which represented optional values using `Option[T]` (~= Haskell's `Maybe t`); represented possible failures using `Try[T]` (~= `Either Exception t`); and I even used the Cats library to make code polymorphic (i.e. for all `M: Monad`, or `M: MonadThrow`, or indeed `Applicative`, etc.). The older ScalaZ library is a popular alternative to Cats which does similar things. Concurrency is currently quite diverse in Scala: we mostly used `Future[T]`, but there are a bunch of alternatives out there like `Task`, etc. Likewise there are other Scala libraries/frameworks which model side-effects differently, e.g. Zio uses algebraic effects.

However, since Scala is not pure by default, we can't count on any of these fancy types to actually prevent the problems they're meant to address. For example, we can only use `Option` alongside `null`: the latter can't be avoided, since Scala isn't pure; in fact `Option` should technically introduce even more null checks (since `x: Int` might be `null`; but `y: Option[Int]` might be `null` or `Just(null)`!). Likewise, `Try` can only be introduced alongside the possibility of any exception being thrown at any time. And so on.

In contrast, we might have a project e.g. in Haskell, where `IO` appears in all of our types. Such code might use all sorts of confusing effects (e.g. early returns, mutable variables, concurrency, etc.), and may blow up in all sorts of ways, just like Scala, Python, etc. Yet at least they're labelled as such, and we're able to write other values and functions which actually do what they claim (modulo totality, unless we're using Idris, Agda, etc.).


👤 abeppu
I think the issue isn't for the most part the kind of problem the code addresses, but the social context in which the code is used. Functional programs can allow for easier reasoning about programs or paths later, which can be useful when updating, refactoring, debugging etc.

Are you _literally_ writing a simple script to do a single task which you won't save into version control? Pick whatever style will be fastest to write + run. But if you'll need to re-read it in 6 months to figure out how to re-use a portion in a different context, or the next engineer will need to migrate something out from under it in a year, or whatever else, then picking a style based on readability, testability, and analyzability can be worth it -- provided the rest of your team is on the same page.


👤 markusde
IMO pure FP is nice because _compositionality_ is nice. If my problem has lots of simple data structures that represent mathematical objects, then usually I find that most of things I want to do with them can be succinctly modeled with pure function compositions. When my problem gets more complex, including but not limited to state, monads can restore that compositionality in a way that works with the rest of my pure code.

This property is true to varying degrees outside of pure FP too. For example in Rust I find that expression-orientation makes programs fairly easy to compose, modulo thinking about ownership.


👤 atomicnature
Pure functions can be visualized as static mappings from inputs -> outputs. It's like a simple table mapping inputs to outputs. Given a set of inputs, you are guaranteed the same outputs on every invocation.

In a complex world with many hidden aspects, unleashing unpleasant surprises all the time, working on purely functional components makes it easier to:

1. understand 2. test 3. debug 4. extend

These are significant benefits of the functional approach (at function/component level) in my pov.


👤 friend_and_foe
I've been diving into Haskell recently and FP in general and although I lack the deep CS knowledge I find what you said to be nice: everything is atomic.

I personally do a lot of complex math and so I'm using it for programs that are doing a lot of math. I still haven't got my chops enough to do a lot of procedural and external stuff with it yet, I find a higher level language is much easier for that stuff, but when doing math I find that it's much less easy to make mistakes that make it into your production code with Haskell.


👤 shortrounddev2
When you run a company and need to decide what your tech stack is, it becomes as much a question about the labor pool as it is about technology, maybe even more so.

If you choose to write your core product in a functional programming language, you now have to hire an entire team of people who can program FP. If you decide to write your core product in Javascript, Node, or Typescript, you can now hire bootcamp grads and a handful of senior engineers to keep an eye on them


👤 andrewl
John Backus, one of the pioneers of functional programming, said “Functional programming makes it easy to do hard things, but functional programming makes it very difficult to do easy things.”

👤 1-more
I like pure FP languages for writing UIs. In particular I like languages where you can't have any undefined behavior. I like modeling the state of a page/form/component/whatever as a bunch of constructors on a custom type, each containing exactly the data you need to render the page/form/component/whatever in that given state. I guess this isn't so specific to UIs but it's where I have the most experience. Modeling something as a state machine is cool and good.

👤 adubois1
Pure FP is fantastic for business applications with complex rules and logic. Shines in projects that leverage Domain Driven Design: https://pragprog.com/titles/swdddf/domain-modeling-made-func...

👤 yboris
Recommended (free) reading: Professor Frisby's mostly adequate guide to functional programming

https://mostly-adequate.gitbook.io/mostly-adequate-guide/

See discussion: https://news.ycombinator.com/item?id=22654135


👤 bigtechdigest
Pure FP is fantastic when you're doing Domain-Driven Design. Optics is brilliant for working with aggregates, functional core imperative shell to structure the dependencies within the applications and put the domain in the center, monads and applicatives to achieve different component composition styles.

👤 hyperthesis
IMHO the benefit of fp is it's easier to prove things. The downside is that it's more work: being able to show you've done something correctly is extra work on top of the actual doing of it correctly.

There's a point of view that if you haven't done it correctly, you haven't done it at all. There's another point of view that you just fix bugs as they found.


👤 schwartzworld
When you are expected to write tests for your code, you will be happier if that code uses functional patterns. Functional code can usually be tested without the complex mocking many developers are used to relying on.

For me, as a UI dev, there are a lot of repetitive tasks. FP allows me to know that the Lego pieces all work as they should.


👤 ezedv
I think it's beneficial when you want code that is easier to reason about, test, and maintain. It excels in scenarios where immutability and side-effect-free functions are crucial, such as concurrent programming and building highly scalable systems.

👤 Ono-Sendai
Can be useful for running untrusted code. You can bound your pure functional programs in time and space.

👤 agentultra
I work full-time as a Haskell programmer, I stream myself building various Haskell projects once a week at https://twitch.tv/agentultra, I maintain a couple of Haskell libraries, and I occasionally do other things too.

I've been a professional programmer for a little more than twenty years and have been using Haskell to this degree for about the last 4 or so. A good chunk of my career was dedicated to Python and C. I've done work in C++ and Javascript. Dabbled in Common Lisp in my spare time. Which is to say, I've ran the gamut of functional, imperative, object-oriented, etc.

I got started in OCaml first by taking the Inria course. I later tried Haskell by taking the University of Glasgow course. I often thought of myself as preferring FP but I always worked in languages with escape hatches where I could retreat to familiar territory. It was easy to rationalize this as, "using the right tool for the job." However thanks to some smart people in my life I decided to dip my toes in languages that were FP-first (OCaml) and later FP-only (Haskell).

> Is there a threshold after which Haskell's purely functional paradigm shines the most?

I think you just have to jump in the pool and go for it. There's no threshold. You can write scripts, programs, whole systems in Haskell. There's no point where, "it becomes worth it."

I'm certain at this point that you can write a relational database engine that is comparable in performance and features as any other in the market in Haskell. You can write video games in it. You can write short helper scripts and tools. Word editors, websites, compilers, etc.

The threshold is how willing you are to leave behind what you're already familiar with and learn a new way of programming that will make you feel like a complete beginner again for a while. Are you willing to re-learn programming over again? I don't know why this is surprising to people but if you started with a C-like language and picked up other C-like languages along the way, picking up Haskell isn't going to be as easy: there's nothing familiar you can use in your C-like knowledge that will be useful for a long while.

I've been doing it for a while now and there are benefits to it which is why I still program in Haskell and got a job writing code in Haskell. You likely already have a good deal of the tools needed to reason about how to design programs in a pure FP language: sets and high-school level algebra. You have notions built into the type system in Haskell like equality, relations, and constraints. Once you start jogging your brain on how to think this way you can design most of your program in types. And when you do it this way, there tends to be only one or a couple of ways to implement a program that satisfies those types... it's almost a mechanical process of filling in the holes.

From a practical perspective of a former Python programmer, I don't have to write a good deal of unit tests I would usually start with when building a Python program. Armed with a well-thought out handful of types the compiler will reject any program that isn't correct by construction which allows me to focus more of my efforts on things that are more important to me: is the logic correct, etc. I'm not testing, "are these things equal," "does this throw an expected error," "is this actually a valid function," etc.

Another weird thing that happens when you learn to program in Haskell is that control flow is kind of... represented in data types. Since Haskell is expression-oriented and execution is carried out by evaluation we get neat features like whole program analysis of all code branches. If I add a new constructor to a sum type, I get instance feedback on all the places where I need to now handle that case. This is super useful.

However... it can be a challenge getting to this place where you're comfortable with it. I had a hard time when I started a small Haskell project early on to make a simple web app. I was so used to logging being a single import away that having to learn all these new concepts in Haskell like, "monad transformers," just to add logging to my app seemed like a bunch of busy work for something that was, "solved," in my mind. The key to getting through moments like that is to forget your past experiences and just open your mind to being a beginner again.

It becomes useful later on when you realize that composition is so incredibly useful. You start to miss it when you go back to procedural languages and there are no guarantees and nothing composes.


👤 klysm
Imo pure functional might not be a good idea if you really care about performance a lot. Immutable data structures can corner you into suboptimal decisions sometimes.

👤 carapace
Functional is the way to go up until you have a problem that you need e.g. TLA+ to solve.

In other words, is your program a calculator or a robot?


👤 revskill
When you parse something instead of validation.

👤 yCombLinks
Theorem proving like lean4. Ie programs that aren't about state manipulation

👤 dqft
It becomes useful when you work with pure functional hardware.

👤 b20000
they are not

just like cargo bikes are not