DEV Community

Cover image for Friction in programming and why FP isn’t mainstream
Zelenya
Zelenya

Posted on

Friction in programming and why FP isn’t mainstream

The other day, I was working on a dashboard: I wrote the core of it in purescript in a couple of hours, and then I got stuck fiddling with css for half an hour. I gave up. I switched to the BE, added all the endpoints, logic, and queries; then I got stuck for another half an hour fiddling with schema derivation (for the api docs).

After that, I needed a break and finished the day. I had this bitter feeling — I’ve done a lot but haven’t finished the task, even though I was so close. I chewed on these annoying issues the rest of the day. I hate friction in coding. With all the meetings and firefighting, it’s already a challenge to find time to code and get into the flow. I don’t want to be interrupted by stupid little things.

Let’s talk about friction.

What does friction have to do with functional programming?

Disclaimer: Not saying that this is THE explanation for everything.

While I was chewing on this, I realized that functional programming is very guilty when it comes to friction. Regardless of the precise definition of FP, it’s intended to reinforce safer code and eliminate numerous bugs through types, abstractions, and functional design patterns. All of these come at a cost!

It’s the same as when you want to secure your app — you add auth, mfa, vpn, and so on. All of these add friction but have a purpose. At the same time, not all security frictions are created equal — some are extremely tedious. You know that feeling when you have to go FROM ssh’ing to production TO layers of vpns and tools?

Same with FP: some points of friction are intentional and others are not.

Types of friction

While outside of FP, it’s acceptable to focus only on the happy path and overlook the rest, it’s unacceptable in FP.

Imagine a function that fetches a role by user id (from postgres, for example). Somewhere in java land, you can say this function takes a user id and returns a role. Done. 1 input and 1 output. But it’s a lie. What about a database connection? It’s also an input. What if the user doesn’t exist or doesn’t have a role? There won’t be a role output.

In functional land, you have to be explicit about all the inputs (even a database connection), optional outputs, and even the fact that the function needs to access a database. All of this adds friction (for everyone who writes this function and uses it). But it also has a purpose. We want this. We don’t want bugs and errors at runtime.

Let’s label this “intentional friction”. It covers types (making them stronger and static), type classes, effects, and all the other things that make a programmer’s life better (but take time to master).

By inverse, this leaves us with “unintentional friction”. Vague error messages, flawed tooling, missing functionality or libraries, and everything that makes your blood boil.

Regardless of whether you agree with those categories, the line between the two is blurry. Many things that are not-100-percent-intentionally introduce the friction that eventually smoothes away. It could be small things or other ways of doing “familiar” things. Let’s take “looking for the right function” as an example. It’s a simple thing. However, if you are new to a language ecosystem and don’t know how to utilize dot-completion or tools like hoogle, it can break your flow: you stop coding, switch to browser to search for a function, get distracted, switch to check your email or slack notifications, and then you have to invest substantial effort to get back into coding (flow).

Friction breaks flow

I used to think that it’s just about steep learning curves or normal hurdles for beginners, but the problem is bigger — for me, it’s about all the things that can break the flow. And it sucks the most when various frictions interfere.

Let’s take an average java developer trying to pick up a new language. I think rust and haskell offer similar learning curves: a bunch of unfamiliar concepts, new things to worry about, different syntax, whatever.

But rust allows you to polish down the rough corners. For instance, unnecessary cloning, dynamic dispatch, and global state are generally discouraged; however, you can quickly use these and revisit your code later (in a second pass, for example).

Haskell doesn’t let you cut corners — it’s either a haskell way (one of many haskell ways) or the highway…

But it doesn’t mean that haskell is rigid or inflexible. Haskell is great for prototyping… When you know what you are doing. Because if you aren’t or return from a big break, it’s tough, not gonna lie. Even after all these years, when I return to haskell (or other fp language), I’m quite lost: how to glue the code? how to deal with errors? what to do about records? So many decisions and friction areas… I can hardly imagine how tough it must be for beginners.

That’s one of the reasons why people don't want to try new languages and technologies — they keep using what they know, even if it's not good, because they are already used to that friction. It's less scary than the new unknown frictions.

