layout: post
title: “Low overhead type definitions”
description: “No penalty for making new types”
nav: why-use-fsharp
seriesId: “Why use F#?”
seriesOrder: 9

categories: [Conciseness,Types]

In C#, there is a disincentive for creating new types ? the lack of type inference means you need to explicitly specify types in most places, resulting in brittleness and more visual clutter. As a result, there is always a temptation to create monolithic classes rather than modularizing them.

In F# there is no penalty for making new types, so it is quite common to have hundreds if not thousands of them. Every time you need to define a structure, you can create a special type, rather than reusing (and overloading) existing types such as strings and lists.

This means that your programs will be more type-safe, more self documenting, and more maintainable (because when the types change you will immediately get compile-time errors rather than runtime errors).

Here are some examples of one-liner types in F#:

  1. open System
  2. // some "record" types
  3. type Person = {FirstName:string; LastName:string; Dob:DateTime}
  4. type Coord = {Lat:float; Long:float}
  5. // some "union" (choice) types
  6. type TimePeriod = Hour | Day | Week | Year
  7. type Temperature = C of int | F of int
  8. type Appointment = OneTime of DateTime
  9. | Recurring of DateTime list

F# types and domain driven design

The conciseness of the type system in F# is particularly useful when doing domain driven design (DDD). In DDD, for each real world entity and value object, you ideally want to have a corresponding type. This can mean creating hundreds of “little” types, which can be tedious in C#.

Furthermore, “value” objects in DDD should have structural equality, meaning that two objects containing the same data should always be equal. In C# this can mean more tedium in overriding IEquatable<T>, but in F#, you get this for free by default.

To show how easy it is to create DDD types in F#, here are some example types that might be created for a simple “customer” domain.

  1. type PersonalName = {FirstName:string; LastName:string}
  2. // Addresses
  3. type StreetAddress = {Line1:string; Line2:string; Line3:string }
  4. type ZipCode = ZipCode of string
  5. type StateAbbrev = StateAbbrev of string
  6. type ZipAndState = {State:StateAbbrev; Zip:ZipCode }
  7. type USAddress = {Street:StreetAddress; Region:ZipAndState}
  8. type UKPostCode = PostCode of string
  9. type UKAddress = {Street:StreetAddress; Region:UKPostCode}
  10. type InternationalAddress = {
  11. Street:StreetAddress; Region:string; CountryName:string}
  12. // choice type -- must be one of these three specific types
  13. type Address = USAddress | UKAddress | InternationalAddress
  14. // Email
  15. type Email = Email of string
  16. // Phone
  17. type CountryPrefix = Prefix of int
  18. type Phone = {CountryPrefix:CountryPrefix; LocalNumber:string}
  19. type Contact =
  20. {
  21. PersonalName: PersonalName;
  22. // "option" means it might be missing
  23. Address: Address option;
  24. Email: Email option;
  25. Phone: Phone option;
  26. }
  27. // Put it all together into a CustomerAccount type
  28. type CustomerAccountId = AccountId of string
  29. type CustomerType = Prospect | Active | Inactive
  30. // override equality and deny comparison
  31. [<CustomEquality; NoComparison>]
  32. type CustomerAccount =
  33. {
  34. CustomerAccountId: CustomerAccountId;
  35. CustomerType: CustomerType;
  36. ContactInfo: Contact;
  37. }
  38. override this.Equals(other) =
  39. match other with
  40. | :? CustomerAccount as otherCust ->
  41. (this.CustomerAccountId = otherCust.CustomerAccountId)
  42. | _ -> false
  43. override this.GetHashCode() = hash this.CustomerAccountId

This code fragment contains 17 type definitions in just a few lines, but with minimal complexity. How many lines of C# code would you need to do the same thing?

Obviously, this is a simplified version with just the basic types ? in a real system, constraints and other methods would be added. But note how easy it is to create lots of DDD value objects, especially wrapper types for strings, such as “ZipCode“ and “Email“. By using these wrapper types, we can enforce certain constraints at creation time, and also ensure that these types don’t get confused with unconstrained strings in normal code. The only “entity” type is the CustomerAccount, which is clearly indicated as having special treatment for equality and comparison.

For a more in-depth discussion, see the series called “Domain driven design in F#”.