We have a reporting application at {Redacted}, one I’ve spent more than a few hours maintaining. A colleague of mine and I (the junior developer I spoke of in a prior post) met up with the business owner of the reporting application, to talk about rebuilding the application in more functional way.
This application is not a terribly complicated one. In short, it takes data from multiple data sources, and re-aggregates it into a series of reports. It has however, had multiple developers come and “peek in”, drop code into it, and leave. In the current C# implementation, it’s 5 distinct assemblies. There are 8 test assemblies to go with it. It has a lot of business value riding on it being robust and correct, so the tests do seem warranted.
A word to the wise though… simple things that are more complicated than they should be AND that have a ton of tests are REFACTORING GOLD. Nothing feels better than taking something complex and unwinding it to it’s core essentials, and nothing feels better than doing it in F#!
We took a page out of For Fun and Profit’s DDD page and focused for nearly an hour on the objects and processes involved in this report. Naturally, the process seemed extremely solvable in a functional way.
Unfortunately, due to time constraints on the rest of the day we were only able to start with some very high level type definitions, but those type definitions described our problem in such a way that the business owner was able to see and understand.
We did the whole thing with an instance of VS Code and Ionide, and had types describing the objects and functions involved, all with just a simple “domain” setup in the process. Did we implement anything particularly? Well, kinda yeah, this is perfectly compile-able F# code, which as opposed to Gherkin or some other spec-based “code”, does not need a secondary interpreter.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Name = string | |
type Percentage = decimal | |
type Return = decimal | |
type TargetReturn = decimal | |
type Model = Name | |
type Allocation = ( Model * Percentage ) list | |
type MarketValue = decimal | |
type AccountReturn = Return | |
type Account = { | |
TAllocations : Allocation * MarketValue; | |
TMinusOneAllocations: Allocation * MarketValue; | |
Return: AccountReturn | |
} | |
type ModelReturn = decimal * Model | |
type RealizedTrackingError = AccountReturn -> TargetReturn | |
type SleeveReturn = ModelReturn -> RealizedTrackingError | |
type SleevedMarketValue = Allocation -> MarketValue | |
// no tracking error return | |
type SleeveLevelPerformance = Account -> ModelReturn list -> TargetReturn |
That’s the crux of this application and function. I think we may be able to get this down to a few pages of code… as opposed to the novella you’d call it now. #FeelingHopeful