DEV Community

Cover image for On “superiority” of (functional) programming and other labels
Zelenya
Zelenya

Posted on

On “superiority” of (functional) programming and other labels

I'm not going to lie. There was a time when I used to believe that. I was convinced I only wanted to work with functional programmers because functional programmers are better programmers.

And I know others still think that way. Likewise, some believe that C programmers are better than Rust programmers, devs that use AI tools are better than others, and vice versa.

Reaching that kind of conclusion takes only a few logical (mis)steps. In my case, it was like this:

fp and good code

I care a lot about writing good, reliable software

I consider coding a craft. I want to be productive and write maintainable code that delivers value. I don’t want to do a sloppy job, introduce preventable bugs or tech debt, and get woken up in the middle of the night.

I know that functional programming is a great tool for that

I have years of experience with functional and non-functional languages and suck at the latter. The other day, it took me 3 pull requests (3 deployments to production) to ship a trivial change to a Ruby codebase. Maybe that’s a special case. However, I’m sure I write and read Scala code better and faster than Ruby, PureScript than TypeScript/JavaScript, and so on.

This misdirected me to a naive conclusion: A programmer who cares about writing good, reliable software should use functional programming.

In other words, if someone doesn’t use FP, they don’t care. Right? Cause, why someone interested in writing good code chooses not to? Why are they choosing to use inferior tools and do a sloppy job?

not-fp and good code

Turns out “I” am not the “programmers”. And there are other programmers (and other people).

Just because a tool helps me — doesn’t mean it helps others. People are different, environments are different, abilities are different, and so on.

For instance, some people are good at holding many things in their working memory and switching between tasks, but I’m not. When working on something, I easily get distracted by slack messages, switch contexts, and — poof — completely forget what I was doing. I need functional programming — I need small independent blocks that I can quickly joggle and work with.

Also, people have different values and preferences. Take, for example, abstraction and expressiveness (those are different concepts but related enough). There is plenty of room for choice here. I believe that a typical functional dev is leaning to a greater extent on both. Extracting abstractions, reusing functionality, and refactoring data types, among other things, are completely different experiences in Rust and Haskell. Similarly, handling optionality, errors, lists, and async programming in Scala and Java…

Graph

If you aren’t familiar with those or don’t care for them, the ordinary opinionated programming thing is generics. There are Go, Java generics, and Zig comptime, and there are plenty of programmers who prefer and are productive with each one.

fp and bad code

So, there are functional programmers who write good, reliable software, and there are other programmers who write good, reliable software. Which is one thing.

The other thing is that if we deceive ourselves with generic statements (like I did), it’s easy to forget that there are functional programmers who don’t write good, reliable software.

Graph

The reason I’m chewing on this topic is this other extreme. I haven’t noticed this in my earlier days, but it turns out that quite a number of FP devs just offload their thinking (or not thinking) onto functional programming, tangential theory, and other standards.

It’s easy to fall into this kind of attitude: FP is so formal and mathematical and whatever, so let’s just over-rely on it and don’t reason ourselves. You know? The people who seriously believe that “if it compiles, it works” or “if it’s according to the specification, it’s perfect”.

Totally shattered my devotion to functional programmers.

In other words, if someone is doing X, it doesn’t mean they have values associated with X, or X can imply any values in the first place.


Top comments (7)

Collapse
 
webbureaucrat profile image
webbureaucrat

I think part of it is some ambiguity in what constitutes "functional" programming. Like really "functional" refers to functions as first-class constructs, but the way it's used it tends to refer to the strictness of type checking and expressive over imperative style.

Being strictly technical, JavaScript is definitely a functional language. Functions are first-class, a ton of the browser APIs take advantage of that, and even "classes" are just syntactic sugar for closures. It's also a lot harder to write good code in JavaScript than any other language I'm aware of (excluding eso-langs like BrainF*ck), and it very aggressively rejects any of the benefits of FP that people usually list.

So I'm kind of getting away from saying "I like functional programming" in favor of saying "I hate runtime errors and like catching problems sooner, and I like knowing my program handles all cases". But that's a lot of words so... idk. Saying "functional" is easier.

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

