
The Functional Style (2018) - yogthos
https://codurance.com/2018/08/09/the-functional-style-part-1/
======
vemv
Given the comment section is devoted to definitions, I'll add mine: the single
most important characteristic of proper FP is _strictly segregated side
effects_.

At a high level, many e.g. Clojure programs are indistinguishable from an OOP
program. Functional composition and immutability are just superficial details
- another 'skin' containing the same stuff.

Does it really matter if you expressed something as a `for` or as a `reduce`?
Is local mutability distinguishable from immutability?

Worth noting, real OOP webapps do very little in-process mutability. They're
'functional': request in, response out. State actually lives in Redis/Postgres
so the actual mutability story is the same for FP and OOP langs.

That's not to say this functional-ish style is wrong or wasteful. It's a nice
step forward. But we really ought to appreciate "strictly segregated side
effects", namely actually pure functions, without exceptions.

There's no such enforcement in a great deal of FP langs, and IMO there really
should be (at least in the form of a lightweight linter) if we want FP to be
genuinely superior.

~~~
elcritch
> the single most important characteristic of proper FP is _strictly
> segregated side effects_.

This is one of the best definitions I've come across.

~~~
kazinator
If we have pure funtions, that is not "strictly segregated side effects"; pure
functions do not have side effects, period.

We can segregate side effects very well in imperative code.

The segregation of side effects is clearer in a strictly evaluated imperative
language than in a lazily evaluated functional language.

------
ilovecaching
I find the authors definition of functional programming really insufficient.
FP should not be defined in terms of immutable state, but as mathematical
functions as the basic form of abstraction, and that programs are composed as
an evaluation of a single expression in spirit with the lambda calculus.
Immutable state is just a correlary.

With that in mind, you really can’t do FP without automatic currying and the
function composition and application operators (. And $) in Haskell. Without
these, you lack the key components needed to work with your only means of
abstraction. Language author seem to think lambdas are enough, but if you
can’t compose functions to build new functions, partially apply them to get
specific versions of behavior, then you aren’t going to get very far.

~~~
skrebbel
This is a generic "no true Scotsman" argument. Not everybody shares the same
definition of FP and while "My definition of FP is what Haskell gives me, and
the author's definition is more akin to what Clojure and Elixir give them" is
a correct statement, it's also not a very insightful point.

You can't deny that there are lots of languages and techniques that people
call "functional" where the key ingredient isn't "programs composed as an
evaluation of a single expression" but something else (such as "immutable
state").

~~~
Qwertystop
Elixir's got |> for composition and & for currying (as well as for lambdas);
I'm not sure that's the best language example for differences (given the
specific things grandparent listed of things languages are missing).

~~~
skrebbel
|> isn't proper partial function application. It can only apply a single
parameter and you can't store a piped-into function in a variable the way you
can a partially applied function in eg Haskell. Eg the following won't work:

    
    
        splitname = name |> String.split
        splitname.(" ")
    

Also, it's a stretch to call & currying. It's shorthand syntax for making an
anonymous function. If & is currying, then the following JS code is also
currying:

    
    
        const cube = a => Math.pow(a, 3)
    

Elixir doesn't have currying or partial function application any more than JS
or C# do. If your definition of "functional" requires these features then
Elixir isn't functional (and that's fine).

------
danenania
I definitely agree with the premise of this post. For me the sweet spot of
functional programming is learning the most important principles—quarantining
mutable state and side effects, declarative data transformations, higher order
functions, etc.—and then bringing these patterns back into more ‘practical’
languages.

A good example is the immer.js library, which takes the pain out of
immutability while retaining the most important benefits. Instead of bending
over backwards to transform hierarchical data with lenses and the like, you
just mutate a ‘draft’ copy imperatively within a bounded context. While it has
its limits, it’s often much quicker to write and easier to understand than the
equivalent ‘purely immutable’ approach without any real downside.

------
3pt14159
My natural inclination when I was younger was the functional style, but over
time I've come to see it as sub-optimal for the problems of day-to-day
business coding. Almost always there is state somewhere and I find the
restructuring of the program flow unnatural at times where an imperative, OOP,
or FP-inspired-but-not-FP-actually style would meet performance requirements
while also maximizing legibility to a future programmer.

But I'm not a zealot. It's fun in the right domain.

~~~
mlthoughts2018
I agree very much. Working professionally in Haskell across 3 jobs caused me
to pretty much lose interest in it except for personal projects with very
small scope where I can personally control all the reasons for refactoring or
behavior changes.

When those things come from a stream of business problems, Haskell, I hate to
say, is a poor tool, for just the reason you stated. You’ll always come across
situations where mutating state would make an incremental change very easy
(despite being “bad practice” for long-term complexity) but where having
rigorously designed a functional system, that mutation or change is now
painfully hard and requires significant refactoring.

