展开 F# 单例区分联合元组类型

Posted

技术标签:

【中文标题】展开 F# 单例区分联合元组类型【英文标题】:Unwrap F# single-case discriminated union tuple type 【发布时间】:2017-11-18 08:12:52 【问题描述】:

我们可以使用像type Address = Address of string这样的解包函数来解包类型

let unwrapAddress (Address a) = a
let addr = Address "sdf"
let str = unwrapAddress addr

所以str 将是string 类型,但如果有这样的类型,这种方法将不起作用:

type Composite = Composite of integer:int * someStr:string
let unwrap (Composite c) = c

会产生错误

let unwrap (Composite c) = c;;
------------^^^^^^^^^^^
error FS0019: This constructor is applied to 1 argument(s) but expects 2

我能否以某种方式将复合类型解包为一个简单的元组?

【问题讨论】:

嗯,就像 PM,所以你可以写:let unwrap (Composite (i, s)) = i, s 我已经更正了您问题中的错误信息。您可能得到了另一个,因为您忘记将类型定义运行到 FSI 中。好问题,不过!我不知道这种边缘情况。 【参考方案1】:

您将类型定义为具有命名字段的单例区分联合:

type Composite = Composite of integer:int * someStr:string

这样定义的时候,union case的字段就不是简单的元组了。它们以特殊方式处理,例如,这些名称在编译代码中用作属性名称。模式匹配不会自动将元素转换为元组,因此您必须单独打开它们:

let unwrap (Composite(i, s)) = i, s

但是,您也可以在字段是普通元组的情况下定义单例联合。 (请注意,您需要在元组类型周围加上括号 - 否则,它最终也会以特殊方式处理,除了项目将编译为 Item1Item2。)

type Composite = Composite of (int * string)

有了这个定义,你的unwrap 函数就可以正常工作并提取元组值了:

let unwrap (Composite c) = c

你也可以像上一个例子一样使用嵌套模式来获取数字和字符串:

let unwrap (Composite(i, s)) = i, s

根据您是否编写 A of (T1 * T2) 或是否编写 A of T1 * T2,其行为会有所不同这一事实有点微妙 - 可能需要区分这两者,以便编译器知道是否将字段编译为两个单独的字段字段或作为System.Tuple<T1, T2> 类型的一个字段。我无法想象任何其他情况下差异会很重要。

【讨论】:

【参考方案2】:

在你的情况下,你可以写:

type Composite = Composite of int * string 

let unwrap (Composite (a, b)) = a, b

对应于:

let unwrap x = 
    match x with
    | Composite (a, b) -> a, b

这里发生的是 F# 允许您使用任意复杂的模式匹配内联解构函数参数。这一点在介绍单例DU时经常被提及,但很少有人得出结论,这导致人们认为单例DU在某种程度上是特殊的。

其实在有多个case的时候可以使用(只要每个case绑定同一套变量):

type Composite = Composite of int * string | JustString of string

let unwrapString (Composite (_, s) | JustString s) = s

但大多数时候,您会在更简单的类型上进行模式匹配,例如元组:

let f (a, b, c) = ...

或者更奇怪的是:

let f () = ...

这里的() 是对单元类型的唯一值的模式匹配 - 而不是通常描述的某种“无参数函数的视觉标记”。

【讨论】:

【参考方案3】:

这些都对我有用。这是您的 matching 语法,您通常会发现它与 match 语句一起使用,但它在 l.h.s 上。的一项任务。最初,这可能对元组最有意义,但您可以将其用于任何结构。

let (a,b) = (1,2)

let (x,_) = (4,5)

另外两个有趣的尝试:

let (head::tail) = [1;2;3;4]

FSI 响应警告 FS0025:此表达式的模式匹配不完整。例如,值“[]”可能表示模式未涵盖的情况。

“那是真的,”你大声说。 “我应该将其表达为匹配并包括一个空列表作为一种可能性”。最好将这些类型的警告冒泡成完全真实的错误(参见:warn as error 例如 --warnaserror+:25)。不要忽视他们。通过习惯或编译器强制方法解决它们。单个案例的歧义为零,因此请继续编写代码。

更有用 + 有趣的是 l.h.s 上的匹配语法。的功能分配。这很酷。精炼的功能,可以先把里面的东西解包,然后一步一步对内部进行操作。

let f (Composite(x,y)) = sprintf "Composite(%i,%s)" x y

f (Composite(1,"one"))

> val it : string = "Composite(1,one)"

关于您的代码:

type Address = Address of string //using unwrapping function like

let unwrapAddress (Address a) = a
let addr = Address "sdf"
let str = unwrapAddress addr

type Composite = Composite of integer:int * someStr:string
let unwrap (Composite(c,_)) = c
let cval = Composite(1,"blah")
unwrap cval

解决方法:

let xy = Composite(1,"abc") |> function (Composite(x,y))->(x,y)

...但是更好的方法是,假设您想保留单个案例 DU 的命名元素是...

let (|Composite|) = function | Composite(x,y)->(x,y)

let unwrap (Composite(x)) = x

let unwrap2 (Composite(x,y)) = (x,y)

...不是严格通过单例DU分解,而是通过单例分解Active Pattern

最后,您可以将方法附加到复合结构...

module Composite = 
  let unwrap = function | Composite(x,y)->(x,y)

关于使用此技术的最佳讨论之一已结束 here

另外,查看 unwrap 给我们的签名:一个接受 Composite(斜体)并返回 int(粗体)的函数

签名 -- val unwrap : 复合 -> int

【讨论】:

"These all work for me."。该代码实际上并没有编译(尽管编译错误与报告的错误不同)所以这实际上并没有回答这个问题。我发现有些模式匹配行为非常出乎意料,Tomas 的回答中对此进行了解释。 @TheQuickBrownFox:同意。我读到这个问题意味着以类似元组的方式分解,因此回顾了基础知识。或许这就是初衷?无论如何,您对问题的编辑使问题更加清晰。我跳过了这个,因为我认为它会很好。在编译器方面整理一下会很好。这是否已作为 github 请求提交?把它全部捕捉下来就好了。 @TheQuickBrownFox:本着原始问题(命名为 DU 元素)的精神,我添加了一个活动模式识别器作为解决方法。感谢您指出这一点。

以上是关于展开 F# 单例区分联合元组类型的主要内容,如果未能解决你的问题,请参考以下文章

空F#区分联合案例的C#类型

F#如何在递归可区分联合中指定类型限制

为啥 F# 可区分联合无法使其 TypeConverter 受到 JSON.NET 的尊重,而其他类型却可以?

同一个列表中非常相似的类型(继承 vs 区分联合 vs 泛型?)

F#:使用区分联合解构绑定

如何将元组类型转换为联合?