I see that you can enforce constructor usage of single-case discriminated unions, can you do the same with multi-case?
for example
type MemberId =
| MemberId of int
| MemberGuid of Guid
I'm currently trying in the fsi
like this
val create : int -> T option
val create : Guid -> T option
but I'm guessing like C#, F# won't allow you to overload based on return type for the unwrap:
val value : T -> string
Edit ---------------
MemberId.fsi =
module MemberId
open System
type _T
val createId : int -> _T option
val createGuid : Guid -> _T option
val value : _T -> 'a
MemberId.fs =
module MemberId
open System
type _T =
| Id of int
| MemberGuid of Guid
let createId id = match id with
| x when x>0 -> Some(Id(id))
| _ -> None
let createGuid guid = Some(MemberGuid( guid))
let value (e:_T):int = e
Appears to be pretty close, but the unwrapper doesn't compile and I can't seem to figure out how to write it
TestConsumer MemberIdClient.fs =
module MemberIdClient
open System
open MemberId
let address1 = MemberId.create(-1)
let address2 = MemberId.create(Guid.Empty)
let unwrapped1 =
match address1 with
| MemberId x -> () // compilation error on 'MemberId x'
| _ -> ()
Indeed there is a way to overload with the output parameter, using some inline tricks:
open System
type MemberId =
private
| MemberId of int
| MemberGuid of Guid
type Create = Create with
static member ($) (Create, id ) = MemberId id
static member ($) (Create, guid) = MemberGuid guid
type Value = Value with
static member ($) (Value, d:int ) = function MemberId id -> id | _ -> failwith "Wrong case"
static member ($) (Value, d:Guid) = function MemberGuid guid -> guid | _ -> failwith "Wrong case"
let inline create x : MemberId = Create $ x
let inline value x : 'IntOrGuid = (Value $ Unchecked.defaultof<'IntOrGuid>) x
let a = create 1
let b = create (Guid.NewGuid())
let c:int = value a
let d:Guid = value b
By doing this you can 'overload' functions, even on output parameters.
Anyway the big difference with the single case DU is that now the unwrapper is not 'safe', that's why the unwrapper makes little sense, except in some specif scenarios.
In these cases you may consider other mechanisms to unwrap the values, like exposing functions isX
or returning options which may be complemented with an active pattern to unwrap.
Having said that, if you are only interested in 'hiding' the constructors to do some validations, but not hiding the DU you can simply shadow the constructors, here's an example:
open System
type T =
| MemberId of int
| MemberGuid of Guid
// Shadow constructors
let MemberId x = if x > 0 then Some (MemberId x) else None
let MemberGuid x = Some (MemberGuid x)
let a = MemberId 1
let b = MemberGuid (Guid.NewGuid())
let c = MemberId -1
// but you can still pattern match
let printValue = function
| Some (MemberId x) -> sprintf "case 1, value is %A" x
| Some (MemberGuid x) -> sprintf "case 2, value is %A" x
| None -> "No value"
let ra = printValue a // "case 1, value is 1"
let rb = printValue b // "case 2, value is 67b36c20-2..."
let rc = printValue c // "No value"
// and if you want to use an overloaded constructor
type T with
static member Create id = MemberId id
static member Create guid = MemberGuid guid
let d = T.Create 1
let e = T.Create (Guid.NewGuid())
// or using the inline trick
type Create = Create with
static member ($) (Create, id ) = MemberId id
static member ($) (Create, guid) = MemberGuid guid
let inline create x : T option = Create $ x
let d' = create 1
let e' = create (Guid.NewGuid())
Functions cannot be overloaded, but methods can:
type MemberId =
private
| MemberId of int
| MemberGuid of Guid
static member create id = MemberId id
static member create guid = MemberGuid guid
Here's the little bit of code from Gustavo's answer I needed that appears to work all by itself
module MemberId
open System
type MemberId =
| MemberId of int
| MemberGuid of Guid
// Shadow constructors
let MemberId x = if x > 0 then Some (MemberId x) else None
let MemberGuid x = Some (MemberGuid x)
来源:https://stackoverflow.com/questions/24212865/can-you-encapsulate-multi-case-discriminated-unions