F#如何通过案例标识符比较判别联合类型

4
有没有一种方法可以通过它们的case-identifier在F#中比较判别联合?
type MyUnion =
| MyString of string
| MyInt of int

let x = MyString("hello")
let y = MyString("bye")
let z = MyInt(25)

let compareCases a b =
// compareCases x y = true
// compareCases x z = false
// compareCases y z = false

如何以通用的方式实现compareCases函数?

例如下面的示例,但更加通用(反射也可以):

let compareCases a b =
  match a with
  | MyString(_) -> match b with | MyString(_) -> true | _ -> false
  | MyInt(_) -> match b with | MyInt(_) -> true | _ -> false

这非常类似于https://dev59.com/TGnWa4cB1Zd3GeqPwxjY - John Palmer
谢谢,看起来很有趣,但它所有的案例都由相同类型支持,我不确定它如何适用于我的情况,因为我的类型不同。 - Grozz
1
如果“Tag”是一个支持的公开属性,那么这将是一种非常高效、简单的方法来实现这个功能。 - ildjarn
4个回答

6
使用GetType()的问题在于,如果有2个“无数据”的情况,它将失败。
以下是一种方法:(已编辑,因为以前的UnionTagReader没有被缓存)
type MyDU =
    | Case1
    | Case2
    | Case3 of int
    | Case4 of int

type TagReader<'T>() =
    let tr = 
        assert FSharpType.IsUnion(typeof<'T>)
        FSharpValue.PreComputeUnionTagReader(typeof<'T>, System.Reflection.BindingFlags.Public)

    member this.compareCase (x:'T) (y:'T) =
        (tr x) = (tr y)

let tr = TagReader<MyDU>()

let c1 = Case1
let c2 = Case2
let c3 = Case3(0)
let c3' = Case3(1)
let c4 = Case4(0)

assert (c1.GetType() = c2.GetType() )  //this is why you can not use GetType()

assert tr.compareCase c1 c1
assert not (tr.compareCase c1 c2)
assert tr.compareCase c3 c3'
assert not (tr.compareCase c3 c4)

+1,虽然您的回答是唯一正确的答案,但我希望我可以给您点赞多次。但是,类型推断似乎有些奇怪 - 我不认为您需要这两个泛型声明,但确实需要。而且不知何故,尽管PreComputeUnionTagReader是非泛型的obj -> int,但我们最终得到了tagReader作为'UnionType -> int。您能解释其中任何内容吗,或者能指向一些相关文档吗? - phoog
phoog: 我没有文档。我也发现静态推断导致我的从obj -> int的强制转换失败了,变成了'UnionType -> int。所以我把代码移到了不受静态推断影响的地方。我模糊地猜测,当我将其移出时,它从功能性静态推断规则变为了.NET对象差异规则。 - jyoung

4
首先,你可以这样改进你的示例:
let compare = function
| MyString _, MyString _, | MyInt _, MyInt _ -> true
| _ -> false

但是这里有一个最佳方式(反射最小!):
let compare a b = a.GetType () = b.GetType ()

1
这应该可以解决问题。
open Microsoft.FSharp.Reflection

type MyUnion =
    | MyString of string
    | MyInt of int

let x = MyString("hello")
let y = MyString("bye")
let z = MyInt(25)

let compareCases a b =
    FSharpValue.GetUnionFields (a, a.GetType()) |> fst
        = (FSharpValue.GetUnionFields (b, b.GetType()) |> fst)

虽然如此,为了对这些值进行任何操作,您仍需要进行模式匹配,所以说实话我并不太明白其中的意义。


0
let compareCases (a : MyUnion) (b : MyUnion) =
    a.GetType().Name = b.GetType().Name

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接