Because business pressures utterly do not care about engineering properties of
the backend system, except for high level summaries of basic properties like
overall cost or reliability (metrics that functional programming vs other
paradigms doesn’t have much impact on), you end up never being able to justify
the upkeep and refactor-as-you-go workloads necessary for the functional
implementation to not get bastardized into the exact same sort of spaghetti
code mess you get with other languages.

In my experience, the types of errors, bugs and extensibility headaches in a
business setting are not addressable with static typing, designs leveraging a
type system, formal verification, or immutable design patterns. Those things
are fine, not knocking them. It’s just that they cannot help you for dealing
with reactive business problems. They don’t live up to the hype about it.

The problems are all about a sudden context change in which a previously valid
set of behaviors, performance characteristics, whatever, is instantly rendered
invalid because circumstances have suddenly changed, and you need to simply
react to it and pray you’re successful enough that the system even lives to
some magical future time when you can refactor.

~~~
james-mcelwain
Right, I can think of many situations where a new business requirement is like
"oh, and for this client, when we do X, we also need to send them an email."
OOP, for all its faults, makes this kind of extensibility trivial, where I
can't imagine easily making such a change in the context of a statically typed
effects system. "This function was pure and now it's not" almost certainly
leads to a refactor.

That's not to say that statically typed FP _can 't_ be written in such a way
to allow this kind of arbitrary modification, but then you get into the realm
of finding the perfect abstraction in the type system, which is exactly the
same kind of maintenance problem that OO architecture astronauts bring to a
code base.

This is all to say -- we should be writing Clojure, which allows a tight focus
on actual business requirements while still being flexible enough to allow you
to bail out of FP patterns when necessary. :)

~~~
yakshaving_jgt
I’m super confused by this.

In the Haskell systems I write, this change would be trivial. It’s a single
line change. Maybe it’s just a benefit of the particular pattern I’m using
(standard Yesod handler, which if I remember correctly is just the ReaderT IO
pattern), but still, it seems completely wild in my view to trash an entire
language and indeed an entire paradigm because you can’t imagine making the
change in your example.

~~~
james-mcelwain
Business logic is pure and stateless. Lifting to IO is a breaking change that
may or may not require non-trivial refactoring. You shouldn't be putting
business logic in your HTTP handlers.

Regardless, the point is not that it's impossible to make such a change in a
Haskell code base, just that it may or may not require non-trivial breaking
changes, while such a change in an OO enterprise code base will always be
trivial.

There's lots and lots wrong with OO patterns, but being able to respond to
changing and arbitrary whacky business requirements is a clear strength.

~~~
yakshaving_jgt
> Business logic is pure and stateless.

Why? From where did you invent this completely arbitrary constraint?

> You shouldn't be putting business logic in your HTTP handlers.

Why not? How else could this work? The user has to interface with the business
logic _somehow_.

There’s nothing at all wrong with a HTTP handler that takes in a request,
delegates to a few different business logic things, and then sends a response.

Not only are you moving the goalposts with your argument, you’re also coming
up with things that are completely false.

There’s nothing about OOP that inherently makes extending some code to send an
email easy. Likewise, there’s nothing about FP that inherently makes the same
task hard.

------
foolfoolz
Sort of surprised how restrictive some of ideas in this article and comment
section are to say if you are writing functional code or not.

If you are writing pure functions and total functions, it's functional code.
The rest is syntax.

And pure functions are probably best tagged with an asterisk because most of
the time you have state to manage somewhere, you're just going to jump through
some syntactic hoop (monad) to mask it

~~~
LeanderK
while I agree with your overall argument, i think this is wrong:

> And pure functions are probably best tagged with an asterisk because most of
> the time you have state to manage somewhere

purity does not mean there is no state! This is exactly what the monad (and
similiar structures) in haskell is for, which you correctly mentioned. But
they don't violeate the purity.

~~~
foolfoolz
i think it devolves into semantics at that point. when each line is doing some
DB query or remote HTTP call it rarely matters if it's in a monad or not

~~~
yogthos
Exactly, whether you use a monad or not is an implementation detail. The high
level idea is that you decouple the code that does IO from the code that's
responsible for the business logic.

------
ninja10
Functional programming as defined in its purest form would be ( as some others
defined here ), akin to "mathematical functions as the form of abstraction".
In the context of computer programming, Haskell would be the epitome of this.
That being said, elements of functional programming are already available and
making more head-way into imperative languages creating a hybrid
imperative/functional style which I think is great. Some elements of
functional programming that provide huge benefits are hard to argue against:

1\. high-order functions ( as inputs or outputs ) e.g. map, reduce, filter,
etc. Direct benefit is a significant reduction of code and the ability to
chain operations together and compose them.

2\. immutability. Direct benefit here would obviously be minimizing shared
state thus reducing bugs

