
Why I Prefer Functional Programming - allenleein
https://morgenthum.dev/articles/why-prefer-fp
======
fauigerzigerk
I think these sorts of examples dodge the real issue. Of course you can show a
(perhaps needlessly) verbose procedural example of an algorithm that lends
itself perfectly to FP and then demonstrate how FP is far more concise.

But the real issue, in my view, is what happens when the algorithm is not
ideally suited to FP, otherwise we're back to the cute inheritance hierarchies
in OOP textbooks.

Say we're dealing with something like the UK share matching rules [1], where
you can't simply process your items in sequential order (because the cost of
shares sold on a particular day may depend the cost of shares purchased on or
after that day).

The open question in my mind is whether pure functional languages scale well
to problems of real life complexity, ugliness and the occasionally crucial
optimization requirement.

Does FP fall apart completely in those circumstances or does it just degrade
to the point where procedural code was all along? I don't have an answer to
that, but it may depend on how dogmatically the purity constraint is enforced.

[1] [https://www.accaglobal.com/uk/en/technical-
activities/techni...](https://www.accaglobal.com/uk/en/technical-
activities/technical-resources-search/2014/december/share-matching-rules.html)

~~~
willtim
Haskell is perfectly capable of expressing imperative algorithms and mutable
state. Such algorithms are still expressed using pure functions, pure
functions that return IO actions.

~~~
lolinder
And x86 assembler is perfectly capable of expressing pure functions. Such
algorithms are still expressed using subroutines, subroutines that save and
restore all state to its start state.

The question isn't whether a language can express an algorithm, Church and
Turing already proved that. The question is whether the language can do so
scalably. We want to see nontrivial examples of FP applied to messy, side-
effect-laden domains in a way that rivals other representations for clarity
and maintainability.

~~~
willtim
Depends on what you mean by "scalable". Given that Haskell can track the use
of effects and mutable state in its type system, might suggest that it allows
such techniques to scale beyond a language that does not. Haskell also has
syntactic and compiler support for imperative code, no encoding is necessary,
so I don't think your analogy of writing FP in assembly is fair. Writing
imperative code in Haskell is much the same as in Java.

~~~
lolinder
I'm not saying that Haskell is as bad at imperative as assembly is at
functional, just that being able to accomplish all tasks in a single-paradigm
language does not constitute proof that the language is suitable for all
tasks.

I love FP concepts, and I've enjoyed my experiments with Haskell. What I
object to is that FP advocates don't push FP as a valuable addition to a
larger toolbox, they push it as a single paradigm to rule them all. For daily
work, I prefer a multi-paradigm language that allows me to choose a
programming model that matches the domain well.

(FWIW, I have the same problem with OOP advocates who try to squeeze
everything into a class. Some things are better modeled as pure functions.)

~~~
willtim
I don't think modern Haskell is a single paradigm language. As I said above,
it has good support for imperative programming and lots of imperative code has
been written in Haskell. Even the GHC compiler is full of imperative code and
uses algorithms with mutation where it makes sense (e.g. unification).

------
biznickman
I think this most effectively demonstrates why I like a lot of OOP: it can be
verbose. This is example function is relatively illegible:

    
    
      alignCenter :: [String] -> [String]
      alignCenter xs = map (\x -> replicate (div (n - length x) 2) ' ' ++ x) xs
          where n = maximum (map length xs)
    

One of the most verbose languages I've used, Objective C, has made this a best
practice. Despite the brackets (which scare people off), it can be some of the
easiest to read code.

~~~
yoz-y
To me this looks like perl golfing, but for some reason perl golfing is bad
but writing extremely terse functional programs is not.

Personally I like to combine both approaches. In swift I would write something
like this:

    
    
        let l = ["abc", "ab", "abcdef", "abcdefgh"]
        width = l.reduce(0, { max($0, $1.count) })
        let centered = l.map({
         (line: String) -> String in 
          var padding = (width - line.count) / 2
          return String(repeating:" ", count: padding) + line
        })

