你能封装多案例歧视联盟吗?

时间:2014-06-13 19:48:00

标签: f# discriminated-union

我看到你可以enforce constructor usage单个案例的歧视联盟,你可以用多案例做同样的事吗?

例如

type MemberId = 
  | MemberId of int
  | MemberGuid of Guid

我目前正在尝试这样的fsi

val create : int -> T option
val create : Guid -> T option

但是我猜测像C#,F#不会允许你根据返回类型重载以进行解包:

val value : T -> string

修改---------------

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

看起来非常接近,但是打包器没有编译,我似乎无法弄清楚如何编写它

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

3 个答案:

答案 0 :(得分:5)

函数不能重载,但方法可以:

type MemberId = 
    private
    | MemberId of int
    | MemberGuid of Guid

    static member create id = MemberId id
    static member create guid = MemberGuid guid

答案 1 :(得分:3)

确实有一种方法可以使用一些内联技巧来重载输出参数:

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

通过这样做,你可以超载&#39;功能,甚至是输出参数。

无论如何,与单个案例DU的最大区别在于,现在解包器并不安全,这就是为什么解包器没有意义,除非在某些特定场景中。

在这些情况下,您可以考虑其他机制来展开值,例如公开函数isX或返回可以用活动模式补充的选项来解包。

话虽如此,如果你只对隐藏&#39;构造函数做一些验证,但不隐藏DU你可以简单地遮蔽构造函数,这里有一个例子:

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 :(得分:0)

以下是我需要的Gustavo答案中的一小部分代码,它本身可以单独使用

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)