Discriminated unions are typically used as data holders, but occasionally I find myself having the need to prevent creation of a discriminated union, but to still be able to pattern match over it using familiar syntax.
For the sake of argument, let's say we represent a URI with a string, but I want to create a type that has a guaranteed validated URI (i.e., it's valid per the RFC). Just using Some/None doesn't work here, as I still want to access any invalid string as well. Also, I like a mild refactoring experience.
I can solve this problem as follows, which I think shows what I intend to do (leaving out the error cases for simplicity):
[<AutoOpen>]
module VerifiedUriModule =
module VerifiedUri =
type VerifiedUri =
private
| VerifiedUri of string
let Create uri = VerifiedUri uri // validation and error cases go here
let Get (VerifiedUri uri) = uri
let (|VerifiedUri|) x =
VerifiedUri.Get x
The extra level with the AutoOpen
is simply to allow unqualified access of using the active recognizer.
I may end up using a typical Result
type, but I was wondering whether this is a typical coding practice, or whether whenever I find myself doing something like this, I should hear a voice in my head saying "rollback, rollback!", because I'm violating classical functional programming principles (am I?).
I realize this is a case of information hiding and it looks much like mimicking OO class behaviors with data. What would be the typical F#'ish approach be (apart from creating a class with a private ctor)?
Aucun commentaire:
Enregistrer un commentaire