访问DU成员的命名字段

时间:2017-03-08 22:17:35

标签: f# field discriminated-union

如何访问受歧视联盟成员的指定字段?

示例:

type Point = | Point of x : int * y : int
let p = Point(3, 1)
// how to access x-value or y-value of p here?

2 个答案:

答案 0 :(得分:5)

对于像您的示例那样的单个案例歧视联盟,您不需要使用match-with表达式。你可以这样做:

let (Point (x, y)) = p
printf "%i" x // 3

或者只是获取x并忽略y

let (Point (x, _)) = p
printf "%i" x // 3

答案 1 :(得分:4)

通常,具有命名字段的联合可以像任何其他联合类型一样工作:您可以通过match访问字段:

let x, y = 
    match p with 
    | Point (x1, y1) -> x1, y1

The F# documentation还提到了一种仅匹配某些命名参数的方法。在您的情况下,这意味着您可以写:

let xOnly = 
    match p with 
    | Point (x = x1) -> x1

如果您只有一个案例,请参阅@ p.s.w.g的答案。该答案中的代码适用于所有受歧视的联盟。对于具有命名字段的单例联合,您可以使用上面显示的特殊语法编写:

let Point(x = myX) = p

这会将字段x的值绑定到myX

根据评论

PS :为什么不能通过p.x立即宣读这些字段?您可以将区分联合视为一个小对象层次结构(并使用它,请参阅discriminated union documentation:"您通常可以使用区分联合作为小对象层次结构的简单替代方法") 。考虑以下DU:

type Shape = | Circle of r: float | Square of float

您可以将Shape视为超类。 CircleSquare是两个派生类,每个类都有一个float属性。您创建的CircleSquare的每个实例都将被转发为Shape

有了这个,很明显为什么你不能立即读出这些字段:你首先需要确定你正在查看哪个派生类,只有在转换到正确的子类之后你才能读出领域。

此对象层次结构视图与F#内部处理DU的方式非常接近:如果您在反射中查看DU类型,您将看到两个与您的联合案例具有相同名称的嵌套类型:

> typeof<Shape>.GetNestedTypes()
|> Seq.iter (fun t -> 
    let p = t.GetProperties() 
    let s = 
        p
        |> Array.map (fun p -> sprintf "%s: %s" p.Name p.PropertyType.Name) 
        |> String.concat "; "
    printfn "Nested type %s: %i Properties %s" t.Name p.Length s
);;
Nested type Tags: 0 Properties 
Nested type Circle: 4 Properties r: Double; Tag: Int32; IsCircle: Boolean; IsSquare: Boolean
Nested type Square: 4 Properties Item: Double; Tag: Int32; IsCircle: Boolean; IsSquare: Boolean

实际数据存在于子类的属性中。对于Circle,我们使用了命名字段,您会看到属性r。对于Square,如果有多个参数,我们在Item属性(或Item1Item2中有数据。其余的是编译器生成的:数字Tag字段,用于快速区分子类,以及两个bool属性,用于子类检查。

超类本身只有编译器生成的属性:

>     typeof<Shape>.GetProperties()
    |> Seq.iter (fun p -> printfn "Property %s" p.Name);;
Property Tag
Property IsCircle
Property IsSquare