top of page
FP.png

Skill strength: 8 out of 10

FUNCTIONAL PROGAMMING (F#)

Problems



"Garbage collection is a horrible hack that we have accepted into our languages because we are just so bad at managing temporal couplings. If we were adept at keeping track of allocated memory, we would not depend on some nasty background process to clean up after us."

"Temporal couplings and race conditions are the natural consequence of programming with variables-of using assignment. Without assignment, there are no temporal couplings and there are no race conditions. You cannot have a concurrent update problem if you never update anything. You cannot have an ordering issue within a function if the system state never changes within that function."

 


Who do you think these quotes are from?


It was neither Rich Hickey (creator of Closure), nor Scott Wlaschin (my great role model in terms of F# and black humor), nor Erik Meijer (known for his work on functional programming languages and his contributions to Reactive Extensions (Rx), a library for asynchronous and event-based programs).

It was ...

... Drum roll ...

... Uncle Bob. Robert C. Martin. The OOP guru par excellence. And I don't mean that disrespectfully. His books "Clean Code", "Clean Coder", "Clean Architecture", "Clean Agile" and most recently "Clean Craftmanship" were important companions for me, cast in letters.

The above quotes are now all taken from his - at this time (2023-10-31 - 17:51) - latest work:

"Functional Design: Principles, Patterns, and Practices" := (https://learning.oreilly.com/library/view/functional-design-principles/9780138176518/ch01.xhtml#:-:text=Temporal%20couplings%20and%20ra,re%20are%20no%20race%20conditions)

I was speechless when this book was recommended to me by this very OO pope via OReilly bookflatrate subscription. But he is not the only "convert", whereby I do not mean that he has turned his back on OOP, but that he has expanded his radius of visibility. So far, FP has always been in his blind spot, as evidenced by his books, blogposts, and talks. At least that was my impression.


Temporal coupling

​

We remember Uncle Bob's wise saying from the paragraph before: "Temporal couplings and race conditions are the natural consequence of programming with variables-of using assignment."



What does Uncle Bob mean by "programming with variables"? Doesn't one use variables in FP? Correct. The difference is only this:



[code)

// Block A



(code)

x=1;



// Block B




In the sequence of system states, the order of blocks A and B plays a decisive role. Block A leaves a different system state than block B and must therefore necessarily be executed first. Swapping their positions would put the system in an invalid state.

The phenomenon is called sequential or temporal coupling, a temporal concatenation with which you are probably familiar. 'Open' must raise the curtain for 'Close', 'New' must pave the way for 'Delete', and 'Try' must set the table for 'Catch'. An incessant list of such timed duos characterizes our object-oriented thinking. And all too often they seem like a curse, locking us into our own routines.

This is solely due to the fact, at least from the example, that here the state, the state of the variable has been changed between code block A and B.

That doesn't happen if you use the basic principle of Immutability combined with Pure Functions from the realm of FP. Sure, even in the OOP world these two elements are possible, but they require a conscious decision, a kind of switch you have to flip. And that is often no small effort. A simple "readonly" before the access modifier, say in the context of C#, is not at all sufficient.  But if you use languages like F#, where functional paradigms are the rule and not the exception - or, as they would say in Python, "Batteries included" - then everything becomes easier. No extra markers are required, no trawling through resharper warnings because you forgot to set a variable to readonly. Even pondering over naming methods that might have side effects becomes obsolete.

Temporal coupling also exists in functional programming (FP), but to a much rarer degree. And when it does occur, it is usually only in specialized contexts or in more complex operations, such as where the order of function calls is optimized for specific, deliberate side effects or for performance. Even in these cases, however, coupling is often less restrictive and more flexible to handle because the fundamentals of FP - such as Pure Functions and immutability - allow for more natural isolation between different parts of the code. This reduces the likelihood that the order of execution will have unexpected consequences.


Race Conditions & Deadlocks


Now you have a software architect at your side who can build the most elegant building out of code. But in the implementation there are always problems. Bugs appear that are devilishly difficult to find. Why? Because a new, insidious dimension has interfered with the code: time.

Characteristic of cloud architectures are, among other things, events or, when it comes down to receiving them, messages (at least that's the differentiation in Azure). They are a way of allowing individual services to communicate with each other asynchronously, i.e. decoupled in terms of time. An entire architecture pattern, EDA (Event Driven Architecture), has been dedicated to this type of communication and its implementation.

Events, messages and especially their "dance teachers", message brokers, are often used prematurely - a tip from practice - and thus a supposed decoupling is bought for the price of higher complexity, but through the back door, mostly via transitive dependencies, the two seemingly separate services then communicate with each other again. It's like two troublemakers in a school class who, after arguing, make jokes about the teacher via text message.

But what makes asynchrony so "risky"?

Concurrency errors are among the trickiest. They often occur late and in unexpected places, perhaps even on a completely different machine or a multi-core machine. This eats up time. Lots of time.

You must always think in two dimensions:

1. in the first dimension of the actual task.
 
2. and in the second, time, with the inevitable question in the baggage:

What could happen if several threads running at the same time get in each other's way?
 
They might end up in a race for a resource, known as a race condition. Or they might block each other because each is waiting for a lock that the other is holding. This is the infamous deadlock: both threads are trapped forever because neither advances and releases the lock the other needs.


Proposed solution


Do you know how to get around this?
 
By using pure functions and immutability.  
 
A pure function is a function that always returns the same output for the same input and, most importantly, has no observable side effects.  
 
Pure Functions (PF) offer the following advantages due to their definition.  
 
Readability and self-documentation. PF are self-contained; everything we need to understand the behavior of the function is defined either as input parameters or in the main body.  
 
This is also called functional honesty. The function honestly tells you what to expect. In OOP this is often not the case. It says string as the return type and what happens if we try an SQL injection - if we were evil hackers?  
 
We get an exception thrown in front of our heads, which in the worst case in a web application also tells the evil hooded man where he has to readjust so that his injection attack hits the mark next time.  
 
Caching. If the result depends only on the input and nothing else, then we can precompute and cache the output for each input. This technique is commonly referred to as memoization.  
 
Simplified testing. Testing can be done using simple input/output assertions. If we need to mock an external service, we can pass the mock as part of our input parameters.  
 
Immutability. If our functions have no side effects, we can run them without worrying about coordinating changes to our state. This is exactly what Race Conditions avoids!
 

In addition, Domain Driven Design can be implemented extremely well with FP, since F#, for example, is much more declarative than C#, much more concise, much more fitting when it comes to modeling domain logic. It's not for nothing that the book by F# god Scott Wlaschin, "Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F#", was celebrated not only on Amazon, but also by the German software architect elite (Dr. Gernot Starcke). No, I don't earn a cent from it, this is simply one of the best books I have ever read.

ScottWlaschinsBookAnnotatedByDrGernotStarcke.png
bottom of page