How to Lose Functional Programming at Work
robertwpearce.com
robertwpearce.com
I can't claim it isn't important. I do get the impression we think it is more important than it is.
I also think we frame things wrong. We push fp as being a declarative counter to imperative code. Feels more akin to switching coordinate systems. With so many examples of "look how easy this perfect sphere is in polar coordinates" when so many of us are working with rectangles. Or, at least, not perfect spheres.
I find recursive functions over immutable values easier to prove to be correct than iterative loops over mutable storage.
Sometimes you're making a prototype. Sometimes you're making one off get some info out of data and never use it again.
Provably correct app that now costs 10x and is essentially yet another CRM or CRUD app is a failure because competition already delivered theirs 2 years ago and ate your market.
Go too far in another direction and you're living in house of cards with tech debt killing all velocity you had and users leaving over app having bugs or just being slow, while you're drowning in rotten spaghetti.
I do think that "doing the minimum to do the job at hand" is wrong approach for near everything that will live more than two or three months, but there is definitely threshold on other side of that where you're essentially just gilding the lilly and wasting time.
The key is to pick your battles, time is resource that thing that every other component of your infrastructure is going to use probably could use more of that time than random feature someone up top invented on a whim.
But depending on the topic it's an absolute nuclear weapon. I've seen people accumulate pages of imperative code. Side effects everywhere, no reuse of small logical bits. 80% waste, averaging 4-6h per bugfix.
On the other hand short immutable code is more readable (yes even with a single letter vars), is linearly ordered, and often reusing the same combinators.
Edit to add: The example I used to use was to look at the imperative version of the Koch snowflake. Then look at a version that is not. Indeed, even the examples you will find in Haskell are typically reaching for a much more imperative style than typical Haskell programs.
Imagine you've got an immutable List. x.append(blah) does not mutate x but instead it returns a new list with blah at the end. If you want to know the state of this new list, you need to know the state of x. You still chase a chain of operations to figure out what your data can look like at some program point.
You get the advantage that mutation cannot be hidden inside of functions, but this is achieved just fine with "const" or whatever.
Which is also one value of FP. This is, IMO, what lead to timetravelling stuff like redux.
What is a constant problem is when it turns out you have the wrong value going into the wrong thing: and that's either a logic bug where your inputs are all fine, or a function which is returning the wrong thing. Debugging this problem isn't helped by avoiding mutation really: you're still going to be stepping through every line and checking function results (and a lot of functional styles are really annoying like this - z(y(x(a))) x(a) y z if you're allowed a point free style might save some space, but usually a debugger has no idea how to show you the intermediate results).
I’m in favor of some statefulness, by the way, not a fan of pure FP languages.
I don't think that mutability has to be an inherent property of a value: it's really a description of how a value can be accessed. Personally, I'm partial to the model used by Rust, where you can either have shared immutable access to a value, or exclusive mutable access to a value, and you can temporarily loan out your access to another function with a strict deadline (a "lifetime"). Together, this means that you can call a function granting it mutable access to your value, and be confident that the called function is the only thing in the world that could have modified the value.
I'm surprised by how this "controlled mutability" solution never seems to come up in discussions of imperative vs. functional programming. Perhaps it has to do with how mind-boggling the borrow checker can appear coming from most other languages.
You're right, it isn't, and I wasn't trying to claim it was a particularly big problem. What it is is another little you don't even have to think about which I found to be a pretty delightful feeling. Cloning isn't a consideration in mutable languages and that's a nice a little win. Though as a sibling comment mentions, if you're working with a bunch of inexperienced folk, these things are a lot more likely to slip through.
As for the debugging bit, I made the sin of comment before I read the article and realized it was using JS. No matter what anyone wants to tell you, JS is very good as a functional language. It can be ok, but it's not great. "Most functional styles" is otherwise a very unfair statement as most functional languages come with pipes or infix application operators that make debugging really easy as you can throw an `inspect` into a pipeline.
Getting a bit off the rails just as I was excited about this, but Elixir recently introduced a `dbg` function which, if used at the end of a pipeline, outputs every line of it which is crazy nice (unsure if this was borrowed from another language or not):
"hello there"
|> String.split()
|> IO.inspect()
|> Enum.map(&String.capitalize/1)
|> Enum.join(" ")
|> dbg()
produces: [file_name.ex:8: ModuleName.function_name/0]
"hello there" #=> "hello there"
|> String.split() #=> ["hello", "there"]
|> IO.inspect() #=> ["hello", "there"]
|> Enum.map(&String.capitalize/1) #=> ["Hello", "There"]
|> Enum.join(" ") #=> "Hello There"
So that's really nice.Annnd another nice thing is consider code like this:
perform_some_action_with_object(user)
user.i_hope_user_hasnt_changed()
In mutable languages we can't be sure that `perform_some_action_with_object` doesn't mutate `user` so we have to check. This isn't even a consideration in immutable languages. Again, not the biggest problems in the world, but it really helps when you want to quickly scan through a codebase you are unfamiliar with. You could also say "don't write code like that" and I tend to agree, but I have seen a lot of code like that in OO codebases in my time.I made sure to double check my spelling this time :D
Yes, it does. You're right that you still have a chain of operations to figure out first. But with mutable data you need to do that, as well as know when you're looking at the data, and what else is running in the system that has access to that data.
It doesn't grant superhuman insight into program state, but it is orders of magnitude simpler than mutable data (perhaps several orders depending on the scope of the data).
I think the only way to adopt it is to either use a language where it’s the default or get consensus from the team.
Otherwise you can sneak in bits here and there in a functional style, just don’t start implementing monads, functors, and Applicative. Anything beyond map and basic higher-order functions will scare the locals.
There’s no pushback because of “strange” FP patterns, code looks just like normal imperative code, but the tricky logic can be isolated in pure functions. The push back is only because of the strong OOP conventions.
That's not really FP though. In FP, you only have pure functions and not a mix of pure and impure.
Having gone through the difficulty of learning what a functor is, what a monoid is, semigroup, monad, etc... I'm better for it. I can see why they're so useful in programming. I can see how they can help me write simple programs. However there was a time when I thought these concepts were complicated and obtuse. I couldn't see their benefit. I'm glad to have relieved myself of such pesky illusions.
The problem I'm alluding to is that on a team of developers with mixed levels of experience and backgrounds it is hard work to find consensus in general. If you've gone through the trouble of picking a programming language to work in and you have established that the style of programming will be OO, coming in and suggesting that the style should change to FP is going to be a hard sell.
You can tell them about all the benefits... but without having been through the process you won't get good feedback beyond anything superficial or downright ignorant.
And if you try to shovel it on them anyway in your little corner of the code you'll risk causing quite a bit of controversy! You can get away with an acceptable amount of functional programming style on most teams these days... but going beyond the ones I mentioned will usually draw such unwanted attention.
The other facet of it is that programming in a functional programming style, beyond the very fundamental ideas, in a language that isn't suited for it poses a difficult proposition: you have to maintain the rules yourself. If you break one, use an escape hatch just once, let a bit of something sneak in; you've broken all of your promises and the usefulness of it is gone.
In a language designed for functional programming first you don't have to waste this mental energy on ensuring functions are pure, that values are immutable, that equality is maintained, that the laws are preserved, that side-effects are limited to a scope; etc.
So definitely, take these ideas from functional programming if they help you write better programs, or help you understand your programs better!
If you're not using an FP-first language be aware of the trade offs and difficulties.
I don't see why this is true, and perhaps it's because I don't have a full enough concept of functional programming.
In any language, why can't we write functions that don't modify the arguments, offer the same return for the same arguments, and modify no internal state when called? And then compose those functions together to form a full working program?
Here here. I am consistently baffled as to why this approach and OOP are apparently mutually exclusive, and why functional programming must be more than the approach quoted here.
Functions/methods as small/deterministic as they can be, judicious type checking, separation of concerns...maybe I'm dumb and/or inexperienced, but this approach seems perfectly fine to me, at least for the domains in which I work.
OCaml has an OOP system in place. C++ has lambdas, some "monads," etc. You can do functional programming in C#, TypeScript, even Java these days. People have even written libraries in Haskell that let you do OOP if you want.
Jack of all trades, master of none, is how I think of it. It becomes complicated trying to be a language that appeases all programmers. The C++ specifications, ECMAScript, etc are all enormous.
The Haskell 2010 report is quite small by comparison. The R6RS specification is smaller. When you have fewer things to consider and a smaller audience to appease you can do a whole lot more with much less.
When you go all in with a language that is built on functional programming principles first and foremost, you end up with a very different language than if you start with procedures and statements. You can emulate functional programming in C but it requires discipline and you don't get any feedback from the type system, compiler, or tools unless you write them yourself. When you write code in Haskell you get all kinds of feedback and the mental energy you used to spend on discipline can be used elsewhere.
10 years ago you would have said the same thing about map and those "basic higher-order functions" that you say about monads, functors, and applicative.
Edit: A good book: https://en.m.wikipedia.org/wiki/Higher-Order_Perl
I mean, at least the former will be somewhat working and with proper IDE support.
Lets say you have a huge overly-convoluted Haskell program. Somewhere deep down a call hierachy of pure functions you need to print something to the console. That is not easy to refactor.
Or vice-versa you have a huge convoluted program where everything happens inside an IO monad because at some point something is written to the console. Now you realize you dont need to write to the console.
Pure functions are great, but they are not a panacea.
> Or vice-versa you have a huge convoluted program where everything happens inside an IO monad because at some point something is written to the console. Now you realize you dont need to write to the console.
These problems are essentially completely resolved these days by a modern effect system like effectful. Basically, they allow you to do arbitrary effects deep down a call stack with minimal plumbing (you still have adjust the types, as you should: that's the point of effect tracking!) and also to remove effects, so you can easily convert between pure code and "effectful code that just so happens to do no effects".
Both. You can have a RVVehicle class and a RVHabitat class. Nothing is forcing you to have 1 class. If it makes more sense to be just one class you can also make it a subclass of 2 classes.
>Are writers children of nationality or is nationality a child of writer?
Nationality would be a child of writer.
Wouldnt that be a has-a relationship? Represented by composition rater than inheritance.
Inplementation inheritance is a useful tool when you need it, but it’s not that central to OOP.
Many other apps don't have many states to manage, OOP is bad for them.
This can be done in any language without introducing “foreign” FP patterns and instantly makes code easier to understand and test.
Fortunately, there is a toggle to change theme in the upper right corner which at least makes things readable.
In my early days teaching LaTeX classes, I used to have an example of incorrect input in the first day’s material (forgetting to leave a space after a command). Even though it said before the example that it was incorrect, invariably, at least one person in each class would type it into their computer and have the error returned to them. I removed the example because it only hindered the teaching process. I feel like articles like this which use opposite-speak in making their point do the same thing.
1. Nobody who knows F paradigms is joining. Also these people most likely won't want to learn either.
2. There will be a constant battle for all PRs. "What is this, what is a filter doing, why are we mapping when we could just for loop and break"
If you have even an inkling to do FP, your only option (besides building your own team) is finding a company that respects it
On Haskell, the odds of a point-free rewriting increasing legibility are about as large of it reducing. And Haskell has all the syntax features that favors it. Javascript has none of those features.
I disagree that it's annoying and click baity — else I wouldn't have went to all the effort — but if it's not for you, it's not for you.
> Tacit programming (point-free programming) is a programming paradigm in which a function definition does not include information regarding its arguments, using combinators and function composition [...] instead of variables.
from https://stackoverflow.com/questions/944446/what-is-point-fre...
I guess it's the fact that in programming languages like OCaml you write functions like that:
let some_fn arg1 arg2 = ...
and so you can partially apply them:* using `some_fn` without any argument is manipulating a function with two arguments
* using `some_fn arg1` returns a new function that only takes one argument
* using `some_fn arg1 arg2` applies the entire function
let last xs = List.fold_left (fun x y -> Some y) None xs
This is of type last : 'a list -> 'a option = <fun>
Neat, `'a` is generic. Let's η-reduce out the `xs` and make the function point-free (ignoring the lambda): let last = List.fold_left (fun x y -> Some y) None
This doesn't work the way that we want: last : '_weak1 list -> '_weak1 option = <fun>
The moment that we call this weakly polymorphic function, its type is fixed and is no longer generic. In the toplevel: # last [1;2;3];;
- : int option = Some 3
# last['x';'y';'z'];;
Error: This expression has type char but an expression was expected of type
int
Haskell, of course, is totally happy to let you do point-free mania with Kleisli arrows and all of the rest of it.A nice example from the article:
const processData = composeP(syncWithBackend, cleansePII, validateData)
async function processData(data) {
await validateData(data)
const cleansedData = cleansePII(data)
await syncWithBackend(cleansedData)
return data
}
// or for the Promise-chainers…
const processData = data =>
validateData(data)
.then(cleansePII)
.then(syncWithBackend)
.then(() => data)
I would always write this as the 2nd option - it's always going to be the easiest to debug[0] and the abstractions it leverages are equivalent to the composed version in the first example or third example.When I write code that leverages FP principles (pure functions, composition, immutable data structures, etc) I know I've found the right abstraction - but there's never a need to actually abstract it all the way. That's the job of a good compiler.
I'm nearly always writing code for other people to later debug, fix, extend and understand and rarely for the computer - don't make your future self's job harder than it needs to be.
“balking”?
The HTML on the page responsible for this:
<iframe class="pdf" loading="lazy" src="/pdfs/2023-01-24-how-to-lose-fp-at-work.pdf" title="How to Lose Functional Programming at Work - PDF">
</iframe>
Here's approximately what I've used myself in the past as an alternative: <object data="/files/my-document.pdf#zoom=110" type="application/pdf" class="my-style-class" title="My PDF document">
<!-- Fallback for older browsers that do not support the object tag -->
<embed src="/files/my-document.pdf#zoom=110" type="application/pdf">
<!-- Fallback in case PDF display of any sort is not supported -->
<noembed>
<span class="my-warning-class">
It seems like your browser does not support displaying PDF files.
You can download the file separately <a href="/files/my-document.pdf" download>here</a>.
</span>
</noembed>
</embed>
</object>
(not too clean of a solution, but seems to work okay in most cases)The presentation/article itself is pretty nice, though. I really like the mentions of the human aspect of it all, how getting others onboard is quite important!
I'll take a look at using an embed tonight. Thanks for the feedback!
I looked into it and it indeed is the settings!
To reproduce the behavior, you'd go to General > Applications, then find PDF and if it's set to "Open in Firefox" then it will render inline, whereas if it's set to any of the other options, then it won't:
- Open in Firefox: will show the PDF inline, which is probably your setting
- Always ask: opening the webpage will result in a prompt for what to do with the PDF
- Save file: will always download it
- Use OS default application: will download it and then open it
This doesn't seem to be the case with the alternative approach, which just renders it inline always (when the browser in question supports the functionality), though this is definitely a bit of interesting behavior otherwise!so a text-link outstide of noembed would be a nice alternative anyways, i bet it will be better for screenreaders too
edit: the real-talk takeaways at the end are worth reading, I think (I'm biased, though)
I totally agree with you! I can't say that conversation belongs alongside this post, though.