Adding Meaning to Primitive Types in fsharp

Published Feb 12, 2018Last updated May 23, 2018

One of the recommended guidelines in Domain Driven Design is modelling the domain ideas using the domain type (CustomerName, CustomerId) instead of using their corresponding primitive type (string, int). In fsharp, with the help of Single-Case Discriminated Union, we can follow this guideline with minimal effort.

While following this practice in one of my recent project in fsharp, I came across a compelling use case, and I used a lesser-known approach to solve the problem. In this blog post, I will be sharing the method that I employed to address the use case.

The Problem Domain

Let's assume that we are developing a F# Application for managing our expenses.

One of the core domain idea that we'll use a lot is Money.

In .NET, the primitive data type decimal is appropriate for financial and monetary calculations.

Hence to model Money in fsharp, what we need is a Single Case Discriminated Union type wrapping the decimal type.

type Money = Money of decimal

To keep things simple, we are not going to consider currency and exchange rates.

The next thing is modelling the income source and expense categories. For brevity, let's keep just two in each.

type IncomeSource =
| Salary
| Royalty

type ExpenseCategory =
| Food
| Entertainment

The final domain representation that we need is Transaction, which is either a Credit or a Debit.

type Income = {
  Amount : Money
  Source : IncomeSource
} 

type Expense = {
  Amount : Money
  Category : ExpenseCategory
}

type Transaction =
| Credit of Income 
| Debit of Expense

For our small personal finance managing application, these domain models are just sufficient. So, let's dive into the use cases.

Use Case #1

Our first use case is finding the expenditure on a given ExpenseCategory from the list of the transaction

ExpenseCategory -> Transaction list -> Money

To implement it, let's create an intermediate function getExpenses, that retrieves the expenses from a list of the transaction.

// Transaction list -> Expense list
let rec getExpenses transactions =
  getExpenses' transactions []
and getExpenses' transactions expenses =
  match transactions with
  | [] -> expenses
  | x :: xs -> 
    match x with
    | Debit expense ->
      getExpenses' xs (expense :: expenses)
    | _ -> getExpenses' xs expenses

With the help of this getExpenses function, we can now implement the use case as follows

// ExpenseCategory -> Transaction list -> Money
let getExpenditure expenseCategory transactions =
  getExpenses transactions
  |> List.filter (fun e -> e.Category = expenseCategory)
  |> List.sumBy (fun expense -> 
    let (Money m) = expense.Amount // <1>
    m // <2>
  )
  |> Money // <3>

<1> Unwrapping the underlying decimal value from the Money type.

<2> Returning the unwrapped decimal value.

<3> Putting the decimal value back to Money type after computing the sum.

Now we have an implementation for use case #1 and let's move to the next.

Use Case #2

The second use case is computing the average expenditure on a given ExpenseCategory from the list of transactions

// ExpenseCategory -> Transaction list list -> Money
let averageExpenditure expenseCategory transactionsList =
  transactionsList
  |> List.map (getExpenditure expenseCategory)
  |> List.map (fun (Money m) -> m) // <1>
  |> List.average
  |> Money // <2>

Like the use case #1,

<1> Unwraps the decimal value from the Money type and returns it.

<2> Put the result of the average function back to the Money type.

Use Case #3

Our final use case is from the list of transaction, we have to compute the balance money.

As we know, the formula for computing the balance is

balance money = (sum of credited amount of money) - (sum of debited amount of money)

Applying the same in fsharp, we will end up with the following implementation

// Transaction list -> Money
let balance transactions =
  transactions
  |> List.map ( function
                | Credit x -> 
                  let (Money m) = x.Amount // <1>
                  m
                | Debit y ->
                  let (Money m) = y.Amount // <2>
                  -m
              )
  |> List.sum
  |> Money <3>

In the balance function, we have used an optimised version of the formula.

Instead of computing the sum of credits and debits separately, we are applying the unary minus to all debits and calculating the sum of these transformed values in a single go.

Like what we did for the use cases #1 and #2, Here also we are unwrapping the decimal type from the Money type at <1> and <2>, and at <3> we are wrapping the decimal type back to Money type after computing the sum.

Unwrapping and Wrapping Boilerplate

Though we have a good domain model in the form of Money, a discouraging aspect is the repetition of unwrapping and wrapping code to perform calculations on the Money type. We might be repeating the same for the future use cases as the Money type is an integral part of the application.

Is there any way to get rid of this redundancy?

Yes, There is!

List.sumBy function

The solution that we are looking for is lurking in the signature of the List.sumBy function

List.sumBy : ('T -> ^U) -> 'T list -> ^U 
  (requires ^U with static member (+) and ^U with static member Zero)

The sumBy function makes use of Statically resolved type parameters to define the the target type ^U (to be summed). As indicated in the signature, the type ^U should have two static members + and Zero.

In our case, the primitive type decimal already has these static members, and the wrapper type Money doesn't have it. Hence we are doing the wrapping and unwrapping!

Let's add these two static members in the Money type

{callout="yes"}

type Money = Money of decimal with
  
  // Money * Money -> Money
  static member (+) (Money m1, Money m2) = Money (m1 + m2) // <1>
  
  static member Zero = Money 0m // <2>

<1> Unwraps the decimal type for two operands of Money and returns the summed value with the target type Money

<2> Returns the zeroth value of Money

We can now refactor the getExpenditure function as

 let getExpenditure expenseCategory transactions =
   getExpenses transactions
   |> List.filter (fun e -> e.Category = expenseCategory)
-  |> List.sumBy (fun expense -> 
-    let (Money m) = expense.Amount 
-    m
-  )
-  |> Money
+  |> List.sumBy (fun expense -> expense.Amount)

List.average function

Like the List.sumBy function, the List.average function has a requirement.

// Signature
List.average : ^T list -> ^T 
  (requires ^T with static member (+) and 
    ^T with static member DivideByInt and 
    ^T with static member Zero)

Out of these three requirements, we have already covered two (+ & Zero) while accommodating the List.sumBy function's requirement.

So, we just need to implement DivideByInt static member in Money to compute the average.

type Money = // ...
  // ...
  static member DivideByInt ((Money m), (x : int)) =
    Decimal.Divide(m, Convert.ToDecimal(x))
    |> Money

With this change, we can refactor averageExpenditure as below

  let averageExpenditure expenseCategory transactionsList =
    transactionsList
    |> List.map (getExpenditure expenseCategory)
-   |> List.map (fun (Money m) -> m)
-   |> List.average
-   |> Money
+   |> List.average

Unary Minus on Money Type

The final function that needs our help is the balance function. To make the unary minus work on Money type, we can make use of operator overloading in fsharp.

type Money = // ...
  // ...
  static member (~-) (Money m1) = Money -m1

And then we can refactor the balance function

 let balance transactions =
   transactions
-  |> List.map ( function
-                | Credit x -> 
-                  let (Money m) = x.Amount <1>
-                  m
-                | Debit y ->
-                  let (Money m) = y.Amount <2>
-                  -m
-              )
-  |> List.sum
-  |> Money 
+  |> List.map ( function
+                | Credit x -> x.Amount
+                | Debit y -> -y.Amount)
+  |> List.sum

Summary

In this blog post, we saw how to avoid some boilerplate code while creating domain types for primitives in fsharp. On a side note, by adding the static members + and Zero we made the Money type a Monoid. The List.sum and List.sumBy functions are designed to act on any Monoids and hence we solved the use cases with less code!

The source code is available on GitHub

Reposted from my personal blog

Discover and read more posts from Tamizhvendan S
get started