Code as Data: Structuring business rules in F#

One of our products, ClearTDS, is built completely on F#. I gave a talk about our choice to use F# recently—this talk explains why F# ended up as the best choice for us for this product.

One of the business requirements was to customise the UI for specific modes of operation. Some fields are valid only in certain years (fields get added or removed in TDS returns), some fields are valid only for certain types of TDS returns (salary or non-salary, original or correction, etc). The number of variables that we end up dealing with is very large.

We ended up with a approach where we have a data structure that defines the UI, in a very declarative way. This structure would be filled with both data and functions, and we had a separate layer which would evaluate this structure with a given context (current screen) in order to create the JSON that would actually drive the UI.

Let me explain this approach with a rather long code sample:

The last statement is the actual UI specification. It’s a data structure that also has functions that determine behaviour of each column. We had a separate function that would take this specification along with the current context, and create a final UI specification for our frontend:

// Given a screen context and the current data item, 
// output the configuration that would drive the UI
let getConfig<'T> (columnSpec) (context) (row : 'T) =
// Implementation...

The output of this function would be a simple JSON specification that the frontend works with:

[
{ "field": "Date", "visible": true, "editable": true },
{ "field": "SectionCode", "visible": true, "editable": false },
{ "field": "Amount", "false": true, "editable": false },
]

There are many advantages to this approach:

  • It is possible to specify the complete behaviour of your business logic in single place. It’s compact, everything is easy to read at a glance.
  • It’s very easy to unit test.
  • Easy to modify. It’s easy to add rules or change rules.
  • Each ‘selector’ function is very small and simple, but you can combine them in surprisingly powerful ways.

I find this approach to writing your business logic fascinating. There have been many approaches to using markup languages to define your logic: some of the old-school XML config files for Java projects come to mind. It’s often easier and much cleaner to define your data with code instead. And it’s especially useful with languages like F#, where the type system does a lot of your work for you :)


Footnotes:

  1. Higher ordered functions are nothing new, a lot of languages have them. It just feels very natural when you apply it to F#. For example, Java won’t let you easily create literal lists / dictionaries, so even if you are using Java 8, this would be very verbose to implement.
  2. This coding style may not work for everyone. I’m using a very wide column size — in these types of situations, I prefer to keep longer lines instead of wrapping it into multiple lines. I feel the readability is much better in this tabular format.

PS: We’re hiring. Please reach out to me on ankit@cleartax.in if you’re interested in working with us!