F# 将 'a discriminated union to string

F# Convert 'a discriminated union to string

我正在尝试将可区分的联合转换为字符串,但我不明白为什么这段代码不起作用。

type 'a sampleType =
 | A of 'a
 | B of 'a

let sampleTypeToString x =
 match x with
 | A (value) -> string value
 | B (value) -> string value

这是 fsharp 交互输出

sampleTypeToString A(2);;
 Stopped due to error
 System.Exception: Operation could not be completed due to earlier error
 Successive arguments should be separated by spaces or tupled, and arguments involving function or method applications should be parenthesized at 3,19
 This expression was expected to have type
     'obj'    
 but here has type
     'int'     at 3,21

这里有两个错误:函数应用语法和丢失的通用性。

函数应用语法

这是错误“函数参数应由 space 或元组分隔...

在表达式 sampleTypeToString A(2) 中,您实际上有 三个 项,而不是两个:

  1. sampleTypeToString
  2. A
  3. (2)

不要让 A(2) 之间缺少 space 骗了你。不知何故,这些不被视为 "one expression"。 A(2) 是不同的术语。

因此,整个表达式 sampleTypeToString A(2) 被解释为函数 sampleTypeToString 应用于两个参数 - A(2)。这当然是行不通的,因为 sampleTypeToString 只接受一个参数,而术语 A 不适合,因为它的类型错误。

修复它的最简单方法是将首先评估的内容放在括号中:

   sampleTypeToString (A(2))

当然,由于 F# 中的函数(或构造函数)应用程序本身实际上并不需要括号,因此您可以删除第一组:

   sampleTypeToString (A 2)

或者,您可以使用管道:

   sampleTypeToString <| A(2)

这是可行的,因为管道运算符的优先级低于函数应用程序(它是所有应用程序中最高的),因此 A(2) 首先被评估,然后才通过管道进入 sampleTypeToString

失去通用性

这与错误“预期obj有关,但这里有类型int

这个有点棘手。查看您如何在 sampleTypeToString 中使用 string 函数?该功能在技术上是通用的,但不是以常规方式。它使用 statically resolved type constraints。无需过多赘述,这基本上意味着必须在编译时知道参数的具体类型。

但是你的函数sampleTypeToString接受了一个泛型参数sampleType<'a>,因此当它调用string时,它传递了'a类型的参数。但是 string 不能那样工作:它需要知道具体类型,不能是泛型 'a。所以编译器会尽力替换具体类型。因为它根本不知道 'a 可能是什么,所以它符合最一般的假设 obj.

因此,您的函数 sampleTypeToString 实际上最终采用 sampleType<obj> 类型的参数,而不是您可能期望的 sampleType<'a>

解决办法?声明你的职能是inline。这将告诉编译器不要实际将它编译为 .NET 方法,而是在调用它的任何地方扩展它的定义(有点类似于 C 中的 DEFINE 或 C++ 中的模板)。这样,类型 'a 将在编译时始终已知,并且挑剔的函数 string 将得到满足。

let inline sampleTypeToString x =
 match x with
 | A (value) -> string value
 | B (value) -> string value

sampleTypeToString (A 2)
sampleTypeToString (B "abc")
sampleTypeToString (A true)

或者,您可以将 string 的参数框起来,将其变成 obj:

let sampleTypeToString x =
 match x with
 | A (value) -> string (box value)
 | B (value) -> string (box value)

但是你会稍微改变 string 的语义,因为它有 special processing for certain types,以文化不变的方式转换为字符串。如果你把参数框起来,它基本上总是回落到 obj.ToString()。另外,您会为盒装值分配额外的堆。

还有一种选择,您可以取消 string 并自己调用 .ToString()

let sampleTypeToString x =
 match x with
 | A (value) -> value.ToString()
 | B (value) -> value.ToString()

请记住,这与调用 string.

完全 不同