Last active
September 8, 2020 23:56
-
-
Save Savelenko/ee690d15d5712fad2b16d15480e7e97e to your computer and use it in GitHub Desktop.
Phantom types with private representation
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
namespace PhantomTypes | |
module User = | |
type Username<'a> = private Username of string with | |
member u.Value = let (Username username) = u in username | |
type Short = interface end | |
type Full = interface end | |
// Make a username suitable for authentication where a short username is required. | |
let authenticationUsername name : Username<Short> = | |
// Imagine validation here + Result in the signature | |
Username name | |
// Refine the phantom type variable, i.e. provide some guarantee. In this case that the resulting username contains | |
// the Full name, regardless of input. | |
let fullName database (username : Username<'a>) : Username<Full> = | |
// Find all user data in the DB. If 'username' was already Full, then nothing to do. Otherwise retrieve the full | |
// name from the specific column somewhere. | |
let (Username name) = username | |
let fullName = (failwith "Find full name in the DB") name database | |
Username fullName | |
module ClientModule = | |
open User | |
// Not allowed | |
// let root = Username "root" | |
// This is the only way a username can be made and it is forced to be Short. Module User could provide more smart | |
// constructor functions. | |
let root = User.authenticationUsername "root" // Username<Short> | |
// This functions does not care about Short/Long. The fact that we can have functions like this and functions which | |
// require a specific "format" at the same time is what makes this all useful; see below. | |
let isLongerThanFive (username : Username<'a>) = 5 < username.Value.Length | |
// A greeting needs to include a Full name. | |
let greeting (username : Username<Full>) = | |
sprintf "Dear %s" username.Value // We have a guarantee here | |
// Combine | |
let greetingToRoot db = | |
let rootFull = root |> User.fullName db // Username<Full> | |
if isLongerThanFive rootFull then "Too much trouble" else greeting rootFull | |
// Does not type-check, must go through f-n fullName | |
// if isLongerThanFive rootFull then "Too much trouble" else greeting root |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment