如何创建具有函数成员和默认 public 构造函数的 F# 结构?

How to create an F# struct with a function member and a default public constructor?

我需要创建一个 struct 类型来传递给不受我控制的外部 class。 class 要求我的 struct 类型有一个 public 默认构造函数。我需要它有一个函数类型作为成员。

以下生成 error FS0001: A generic construct requires that the type 'MyStruct' have a public default constructor:

[<Struct>]
type MyStruct =
    val callBack: unit -> unit
    new(cb: unit -> unit) = { callBack = cb }

type ExternalClass<'T when 'T : (new : unit -> 'T) and 'T : struct> () =
    member val something = new 'T()

let c = new ExternalClass<MyStruct>()

如果成员的类型从 unit -> unit 更改为 int,这将正常工作。

我试过使用 DefaultValue 属性,根据 docs 应该可以在函数成员上正常工作,但这会产生两个错误:error FS0765: Extraneous fields have been given valueserror FS0696: This is not a valid object construction expression. Explicit object constructors must either call an alternate constructor or initialize all fields of the object and specify a call to a super class constructor.

如何创建合适的类型来满足外部 class 的约束?

这里的问题是类型 unit -> unit 没有默认值。这是一个较短的重现:

[<Struct>]
type MyStruct =
    val callBack: unit -> unit

let s = MyStruct()

最后一行出现错误:The default, zero-initializing constructor of a struct type may only be used if all the fields of the struct type admit default initialization

类型unit -> unit不允许默认初始化。它必须有一个函数类型的值,并且没有“默认”函数值这样的东西。

.NET 中的结构是这样工作的:每个结构总是有一个默认的构造函数,程序员无法实现它。相反,运行时使用默认值初始化所有字段。这背后的想法是使分配数组或结构非常便宜:您只需将内存块清零,瞧!

所以按照这个逻辑,你的 callBack 字段必须是零可输出的,但它不能,因为在 F# 中函数类型的变量不能是 null.

它与 int 一起工作正常,因为 int 确实有一个默认值零。


现在,一个“好的”解决方案将取决于您实际尝试做什么。但在没有这些信息的情况下,我可以建议一些本地解决方法。

第一个选项 - 使字段具有类型 obj(因此其默认值为 null)并提供一个安全的访问器return 一个 option:

[<Struct>]
type MyStruct =
    val private callBack: obj
    member this.Callback with get() = this.callBack |> Option.ofObj |> Option.map (fun o -> o :?> (unit -> unit))
    new(cb: unit -> unit) = { callBack = cb }

Callback 访问器 属性 将 return 一个 (unit -> unit) option,如果函数已初始化,则将是 SomeNone如果不是:

> MyStruct().Callback;;
val it : (unit -> unit) option = None

> MyStruct(fun() -> ()).Callback;;
val it : (unit -> unit) option = Some <fun:it@10>

第二个选项 - 将回调包装在可为 null 的类型中:

[<AllowNullLiteral>] 
type ACallback(cb : unit -> unit) = 
    member val Callback = cb with get

[<Struct>]
type MyStruct =
    val callBack: ACallback
    new(cb: unit -> unit) = { callBack = ACallback cb }

然后:

> MyStruct().callBack;;
val it : ACallback = <null>

> MyStruct(fun() -> ()).callBack;;
val it : ACallback = FSI_0006+ACallback {Callback = <fun:it@30-1>;}

这(可以说)以额外分配为代价提供了更多的类型安全性。

此外,有可能获得 null,但如果这是一个问题,您也可以将其包装在 option 类型的访问器中:

[<Struct>]
type MyStruct =
    val private callBack: ACallback
    member this.Callback with get() = this.callBack |> Option.ofObj |> Option.map (fun c -> c.Callback)
    new(cb: unit -> unit) = { callBack = ACallback cb }

> MyStruct().Callback;;
val it : (unit -> unit) option = None

> MyStruct(fun() -> ()).Callback;;
val it : (unit -> unit) option = Some <fun:it@38-2>