关于 f#:如何编写参数是类型但不是类型值的函数?

 2022-01-08 

How to write function where argument is type but not typed value?

我想将几十个枚举类型的字符串表示形式转换为枚举值。
将字符串转换为具体类型很容易:

1
Enum.Parse(typeof<FontStyle>,"Bold") |> unbox<FontStyle>

但现在我想编写类型和字符串作为参数的函数。
我能写的最好的是:

1
2
3
4
> let s2e (_: 'a) s = Enum.Parse(typeof<'a>,s) |> unbox<'a>;;
val s2e : 'a -> string -> 'a
> s2e FontStyle.Regular"Bold";;
val it : FontStyle = Bold

有没有什么办法可以写出这样的东西,但将类型本身作为第一个参数?


该函数需要接受一个类型参数,这将是返回的枚举的类型。但是,您不需要使用代码中任何地方都没有使用的"假"参数来指定类型 - 您可以在调用函数时使用与调用 defaultof<SomeType> 时相同的符号来指定实际的类型参数.

以下修改后的函数采用仅出现在返回类型中的单个类型参数(为避免混淆 SO 代码格式化程序,我将代码中的 ' 替换为 '):

1
2
> let parseEnum<′a> s = Enum.Parse(typeof<′a>,s) |> unbox<′a>;;
val parseEnum : string -> ′a

调用函数时,需要明确指定类型:

1
2
> parseEnum<FontStyle>"Bold";;
val it : FontStyle = Bold

您的解决方案非常接近这一点 - 唯一的变化是您可以显式指定类型参数,而不是提供见证值来指导类型推断。


托马斯的回答很好。此外,请注意,您可以应用 F# 的 enum 约束来防止使用无意义的类型参数:

1
2
let parseEnum<'t,'u when 't:enum<'u>> s = System.Enum.Parse(typeof<'t>, s) :?> 't
(parseEnum"Class" : System.AttributeTargets)

现在调用 (parseEnum"Test" : int) 将在编译时失败,因为 int 不是枚举。

编辑

因为我们实际上并没有对底层类型 'u 做任何事情,所以我们不需要 F# 枚举约束的全部功能,正如 ssp 在评论中指出的那样。这更容易使用,因为它只有一个类型参数:

1
2
let parseEnum<'t when 't :> System.Enum and 't : struct> s =
  System.Enum.Parse(typeof<'t>, s) :?> 't

请注意,struct 约束阻止使用 System.Enum 本身作为类型参数。