We talk about this a lot with one friend and occasionally wonder if it wasn’t a mistake for us to give up on spring. The framework isn’t perfect, but it eliminates most of the decision- and discovery-related friction, which is great.

In either case, there are still plenty of other frictions left, so…

What can we do?

Obviously, we all want someone to work on tooling, improve errors, write books, and reduce all sorts of friction. Usually, by “we”, we mean “someone else”. This won’t do! We should all take responsibility. I won’t ask you to contribute to open source or write books — I’ll ask you to start thinking and be more conscientious.

We should be more diligent with decision-making; for example, choose the right level of abstractions and fitting techniques. We shouldn’t always choose based on what we want to play with at the moment, but consider the longer-term consequences and the bigger picture. I can think of many examples from various communities of technical decisions that added unnecessary friction and had negative consequences for maintenance, adaptation, and the larger community. But those weren’t my decisions, and it’s not for me to criticize them right now.

I have a good and common example, which I am occasionally guilty of: strength of types.

Imagine we have an api that returns data for a role management dashboard. We need to add how many admins we have. How should we model it? It could be a string, an integer, an integer newtype, a refined type (for a natural number or something more precise — we might want to include or exclude 0). We can even bring it to the type level, so we can restrict some functions from compiling when the number is 0. What type is the best?

It might be tempting to have the strongest guarantees. But consider the volume of friction it will add for using the api, extending it, and onboarding new developers. Is it worth it?

Let’s say we chose the refinement type. What do we do if we start using a new storage library that doesn’t have the required instances for refinement types? What do we do if we need to add other numbers, for example, for super-admins and members? What if we want to ask a junior developer to update something small, and they have never heard of refinement types? Friction, friction, friction…

End

Coding in a state of flow is great, safe approachable code is great, and unintentional friction sucks.

Friction can be valuable. However, reducing unintentional friction enhances the flow and increases the likelihood that people will do the right thing. This requires a lot of work and awareness, and is easier said than done.

Looking at problems from a friction point of view can be fruitful, but remember it’s not a hammer that fits all.

With that being said, let’s extend the metaphor and look at two other things before wrapping up.

Appendix

Why haskell is not more popular?

A discussion about making haskell more popular arises multiple times a year. Every time with the same suggestions: better build tools, more learning resources and libraries, friendlier error messages and community, … and other things that reduce friction!pie chart

If you asked me what the haskell community should do to make haskell more popular, I could say focus on whichever of these reduces the most friction, and it would probably help. However, I believe the biggest friction is in people’s heads — haskell’s image. The perception of haskell magnifies its friction points.

For instance, most languages have mediocre tooling full of friction. Tooling is hard. But somehow, haskell tooling is perceived to be one of the worst. So, someone tries haskell, struggles with the tooling, and instead of persevering (as they would with other things), they give up because that was their expectations from the start. The meme lives and spreads.

So, unless haskell goes through a major rebranding, I don’t think it’s going to be popular. Bite-sized evolution isn’t enough.

And should haskell be popular? A whole other question…

What about llms?

On the contrary, I’m fascinated by how blunt llms deal with friction (so far). Changed some code and broke tests? Fuck it. Delete the tests. Need a slightly different function from what a library provides? Fuck it. Reinvent the wheel.

Remove or ignore anything that’s in the way. Zero friction tolerance.

One of the reasons some love llms and others hate them.

It is what it is.


Top comments (2)

Collapse
 
giullianosep profile image
Giulliano Ferreira • Edited

I'm not an expert in Haskell, but I don't even think the tooling is as bad as people think, I just use VS Code and it's fine. But I think the ecosystem is very fragmented, with many build systems like cabal, stack, nix, etc. Some libraries and frameworks are only available on stack (Yesod) others on Nix (IHP), all of this makes everything harder compared to more standardized languages like Go and C# where you just install it and the VS Code plugin and everything works out-of-the-box.

I'm sure a lot of people are interested in learning the language, but they lose interest due to this "friction"

Collapse
 
paulsebastianmanole profile image
Paul-Sebastian Manole

Friction in FP can be expressed simply: you have to handle all side effects explicitly and you have to think declaratively (lazy expressions), two things which are counter intuitive to most humans.