~~~
0xcoffee
Obligatory C#:

    
    
        var input = new string[] { "abc", "ab", "abcdef", "abcdefgh" };
        var maxLength = input.Select(e => e.Length).Max();
        var output = input.Select(e =>
        {
            var padding = (maxLength - e.Length) / 2;
            return new String(' ', padding) + e;
        });
    

Most modern `oop` languages these days all support functional constructs and
achieve the same thing in same amount of code & style. Language and
oop/functional style are not mutually exclusive anymore, which this article
seems to overlook by comparing both languages and styles at the same time.

~~~
fnord123
This looks like the Java if the Java was written by someone who knows Java:

    
    
        List<String> alignText(List<String> texts) {
          int maxLength = texts.stream().mapToInt(String::length).max().orElse(0);
          return texts.stream().map(text -> {
            var spaceCount = (maxLength - text.length()) / 2;
            return " ".repeat(spaceCount) + text;
          }).collect(Collectors.toList());
        }

~~~
p2t2p
/nit Move the lambda to private method and replace it with method reference
and it’ll be just perfect

~~~
fnord123
Good feedback. +1

------
ts0000
As someone strongly favoring a functional or even purely functional approach,
I strongly dislike the flood of pro-functional-programming blog posts that
attack the straw man of "imperative programming wrapped in classes". I don't
want to believe that is what experts of the paradigm consider object-oriented
programming. I would love to read an honest comparison of both, judging
benefits and cost.

~~~
marcosdumay
> I don't want to believe that is what experts of the paradigm consider
> object-oriented programming.

Once you get to the scientific literature about the subject (where actual
experts reside), it tends to be much more common to see the conclusions of
"there isn't any formal difference between OOP and FP languages", or that in
Haskell in particular "implements an strict superset of OOP".

The problem is that FP and OOP are ill-defined concepts.

~~~
AnimalMuppet
In what sense is Haskell a "strict superset" of OOP?

~~~
marcosdumay
In the sense that you can recreate all the usual OOP syntax and behavior on
Haskell. What people only do on practice for very limited extents, because
it's not very useful to import those concepts to a language aimed at dealing
with pure functions.

------
CJefferson
I don't see why "Object Orientated Programming" requires for loops, over map /
streams (which Java has).

Is there a definition of "OOP" which requires using for/while? This doesn't
really sure any OOP at all.

A better example would be to show a case where OOP would be useful, say having
a base class and deriving it several times (iostreams for example). Show me
how FP does somewhere where (traditionally) OOP is considered strong.

~~~
ezrast
Agreed. Ruby is a very fundamentally OO language, but its Enumerable interface
is more comprehensive than many FP languages' equivalents (in addition to the
standard map/reduce/select, it has methods for iterating over permutations of
elements, n-sized slices, etc, all of which can be made lazy), and because
it's an interface you can use it with custom data types easily. But FP
enthusiasts often speak as if the ML and Lisp language families have a duopoly
on higher-order functions. It's odd.

------
haroldl
Functional Programming is not just for Haskell; modern Java has lots of pretty
decent options so I feel like their Java version of the code is a bit of a
straw man. Here is how a more modern code style in Java 8 (which is years old
now) might look. Notice that the logic for this problem is 7 lines of code,
with one line being the closing brace.