I spot confusion in this words (not in a negative way 😅).

Functional programming is a paradigm, just as OOP and a handful more.

The tools are the language you use (with its native API), libraries, frameworks and so on and so forth, even your keyboard is a tool!

Depending on the paradigm your tools implement certain tasks will be easier while others might need a workaround and vice-versa. As examples, pure OOP is not good at reflection while pure FP is not good at handling side effects.

Stretching the keyboard example, a QWERTY layout typically requires you to press 3 keys to print { which is 2 keys in an ANSI keyboard.

Getting to know different paradigms can help you using the right tool for the job, specially in languages like JavaScript or Python, to name a couple, which are multi-paradigm.

Imperative, procedural, object-oriented, functional, event-driven, declarative... all is possible in both languages used as example.

If you have 10 tools but use a specific one all the time, it can't make of you a better developer, if anything it'll make you worse as you would probably be applying the golden hammer approach (when all you have is a hammer everything looks like a nail) rather than using the right tool for the job.

Of course being a Haskell engineer you use the tools Haskell offers and there's absolutely nothing wrong with it! 💪🏼 now force-using only one paradigm in multi-paradigm languages is quite a red flag

This post can help decipher a bit this topic:

Also, let me know if I misunderstood anything and feel free to roast my Haskell example in the post 😂

Collapse
 
zelenya profile image
Zelenya

Yeah, I agree that tools are tools, but it's easy to forget that. I wrote this as a reminder to myself and others.


However, this statement is not true:

pure FP is not good at handling side effects.

It's quite the opposite

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

Can you please dig deep on that affirmation? 🙏🏻 If I'm wrong about it I'd like to learn!

Thank you

Thread Thread
 
zelenya profile image
Zelenya

Monads and algebraic effects allow you to control side effects (and other effects) like nothing else. I've recently written a blog post that covers both and one about referential transparency that also covers this topic.

Here is a small (scala) example. If I have some computation (either created it myself or someone passed it to me), I can use it like any other value: modify it (before using it), ignore it, pass it somewhere else, use it multiple times, retry it...

def foo(someInput: String): IO[Unit] = 
  IO.println("Imagine this calls some other service")

// run it sequentially with different args
foo("first") >> foo("second")

// retry the computation if it fails
retry3Times(foo("input"))

// bonus, run two concurrently and cancel the looser
IO.race(foo("cache"), foo("full"))`
Enter fullscreen mode Exit fullscreen mode

On top of that, we can distinguish code with side-effects and code without, have fine-graned control over specific effects, and so on. All of this is "pure" fp.

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

Thank you so much for the example and your time!

I likely made a poor choice of words. I understand a monad as a generic structure in functional programming that handles side effects in pure functions. Would that be correct?

Meaning that, just with pure functions it is not conceivable to have side effects, as the result of calling the function might differ, making it impure?

E.g.

pure functions

sum(a, b) // 6
sum(a, b) // 6
Enter fullscreen mode Exit fullscreen mode

impure functions

let counter = 0;
incrementCounter() // counter = 1
incrementCounter() // counter = 2
Enter fullscreen mode Exit fullscreen mode

Maybe the definition above needs refinement 😅

Collapse
 
paulsebastianmanole profile image
Paul-Sebastian Manole

Yet, when you look at good OOP code, you can't help but notice the similarities to FP and even the use of some FP concepts, probably most importantly the use of Result/Either types instead of nullable types and immutable state especially in core logic, but also the use of FP libraries made for doing FP in OOP languages.

I believe the entire reason that this false dichotomy exists between OOP being bad and FP being good programming, is that the majority of OOP programmers don't do OOP correctly (programming focused on interfaces and messaging, not on state changes, object hierarchies and tight coupling, and overabstraction with factories, builders, facades, adapters, and all these things invented to "fix" bad OOP), while the majority of FP programmers do it quite well, because the basic principles are simpler and lead to less complicated abstractions at runtime.