š¹Ā Hate reading articles? Check out the complementary video, which covers the same content.
I saw a tweet once saying, āFP doesnāt worth it. Itās very time-consuming, hard to understand, hard to read. Iām fine with some bugs caused by mutability and side effectsā. I was so happy to see it; itās a great filter.
Like other ātechnologiesā and things in general, FP is not for everyone. When choosing a job or picking a community, values matter.
Itās vague and not explicit, but I gravitate to FP (languages, concepts, communities, and so on). I know that under this umbrella, Iāll find people and jobs with similar values. While working with others doesnāt bring me joy.
To clarify:Ā Itās not about fake corporate values like loving our customers, transparency, and others. I wanna talk about actual things we care about.
Disclaimer: I can not speak for everyone; there is no one FP and no FP community. My experience comes from various places, as Iāve been in the pot for a few years: FP conferences, online communities, and jobs (in Europe, the US, Haskell-first, Scala-first... companies). For the purpose of this topic of FP values, Iāll cover the most common trends Iāve observed and experienced.
Do the types and values align
NaivelyĀ we might say that all languages and communities care about:
- Performance
- Security
- Multiplatform support
- ā¦
Obviously, nobody wants to write not-performant, insecure code. Right?
It depends. Because you canāt have it all.
For instance, there are operating systems specifically focused on security and have to sacrifice performance, out-of-the-box experience, some hardware-compatibility, or all at once.
On the contrary, what language comes to mind when you think about simplicity? Would it sacrifice performance and security to make things simple? Probably. Because simplicity is their core value.
So, what are the core FP values?
Core FP values
Curiosity
Iāll start with a personal favorite: curiosity, which is the coolest part of our job. There are so many different ways of doing things, so much to learn and explore. FP is even better in this regard.
For instance, there is still no standard way to organize an app or handle errors. Some might find it annoying. For me, itās the opposite! Weāre not stagnating ā weāre constantly trying to come up with better approaches. We donāt want to settle onĀ 23Ā ādesign patternsā and use them for everything for years.
We donāt have to do what everyone else does; we can find our way.
I alsoĀ wantĀ GHC to be able to innovate ā¦ I think innovation is a foundational part of Haskellās attractiveness and culture
ā Simon Peyton Jones (a lead developer of theĀ Glasgow Haskell Compiler)
Language and library developers are on the same page ā constantly innovating. Both Scala and Haskell are (at least partially) research languages. Not everything sticks ā someoneās curiosity and research paper is someone elseās maintenance burden, but you donāt know if you donāt try.
There is another tiny problem here. The Aha moments! What a joy. Most people, when they learn something, get a dopamine kick, which might lead to an addiction. Which might lead to devs getting high on math and failing to deliver.
Robustness
We donāt like null-pointer exceptions, compiler-preventable bugs, and waking up in the middle of the night. We prefer to write robust code.
š”Ā Note that even the core values arenāt equal. For example, some people value robustness less than curiosity (for example, in research or academia).
And it comes at a cost. In FP land, you canāt pretend that only the happy path exists ā you have to deal with the unexpected: optionality (missing values), handle errors, be explicit about I/O, etc.
Other values aside, Haskell and Rust are quite beloved by blockchain and fintech startups. Using less error-prone language contributes to the reliability and robustness of the code we write.
Additionally, FP devs accept more rigorous testing techniques and analysis (such as property-based testing and formal methods).
If it seems tedious, remember that mistakes in code can be expensive and harmful. At the same time, we write the code that we have to maintain tomorrow, next week, next quarterā¦
Maintainability
On top of that, we donāt like one-off scripts, boilerplate, poor types (we prefer rich type systems), and invariants outside of code (in the comments or developersā heads). Robustness and maintainability come hand in hand. The more robust the code is today, the easier it is to maintain it tomorrow.
Maintainability is one of the cornerstones of FP and far more valued than in other communities.
head :: NonEmptyList a -> a
In this example, we explicitly state that the list should not be empty. Itās in the functionās interface ā not in the comment or assumed in the functionās body. We must ensure weāve constructed a valid list when we call the function.
Refactoring is easy ā there are no whopper frameworks or inflexible design patterns. We donāt have to worry about the global state or assumptions.
Refactoring is cheap in Haskell so you don't need to get things right the first time
You donāt have to be afraid of changing your codebase!
We also love composability. When you have small, independent, and composable pieces, reasoning about them is more straightforward. It lets us develop them in isolation while simultaneously building larger components from smaller ones.
Expressiveness
We donāt have to suffer pursuing maintainability and robustness, when we have powerful tools. Most of the FP languages and libraries are quite expressive.
This is my favorite example from work experience: we had a data transformation pipeline, which accepted warehouses with stocks, and our task was to update all existing shoe stocks. At some point, the requirements changed, and the input data was remodeled.
š”Ā The actual details and code donāt matter, donāt stress over it. Iāll cover this in detail somewhere else in the future.
Before:
- there is a list of warehouses with inventories;
-
inventory
is optional; -
name
andstock
are also optional;
[
{
"inventory": {
"shoes": [
{
"name": "CHUCK TAYLOR ALL STAR LIFT",
"brand": "Converse",
"stock": 4
},
{
"brand": "Morrison"
}
]
},
"location": "London"
},
{
"location": "Berlin"
}
]
After:
- the
warehouses
field of response is optional now; - there are multiple inventories per warehouse (the
inventory
field becameinventories
).
Code changes:
- add one
_Just
; - change
inventory
toinventories
;
_allShoes =
-- warehouses . traverse . inventory . traverse . shoes . traverse
warehouses . _Just . traverse . inventories . traverse . shoes . traverse
_existingStock = _allShoes . stock . _Just
over _existingStock (subtract 1) warehouseResponse
The changes in our code were quite minimal because we were using flexible tools.
Thatās why FP languages are fantastic for prototyping. Itās an industry standard that most prototypes go to production. In which case, weāre safe and ready to go. Otherwise, adapting the code to catch up with changing requirements is fast.
Another side of the coin is over-abstraction. Letās revisit optics as an example:
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
or
type Lens s t a b = forall p. Strong p => Optic p s t a b
These are very expressive types. But also painful to work with, especially as a beginner: itās not easy to grok, and errors arenāt friendly at all (as the types s t a b
suggest).
Another example is Scalaās http4s: HttpRoutes
is ājust an aliasā for a Kleisli. Which sounds cool. As cool as taking an ice bath. Itās too much. I just want to send a couple of jsons over the wire.
There are dozens of other examples, and it can be very frustrating.
Sometimes expressiveness leads to simplicity, but sometimes it leads to complexity. Weāll talk about this later.
Perfectionism
Now, last but my least favorite core value:Ā perfectionism, which, for example, can come from obsession overĀ robustnessĀ or over-pursuingĀ expressiveness. Scala? Not expressive enough! Haskell? Not expressive enough! Dependent types? Not enough ā¦
This can be discouraging, especially in the work environment.Ā PerfectionismĀ is stressful, expensive, and counterproductive. While dreaming of perfection, we end up with nothing instead of having something good or great.
Some FP devs would rather spend weeks reimplementing a library than use or adapt an imperfect existing one. Others would start working on the compiler (existing or new) just because a feature is quirky. And then people burn out cause itās a lot of work and pressure.
But itās not all bad. Iām grateful I can use all the polished software (if it ever gets published).
Many libraries widely used in production have been stuck on version 0.+
for years because authors think that the API is not perfect. Which I guess is the compromise.
Interlude
What can be done with Java, can be done with Go, and can be done with Haskell. You donāt need to do FP ā itās not ultimately better. Nothing is. If the person goes out of their skin trying to convince you that X is ultimately better than Y, they are probably biased; it has something to do with what they deeply care about. If youāve ever seen companies doing ārewritesā, youāve probably noticed that the reasons are rarely purely technical. Is it politics? Or is it values related?
We may say, therefore, that modern technology has deprived man of the kind of work that he enjoys most, creative, useful work with hands and brains, and given him plenty of work of a fragmented kind, most of which he does not enjoy at allā¦we might do well to take stock and reconsider our goals.
ā E.F. Schumacher, Small Is Beautiful: Economics as if People Mattered
Why do anything? Just for the money? For financial stability or job security? Then the rest doesnāt matter ā pick the most-mainstream-top-ranked tech and call it a day. āNobody ever got fired for choosing IBMā, right?
Or is there more to it? Intellectual challenge? Growth? Community? Innovation? Sustainability?
When I started programming, it was fun ā it fulfilled my creativity and curiosity. And it still does, but at some point, it became less romantic: ceremonies, JIRA, eye-rolling meetings, wobbly requirements, and so forth. And when I get a moment to code, I have little patience for null-pointers, boilerplate, silly bugs, archaic design patterns, semicolons on every line, and other nonsense.
Not FP values (yet?)
Letās go through some values that a developer might expect but probably wonāt get from FP because they clash with the core values or are just low on the priority list.
Growth
The first one is evident based on the popularity and state of FP: thereās minimal effort put into marketing and growth.
You might have heard of Haskellās motto: āAvoid Success-at-all-costā. We donāt want to sacrifice robustness, perfectionism, and the ability to innovate (curiosity). Mainstream approval doesnāt worth it.
And it seems like most devs donāt mind. Small is beautiful. Til you have to look for another job š
Beginner-friendliness
In some sense,Ā growthĀ is linked toĀ beginner-friendlinessĀ (orĀ approachability). Hard to grow if the onboarding experience is challenging.
- Little (or no) documentation and learning materials.
- General complexity of things.
(Just recall what we talked about in theĀ ExpressivenessĀ chapter).
[In a sarcastic tone] Of course, we want more users. But if the choice is between making somethingĀ approachableĀ or moreĀ abstractĀ (for example), the cool abstraction always wins. Also, some say itās not an option to diminish the language or library to make things easier for new users.
I think itās just excuses. There are no technical sacrifices here. Just time and effort. Plenty of languages and technologies manage to be quite friendly to beginners on top of their āmore technicalā values. I hope the situation gets better sooner than later.
Simplicity
As I alluded to earlier, sometimesĀ simplicityĀ gets a hit. This value is still up for debate. Occasionally, there are initiatives to commit toĀ simplicityĀ (for example, seeĀ Simple HaskellĀ andĀ Boring Haskell), but they have yet to stick around.
Again, notice how it conflicts with current core values.
For instance, itās hard to predict whereĀ curiosityĀ will lead you. And itās hard to resist weird abstractions, quirky unmaintained libraries, and experimental compiler extensions.
The languages, tools, and libraries have a lot of complexity. When we useĀ abstractions, we hide some complexity, making it easier to use. But easy isnātĀ simpleĀ ā complexity is still there, just hidden.
The simplest code is no code because code is a liability. But this fact is generally neglected. I think because itās fun to write functional code, so YOLO.
Stability
Another disputed value in FP is stability.
Haskell and Scala have their roots in academia, so innovation is crucial to them.
So, on the one hand, they donāt want to restrict the research possibilities for the sake of corporate users who wish for stability and no breaking changes.
Every year,Ā State of Haskell Survey ResultsĀ shows that upgrading the Haskell compiler breaks a significant amount of code, and people would rather stay on older versions.
A similar situation in Scala.Ā Scala 3 has been around for years, but still, a lot of companies stay on Scala 2 because of the breaking changes ā the migration is out of reach for some.
But somehow, on the other hand, standard libraries (e.g., Haskellās base) are ātooā stable ā they are full of unsafe and slow functions that developers should generally avoid. So why do we keep them? Because we afraid to break all the learning material that relies on it? I donāt know. How many books are there anyways?
Debuggability
Last and the least, letās talk about debugging.Ā The funny thing about debugging ergonomics: itās not that FP devs donāt debug or like to suffer doing it; itās just not something we do that often.
Once more, itās all tied to the core values: the more time you spend on robustness and perfecting your code, the less time you have to waste debugging. And if we need to find a bug, we haveĀ other tools and approaches. Functional code is composable ā we donāt need a ārealā debugger; we can inspect code snippets in isolation.
The principal problem with debugging is that it doesnāt scale. (ā¦) in order to catch bugs, we often need to be able to run with sufficiently large and representative data sets. When weāre at this point, the debugger is usually a crude tool to use (ā¦) Using a debugger doesnāt scale. Types and tools and tests do.
ā Ben Deane, an experienced game developer who worked on Goldeneye, Medal of Honor, StarCraft, Diable, World of Warcraft, and so forth
šĀ I took this quote from Daniel Lemire's I do not use a debugger
A few cases
Before we wrap this up, I want to reemphasis that these values are not some noble truth.
ā ļøĀ Note: Iām only focusing the values that Iāve covered.
Elm
For example, Elm has a different view on these, emphasizing beginner-friendliness and simplicity. This works well for their specific use-cases. At the same time, itās limiting and comes at the cost of expressiveness and curiosity, which irritates a big group of FP devs. Just try calling an effectul JS function out of Elm.
Iām not sure about perfectionism and stability. The compiler hasnāt had a release since 2019. And elm-devs are quite content with this. I donāt know; I donāt have enough knowledge here.
- ā¬ļøĀ Beginner-friendliness
- ā¬ļøĀ Simplicity
- Robustness
- Maintainability
CuriosityExpressiveness- āĀ Perfectionism
- āĀ Stability
Rust
š” Note that Rust isnāt really an āFP languageā, but FP devs gravitate towards it, and you might guess why based on the values.
Rust is another ecosystem that strives to be beginner-friendly. If it actually is doesnāt matter. What matters is that they focus on and prioritize it. Most libraries come with a book, and the over-abstraction is not endorsed. For example, instead of having one expressive concept to deal with optionality, errors, lists, and async/await, Rust has different constructs for each. Itās annoying for some FP devs but great for beginners.
I donāt have to tell you about Rustās growth, the hype train is blazing. You must live under a pile of rocks if you havenāt heard of it.
On top of robustness, the community aims to get shit done ā there is no unhealthy perfectionism weāve talked about. I love it. There are production-ready libraries for everything.
But because robustness is so much valued, maintainability suffers. I might be biased, but Iāve heard it from more than one Rust dev. When you start working on a new feature, if you pick a wrong approach, itās pretty penalizing to go back and change anything down the road. Changing ownership and lifetime in one place bubbles up through all the usage.
Sometimes you can't even swap two seemingly-innocent lines because they have to be in a specific order to satisfy the borrow checker.
- Robustness
- ā¬ļøĀ Beginner-friendliness
- ā¬ļøĀ Growth
- āĀ
Maintainability - āĀ Curiosity
ExpressivenessPerfectionism
In summary
The work that we do and the technologies we use are reflections of our values. And as you might have noticed, you canāt have them all.
If you havenāt done it recently, I encourage you to (re)examine your values. What are your values? Does your work align with your values?
And if youāre doing FP, what do you think of its values? Am I missing something? Iād like to hear about other perspectives.
References:
- Scott McCloud, āUnderstanding Comicsā
Top comments (1)
Elm is the most perfectionist language I'm aware of except for Idris. I say that because there are only two ways to get a runtime exception in Elm.