Anyone who’s read For Fun and Profit’s domain modeling exercise may see some similarities in today’s code. Mainly, I wanted to point out the value of quickly being able to create types that describe my functions here. For context, I’m in the middle of rewriting a sticky report that consumes data from a ton of different places… this is an example (slightly modified, to avoid spilling too many of {Redacted}’s beans.)
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 ShortCode = private ShortCode of string | |
type ProductShortCode = private ProductShortCode of ShortCode | |
type AccountShortCode = private AccountShortCode of ShortCode | |
module ShortCode = | |
let create s = | |
if System.String.IsNullOrEmpty s then None | |
elif s.Length > 15 then None | |
else Some (ShortCode s) | |
let value (ShortCode s) = | |
s | |
module AccountShortCode = | |
let create s = | |
match ShortCode.create s with | |
| Some sc -> Some (AccountShortCode sc) | |
| None -> None | |
let value (AccountShortCode (ShortCode s)) = s | |
module ProductShortCode = | |
let create s = | |
match ShortCode.create s with | |
| Some sc -> Some (ProductShortCode sc) | |
| None -> None | |
let value (ProductShortCode (ShortCode s)) = s |
In the above, we’re dealing with a thing called ‘Short Codes.’ At redacted, we have a shortened string which represents many of our more common domain objects, called a ‘Short Code’ which makes domain objects easily identifiable when viewed in spreadsheets.
In C# code, we’ll typically treat these objects as simple strings, or you deal with of domain types as espoused in Vladir Khorikov’s Pluralsight course, “Applying Functional Principles in C#”.
But in F#, you get drastically simpler code, that gives you similar benefits.
- Any change to ShortCodes can be done once, and all references using it get the change. That’s as DRY as it comes.
- ShortCodes can be equal to each other, but AccountShortCodes cannot be equal to ProductShortCodes cannot be (try it in FSI, you get compiler errors!)
- The modules allow us to retain the business logic, so we avoid the annoying issues of duplicating validation code everywhere. If I reference an AccountShortCode, it’s implied that I created one successfully in the first place!
The code to do stuff becomes quite easy:
type ConsumingRecord = { ShortCode : AccountShortCode ImportantValue : decimal } let m = AccountShortCode.create "APPLE";; // m is an AccountShortCode option, because of the // validation logic there, so we need it from the option, // before we push it into our consuming type. let n = { ShortCode = (Option.get m) ImportantValue = 5.0m}
The subtle thing here is that we have to actually deal with the fact that it’s an option. We CAN fail to get an AccountShortCode here, depending on what we pass in, but once we have a “ConsumingRecord” object, the validity of the ShortCode is guaranteed. This only works, however, because F# doesn’t do nulls. Once your language does nulls, it throws this stuff right out the window.
But I’m sure eventually C# will get that, too. You’ll just have to use an attribute to make your class less C-sharpy. 😉