Tuesday, June 5, 2018

Functional Adventures in F# - Type-safe identifiers


In this post we will look at type-safe identifiers for things.

This post is part of a series:
Functional Adventures in F# - A simple planner
Functional Adventures in F# - Calling F# from C#
Functional Adventures in F# - Using Map Collections
Functional Adventures in F# - Application State
Functional Adventures in F# - Types with member functions
Functional Adventures in F# - Getting rid of loops
Functional Adventures in F# - Getting rid of temp variables
Functional Adventures in F# - The MailboxProcessor
Functional Adventures in F# - Persisting Application State
Functional Adventures in F# - Adding Snapshot Persisting
Functional Adventures in F# - Type-safe identifiers
Functional Adventures in F# - Event sourcing lessons learned

So, working on my game I just used to define identifiers like for example ShipId like the following:
type ShipId = Guid
type Ship = 
 {
  Id : ShipId
  Name : string
 }
let ship = { Id : Guid.NewGuid(); Name : "Nebuchadnezzar" }
Turns out this was not the best way to do things. From C# code you can just send in any Guid, nothing is typed. And you can assign a 'ShipId' to a 'UserId' in F# as well, as it is just a type alias. I found out that a hard way while tracking down a bug that turned out to be a copy paste issue.

So, now that I have a bunch of stuff already written, I had to change this to a single item discriminated union. Took a while but it seems to work much better with the type-safety.. The way I thought that I thought it already did.

So, lets redefine the ShipId as a single item discriminated union like
type ShipId = ShipId of Guid
The code to use it from code is like:
let ship = { Id : ShipId (Guid.NewGuid()); Name : "Nebuchadnezzar" }


Usage from C#

My game is partially written in C# as it just works better for some tasks. So interfacing with the new typed ids will be like:

Getting the underlying Guid
var shipIdGuid = ship.Id.Item,
Creating a new ShipId from a Guid
var shipId = ShipId.NewShipId(Guid.NewGuid());

So a little bit more hassle from C#, but not that much, and most of my code is just service layer code creating F# records from external input. And the little extra overhead is worth it for the type-safety!

All code provided as-is. This is copied from my own code-base, May need some additional programming to work. Use for whatever you want, how you want! If you find this helpful, please leave a comment, not required but appreciated! :)

Hope this helps someone out there!
Until next time: Work to Live, Don’t Live to Work


No comments:

Post a Comment