3\. side effects: minimizing and pushing side-effects to "edges" of an
application. and/or designing programs such that side effects can be mocked
out. e.g. building up a Http request that can later on be executed, thus
facilitating unit-testing.

4\. type checking: Converting runtime errors into compile time errors ( to
some degree ). again, by offloading work to the compiler, you can catch errors
much earlier on.

The biggest problem w/ FP ( as I have evidenced ) is that while these things
are great, when taken too far for the sake of pure FP itself, can create
complex code. so there has to be a balancing act involved.

------
jtdev
It seems that FP zealots have a hard time appreciating that there’s both a
value and a cost to FP adherence. I find that FP forces too many contortions
in the name of some ethereal value without a consideration of cost.

~~~
alexandercrohde
I think I know what you mean. I wouldn't use the word zealot as much as
astronaut.

FP Astronaut -- Super focused on hypothetical, academic, mathematical concerns
and random performance optimizations (e.g. tail recursion) over clarity. I
think scala's Slick library is an example of this.

FP Pragmatist -- Concerned about clarity, debugging, logging, correctness,
readability by all skill levels, modifiability, documentation, the common
cases first and the extreme cases last

I think you can be a big FP advocate without being an off-putting
incomprehensible blowhard

~~~
julesnp
I've never heard of tail recursion being used as a performance optimization.
Generally it's required to prevent a recursive function from blowing the
stack.

Iteration is usually more performant than recursion, but doesn't mix well with
immutability.

~~~
Technetium_Hat
Tail calls _are_ iteration, just expressed as a function.

------
sprague
Discussions about functional programming really need to include R as an
example. Highly popular with data scientists, it regularly makes top 10 lists
and is much more mainstream than, say Haskell or F#.

------
zupa-hu
Great work, thanks for sharing!

I was looking for that comment to upvote it but it didn’t exist. Everyone is
expaining their pet feature of FP.

Maybe we should put aside our differences and lean towards cheering the people
who put lots of effort into opening this world up for as many people as
possible. Those who are not FP experts need exactly this kind of explanation,
not mathematical mumbo jumbo.

I’ll save the URL for sharing with anyone interested.

So here it is again: Great work, thanks for sharing!

------
melling
Personally, I like the idea of using immutability. In Swift, I declare as much
as possible with let instead of var

    
    
        let pi = 3.141592
        let label = UILabel()
        let names = [“Java”, “Perl”, “Swift”]
    

These won’t accidentally be changed and they’ll never be null.

The editor/tools can now make additional checks before I try to run the code.

A small step towards correctness and readability.

------
protomikron
My main problem with functional programming (mostly based on some experience
with Haskell) is that I find it _really_ hard to estimate the performance (in
terms of runtime and memory consumption) of programs.

It's really nice to structure your program around types and build your program
via function composition - but getting it performant (let's say similar to C,
Rust or Java) is far from simple. In particular performant Haskell does not
look like idiomatic Haskell, whereas performant C, Rust or Java does.

Now don't get me wrong - I absolutely love the idea of functional programming
and λ-calculus is a beautiful system to express computation, but in practice
it's still hard to make performant while keeping a readable style. Maybe Idris
2 can close the gap?

~~~
Ono-Sendai
I think this is more of a problem with Haskell than functional programming in
general.

------
SamuelAdams
Everyone talks about functional programming as if its this great new thing.
I'm coming from a strong C# background, and to me this reads exactly like OOP.

> the output value of a function depends only on the arguments that are passed
> to the function, so calling a function f twice with the same value for an
> argument x produces the same result f(x) each time.

OK, that's basically a static method in C#. Is that wrong?

The piece goes on to say

> Functional programming, therefore, is programming so as to avoid these side
> effects wherever possible.

So functional programming should never handle state? Do you just move that
logic into another framework (Angular, React, etc)?

Maybe there's some obvious stuff I am missing. Why should I use a functional
language (like F#) over an existing OOP language?

~~~
mrkeen
Non-functional languages seem to pay lip service to FP, confusing people into
thinking they're already using it.

Example: It is well known that "Java Strings are immutable", which is a _good
idea_. Yet you can absolutely write: > String str = "hello,"; > str +=
"world!"; So it's "immutable" for some lawyerish definition which does me no
good. If I write a method that depends on s, it will produce different results
each time.

> So functional programming should never handle state Not at all. You should
> know (and be able to enforce) when your code does mutations.

~~~
kbp
> Yet you can absolutely write: > String str = "hello,"; > str += "world!"; So
> it's "immutable" for some lawyerish definition which does me no good.

`str` is a mutable reference to an immutable object. You can make the
reference point to something else, but the object being pointed to is
immutable, so eg if you pass it to a function, that function cannot change it
under you. If you want to make the reference immutable, declare it as final. I
don't think understanding the difference between a pointer and the value it
points to is lawyerish.

~~~
mrkeen
Thank you. ^ that's the lawyerish definition I was referring to ^