[https://gist.github.com/haroldl/aee6a407a01131345fc4ecb1b9c9...](https://gist.github.com/haroldl/aee6a407a01131345fc4ecb1b9c9bec2)

------
recursive
You can write mutation-style java tersely too...

    
    
        void alignCenter(String[] text) {
             int maxLength = 0;
             for (String line : text) maxLength = Math.max(maxLength, line.length());
             for (int i = 0; i < text.length; ++i) {
                 text[i] = " ".repeat((maxLength - text[i].length()) / 2) + text[i];
             }
        }
    
    

No lambdas to be found.

~~~
cerved
Or just use Scala

~~~
recursive
Scala is a pretty immutability-functional-happy language. My point was that
the mutation/readonly functional/oop comparison was unfair.

------
AlexCoventry
I prefer functional programming because referentially transparent functions
are easier to reason about and test. Brevity of control flow hasn't brought me
much benefit.

~~~
kbuchanan
Couldn't agree more! ... I just had to laugh to myself at how this, THIS is
why functional programming is so... rarely adopted. Like, "referentially
transparent functions"?! What happened to `website.run.now!`?!

~~~
lemmsjid
Referential transparency is a handy thing to think about in OOP as well as
functional programming. If you provide the same parameters to a function, will
you always get the same result? If yes, and if the function is 'pure', e.g.
does not result in some side effect like state manipulation, you could replace
the function itself with the output of that function. If so, and the function
is computationally expensive, you can memoize the function, which is a
specific form of caching where you auto cache the results by the parameters
passed to the function.

This is a handy chain of reasoning in any language, and in a more dedicated
functional language might be supported as a concept in the language itself.

Like in OOP, you don't have to have that term memorized to actually use a
functional language, just remember the pattern.

------
p4bl0
I like functional programming too, but I'm not sure it is for the same
reasons. Anyway, I think what the author describes as object oriented
programming is actually imperative programming. In purely object oriented
languages, such as Smalltalk, control flow can be encoded in objects too (for
example Booleans can be an abstract class with two methods ifTrue and ifFalse
that takes a callable object, and True and False can be subclasses that
implements these methods by appropriately calling or not their arguments).

------
Traubenfuchs
Dear functional programmer, please do not slam on languages you do not even
know how to program in. To be honest, I have no idea what this code is even
supposed to do and which idea of "centering" it satisfies. Whether this mess
of Java or the mess of Haskell the blog writer created is more readable is up
to you. C# has been functional forever and Java caught up a lot.

    
    
      int maxLength = Stream.of(text).map(String::length).max(Integer::compareTo).get();
      IntStream.range(0, text.length).forEach(i -> text[i] = " ".repeat((maxLength - text[i].length()) / 2) + text[i]);
    

(This becomes significantly more readable if you change the contract from
passing an array of Strings to passing a list and returning a list. Since
functional programmers are so in love with immutability: Java now provides
immutable lists in the standard lib.)

------
collyw
Functional programming is all nice until you need to debug someone else's
code. (Maybe it just because i have done a lot more of it with imperative
style, but I am pretty sure that's the case for the majority of programmers).

------
AllegedAlec
> Obviously, composition over inheritance strives a bit against one of the
> original key concepts of OOP - which is inheritance.

I don't understand why people keep pushing this idea of OOP as requiring
classes and inheritance, when it very much does not.

Also, it's really easy to say that FP is better than OOP, or the other way
around, when people keep comparing their favourite with a straw-manned version
of the other.

~~~
almostdeadguy
I assume you're referring to the definition of OOP coming from the Alan Kay
quote: “OOP to me means only messaging, local retention and protection and
hiding of state-process, and extreme late-binding of all things."

This definition includes nearly every language with modules, polymorphism, and
depending what counts as message passing might either dis-include statically
typed languages, languages without a defined model of concurrency that
disallows shared mutable state, or languages without message passing library
support.

If that's what canonically counts as OOP, there's no straw-manning going on
here, the discussion is just a comparison against a pretty well-defined
category of programming languages with different features that are often
described as and understood to be object-oriented.

Whenever people bring this up I often hear that Erlang is supposed to be the
canonical object-oriented language, and if that's the case, I'm not sure how
we could call this discussion straw-manning, because it's just talking about
totally different languages.

------
throwaway5752
What I dislike about functional programming is that when objects are not
supported or they are avoided I have seen poorly documented and complex
hashes/collections data structures that end up reinventing the oop wheel (and
the problem the wheel was solving in the first place).

It feels like a false choice and you can have rich FP capabilities in a
language that supports objects and relationships between objects.

~~~
cerved
Then you should look into Scala

~~~
tombert
Scala is fine, but do you not feel that it tries to do too much sometimes?

Like, I like Scala if I'm the only person writing it; I basically write
Haskell while still having access to all of Java's libraries, but I absolutely
_dread_ having to collaborate with people using Scala.

A coworker of mine (whom I respect very much) said it pretty well once: "I
write Scala...but in a Java accent"...he doesn't use the functional features
of the language, and basically just writes Java without semicolons. When we
try and collaborate on stuff, it becomes difficult because of the sometimes-
conflicting way we write code.

Personally, for functional-on-the-JVM, I prefer Clojure. You still have access
to all the Java libraries, but it's a bit more decisive on what it wants to
be: a functional, dynamic lisp on the JVM. While you _can_ write OOP-style
code in Clojure using records and protocols, it's often frowned upon, and not
always 100% intuitive....virtually every tutorial you'll find on Clojure
writes in a pure-ish functional style.

~~~
cerved
It was in response to the parent lacking OOP in FP.

I think this is a problem with any language that supports multiple paradigms
and ways to skin a cat.

Enforcing guidelines is a way to combat that. Ofc you might also just opt for
a more specific language

~~~
tombert
The problem with coding guidelines is that they actually are really difficult
to enforce, especially during crunch-times. To quote Carmack, if the compiler
allows something, it _will_ end up in the codebase at some point.

Of course, this is also true of Clojure; people will make use of bad JVM
libraries because they're available if you follow this logic, F# can do unsafe
mutables, and even a more pure language like Haskell allows binding into C.

The difference in how _idiomatic_ and _easy_ it is to do a bad thing. For
Clojure, the language has the "right" versions of the main data structures as
first-class syntax, making it unlikely that you'd call into a Java library
until needed, in which case hopefully you're using the correct one; while
calling into Java isn't difficult, it _is_ made explicit, and it does feel a
bit unnatural as a result. Since Scala attaches no syntactical stigma to using
the "bad" Java conventions, it's common for people to do things in a bad way
(e.g. using `var` everywhere instead of `val`).

------
rb808
I like FP, but cautious as there aren't many big applications (open source or
private) that are written that way. Am I wrong?

~~~
klodolph
You don’t need to go full FP to experience the benefits. You can just apply FP
principles on a small scale to sections of your code, where appropriate.

As for “many big applications”, it depends on what your threshold for “many”
is. There are a number of firms that use e.g. Haskell, F#, or ML privately,
e.g. Jane Street, Galois. For whatever reason, functional programming seems to
be more popular in finance.

------
heisenbit
Is it just me or is iterative non functional code usually easier to debug? Or
are the tools a lot more mature?

------
tombert
I started programming (like a lot of people) after I bought one of those
"Learn C++ FAST!" books (I don't remember the actual title).

A large chunk of this book was about the object-oriented part of C++, and
comparing it to the equivalent version in C, and acted that since the only two
paradigms that exist are OOP and imperative, you should always use OOP. (NOTE:
I'm paraphrasing, that was the tl;dr as I remember it from 18 years ago).

Later, when I was 19 (around 2010), I was on an IRC board and someone was
talking about how cool Haskell was, and when I asked him to explain why it was
better than C++, he went into elaborate detail about how "C++ is total
bullshit because you have to mix your types with your functions". He then went
on elaborate detail (that went completely over my head at the time) about how
Haskell's typechecker made C++'s look like "dogshit".

Maybe I am too suggestible, but at that moment I agreed with him, and have
kept that mentality ever since. I fundamentally do _not_ like attaching
methods to my structs/records/whatever, since I think that forces strong and
unnecessary coupling. I _do_ really hate the type systems of Java and C++
because I think they're restrictive and don't actually help.

Obviously opinions vary; a lot of very smart people really like Java and C++,
but I seriously cannot personally understand why. I feel like I get more done
quicker with Clojure than I ever could with Java, and I have trouble
comprehending anyone who says otherwise.

I'm not trying to start any kind of flame war; if you like OOP we can still be
friends, I'm not judging you as a person, I just disagree with your language
choice :)

~~~
MauranKilom
> I do really hate the type systems of Java and C++ because I think they're
> restrictive

Can you give an example of how the C++ type system restricts you? Is it just
lack of some inbuilt mechanisms/syntactic sugars or is there something that
you can't actually model with it?

> I think that forces strong and unnecessary coupling

How do you model/maintain invariants of a set of data in FP? That's the part
that I don't understand yet.

~~~
tombert
I haven't touched C++ for several years at this point, so bear with me a bit,
but in the category of "just really annoying, not really restrictive", there
is the fact that you have to constantly re-type the types for everything, like
`MyType x = new MyType()`, but my understanding is that has been addressed by
the `auto` keyword.

Is there some equivalent to a monad in C++? As in, something that will
"pollute" the function so that if it's trying to call something with side
effects, it's reflected in the type? I have no doubt that you could stitch
something together with the templates to get something, but in Haskell, out of
the box, your functions that don't have side effects if they don't return
`IO`.

Of course, this wouldn't be a sign of it being "restrictive", just a feature
that's not in there. Maybe I'm being a bit unfair to C++ by tying its type
system to Java, which _is_ terrible.

One thing that I really dislike is the fact that, in order to make class X
part of interface Y, you need to have access to X's source, or extend it. I
find this incredibly annoying, and with Haskell, you can attach _any_ type to
a typeclass by providing its implementation. I know C# has this with its
extension methods, but AFAIK C++ doesn't have any equivalent.

> How do you model/maintain invariants of a set of data in FP? That's the part
> that I don't understand yet.

I don't really know what you mean by that; you can restrict the allowed input
types with typeclasses or existential quantifiers, but I'm not sure that I'm
answering your question.

~~~
zabzonk
You almost never use `new` in modern C++, and you would never use it as you
suggest because it wouldn't compile, you would simply say:

    
    
         MyType x;
    

C++ is nothing at all like Java.

~~~
tombert
Sorry, you'd need to use the MyType* x to use the `new` keyword; it's been
awhile. It doesn't really deter from my point though; in order to heap-
allocate something you have to use a pointer, and you end up doing something
_not that dissimilar from Java_.

> C++ is nothing at all like Java.

Is that supposed to be a joke? Java was marketed specifically towards C++
engineers...

~~~
iainmerrick
Javascript was marketed towards Java programmers, and those languages are
pretty different from each other too.

~~~
tombert
I would say that C++ is closer to Java than JavaScript is; it's fairly easy to
copy-paste and edit C++ code into a Java file with, just changing a small
amount of syntax and get rid of the manual memory allocations.

Try copy-pasting JS into a Java file, and there are semantics there that are
completely foreign; you can't simply translate it.

~~~
iainmerrick
I agree that JS and Java are very different; that was my point.

I disagree about Java and C++. If I have a C++ file that uses RAII or
references, for example, it won’t translate to Java easily. And those aren’t
unusual, they’re both common techniques.

------
foobar_
I still don't get the point of immutability. Sure state change is a problem.
How about a log like data structure that stores all modifications of the data?

I'm sticking to procedural coding for the next 10 years and I will try my best
to unwash young coders from poop.

~~~
roywiggins
That seems like an implementation detail. If you have an append-only log, you
can always rely on each entry to never change. That's a kind of immutability.
If you hold on to a reference to the log at a particular time, it will never
change out from under you.

~~~
foobar_
There should be an alternative to both extremes - complete mutability and
complete immutability.

