6

F#: Documenting Domain with Unions and Pattern Matching

 2 years ago
source link: https://medium.com/@danielmartinez_27571/f-documenting-domain-with-unions-and-pattern-matching-658c8ae8a9d7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

F#: Documenting Domain with Unions and Pattern Matching

Benefits of simplicity

Coding is hardly ever the valuable thing we developers do. How we think about the problem and its solution is the truly valuable thing. As part of designing our solutions we want make sure we make invalid states unrepresentable and along the way use our types to document the meaning behind values or behaviors which make our application special.

In F#, this is just too easy to achieve and one of the main reasons I personally love using F#.

We often use tagged unions to better design our models and express relationships between values while still having different constructors or shapes.

For example, handling a http responses usually means having two distinct shapes, success and error. Each response must exactly have a set of properties and cannot have others. Where success has a body of the response error has an error message.

type Success<'body> = { Body: 'body }type Error = { Message: string }type AppResponse<'body> =
| AppSuccess of Success<'body>
| AppError of Error

Here we can express the relation between 2 different kind of responses while ensuring each has its own constructor and data members.

But we can go further still. In most cases we hardly work with a string that has no other semantic meaning behind it. It usually means something that cannot be confused or interchanged with another string. For example HTTP code strings and country names, although both are strings they should not be confused.

type CountryStatus = { CountryCode: string; Status: string }let makeCountryStatus (countryCode: string) (status: string) =
{ CountryCode = countryCode
Status = status }

Modeling our type like this adds the probability to confuse both strings.

makeCountryStatus "AUD" "OK" // Desired
makeCountryStatus "NOT_FOUND" "US" // Also compiles

So now we need to write tests to prevent this from happening. Another more elegant and effective solution is to unload this checking onto the type system. This can enforce the rules and further document the value.

type CountryCode = CountryCode of string
type Status = Status of stringtype CountryStatus2 =
{ CountryCode: CountryCode
Status: Status }let makeCountryStatus2 (countryCode: CountryCode) (status: Status) =
{ CountryCode = countryCode
Status = status }

This now ensures we don’t confuse the types without having to write many example based tests.

makeCountryStatus2 (CountryCode "AUD") (Status "OK") // Desired
makeCountryStatus2 (Status "NOT_FOUND") (CountryCode "US") // Fails to compile

Now I personally prefer this way. But there is more to take into consideration, and this is how we can use the actual value, the string behind the tag, if you will. Since we are wrapping a value with a type to ensure we don’t confuse the two, we can’t use the value directly. We have to unwrap it.

For this you only need to pattern match and get the value out.

let myCode = CountryCode "AUD"let countryCodeToValue (countryCode: CountryCode) =
match countryCode with
| CountryCode str -> strcountryCodeToValue myCode // returns "AUD"

You can also inline pattern match, which is super handy and succinct.

let myCode = CountryCode "AUD"
let (CountryCode code) = myCode // creates a variable code with value "AUD"

Our type can have more behaviors related to it and we don’t want to wrap and unwrap the value every time we need to use it. We can create static functions within a type and define the functions we need that can work with the type.

Keeping these functions together within type increases cohesion, maintainability and safely extends the functionality of the type.

type HttpCode =
private HttpCodeStr of string
static member make str = HttpCodeStr str
static member value (HttpCodeStr code) = code
static memberequals (HttpCodeStr code1) (HttpCodeStr code2) = code1 = code2let httpCode = HttpCode.make "Ok" // construct type
let httpCode2 = HttpCode.make "Ok"HttpCode.equals httpCode httpCode2 // returns true
HttpCode.value httpCode // return inner value "Ok"

Wrapping Up

F# is an awesome language which makes modeling your domain types and logic easy and effective. We can take advantage of this and even document the meaning behind “simple” or native values like strings, integers or booleans.

We can have convenient functions which help us work with these types and use pattern matching to get a hold of the native value when needed.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK