Can you encapsulate multi case discriminated unions?

蓝咒 提交于 2019-12-23 01:02:36

问题


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'
  | _ -> ()

回答1:


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())



回答2:


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



回答3:


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!