In my last article about Functional domain modeling, I explored the expressiveness of F# in modeling a domain. I fell in love with the simplicity and expressiveness of the language. In this article I will attempt to explore function purity in F#. In F#, functions are first class citizens because it allows to pass function as an argument to other function, return a function or assign function to a variable. Initially I found it a bit hard to wrap my head around the concept of treating functions as first class citizens. In fact one of the biggest challenge for me was surprisingly not the weird syntax of F#, but to think in terms of functions.

Coming from an OO background and not very comfortable with F#, below was my first attempt to write a use case. I basically tried to mimic a use case in a typical ports and adapters project from one of the C# projects. This use case serves a basic purpose which is to update the dimensions of a product, if it exists, of course.

Pretty straight forward. The use case accepts a DataStore dependency using which the update method can query the Products table for existence of a product. If the product is found then the product is fetched from the data store and dimensions are updated. Finally, the product with updated dimensions is persisted in the database using the data store.

Though this use case works, there are several problems with this way of writing code. Lets take a look at the problems:

Testability

The code above is not easily testable. Why you ask? Lets look at how many ways things can go wrong with this code.

  1. What if the constructor arguments are null?
  2. What if the productExists data store method returns an exception.
  3. What if updateDimensions, which in turn calls getProduct and updateProduct methods on the data store returns exception?
  4. What if the product does not exist?
  5. What if product dimensions does not exist?
  6. what if ....

and the list will go on ..

So many scenarios to test with just one dependency. What if I add ExternalService as another dependency to this use case? Imagine the number of ways this third party service call can go wrong. The point is that whenever we deal with external systems, be it database or an external service, we are entering a world of uncertainty and we do not have much control over the behavior of those external systems. It would be nice if the use case and the domain model could completely avoid any type of IO operations or side affects. Sounds like a nice idea, but a use case which does not perform IO is next to useless. Stay with me and we will explore a way to minimize or avoid IO and side affects in the use case and domain model by the end of this article.

Hidden dependencies

One of the beautiful things about F# is its expressiveness. The update method in our example is all but expressive about its intent. It has a dependency on DataStore which is not evident when we look at the method signature. This is what I call as a hidden dependency. What if in future someone modifies the update method and calls a completely different method on the datastore? Since the update method is not expressive enough, developers can assume a lot of things. Is there a way to avoid these hidden dependencies? Can we make the update method a little more expressive to avoid mistakes by future developers? Again, by the end of this article, we will try to re-write the update method to make it a little bit more expressive.

Referential Transparency

Methods in our use case are not referentially transparent. Especially the update method is not by any means. What is referential transparency? A function is referentially transparent if the function can be replaced with its corresponding value without affecting the behavior of the system. For example,

if, x + y = 10

then, the expression x + y + z = 20 can be written as 10 + z = 20

In our use case the method update, depending on whether one of the data store methods return an exception, may or may not update the product dimensions. We can never assume that a call to update method always updates product dimensions. Due to this uncertainty, I will not be able to include update method in a method chain like this and assume that warehouse system will be always notified after the product dimensions update.

There are other smaller problems with this code. But for now, lets focus on fixing the problems that we identified above and making this use case a little bit better.

Here is an attempt to fix some of the problems we discussed above.

First thing worth noticing is that these functions are not inside a type because there is no common dependency between these two functions. These functions exists on its own (under a module) and only accepts parameters that they can work with. The function name and signature are self documenting and they clearly communicate the intent. There is no scope of nasty exceptions bubbling up because of an unstable dependency. These are pure functions.

Lets see how easy it is to test these functions.

Simple and straightforward. There is no need to setup mocks for different scenarios here. You can create test data to your hearts content and test this function out. I have used Autofixture, but Property based tests are better suited for testing these functions.

Coming back to the referential transparency. The find and update functions are referentially transparent. For instance, if the Product exists in the list of products then find will always return the Product. Otherwise, it always returns None, consistently.

So, we have seen how getting rid of impure operations tremendously simplifies the program. But wait, this new shiny pure functional program does nothing useful. The end goal of this program is to update dimensions in the database. How do we make sure that we stick to function purity but also be able to perform impure operations like persisting data in a database or calling a third party service etc? You need to do the following:

  1. Push IO (impure) functions at the boundary of your domain.
  2. Call pure functions from the impure functions and not the other way round.

As long as an impure function calls a Pure function you are good. When a pure function calls an impure function then the whole method chain becomes impure.

You would have guessed where we are going with this kind of separation between pure and impure functions. OK, no points for guessing, moving impure functions at the boundary and calling pure functions from there naturally leads you to clean architecture.

Let's see how does the call to pure functions from an impure function looks like.


tryUpdateProductDimensionsis an impure function because it calls getAllProducts which in turn calls allProducts from the DB module. DB module performs a bunch of impure operations. We can call it as the core of impurity (pun intended). For the sake of completeness, here is how I have implemented the DB module.

Conclusion

For a developer who is not fully familiar with F# or the functional programming paradigm may quite easily fall into the pit of writing OO style code with F# just like me in the beginning of this article. In F#, separating impure functions from pure functions requires discipline. As I understand, F# has no in-built magic to prevent you from mixing pure and impure functions. Haskell in that respect is pretty strict because it forces you to wrap impure functions using an IO monadic system. In simple terms an IO monad is an abstract data structure which elevates a value. Once the value is elevated you can no longer access the original value. To access the original value, you must use one of the mapping functions of the monad. I can write a similar IO monad in F#, but is it really worth the effort? The F# compiler would not honor such an IO monad. Though I think that it may be a good idea to enforce it as a coding convention for your projects.

Let me know in the comments section below what you think about the IO system in Haskell. Should F# support something similar? If yes, why and if no why not?