如何在F#中创建一个值类型的联合类型?

3

普通的F#判别联合类型是引用类型。我该如何创建一个简单的(非递归且只有值类型字段)F#联合类型,使其成为值类型?

根据一些互联网搜索,我的当前(不起作用的)尝试如下:

[<StructLayout(LayoutKind.Explicit)>]
type Float =
    [<DefaultValue>] [<FieldOffset 0>] val mutable Val1 : float
    [<DefaultValue>] [<FieldOffset 0>] val mutable Int1 : int
    new (a:float) = {Val1 = a}    

以下博客文章似乎展示了C#的可能性

我知道上面不是F#的典型用法,但我正在尝试优化我的应用程序的某个部分的性能,并且分析清楚表明堆分配(JIT_new)的成本是导致性能瓶颈的原因...一个简单的联合类型是我需要的完美数据结构,只是它不是一个堆分配的。


1
为什么?你是想创建类似于 C union 的东西吗?F# unions 是非常不同的。C# 的技巧可能更适合映射到一个结构体。 - John Palmer
@JohnPalmer 上面详细阐述了为什么。我可以使用一个具有两个字段的 F# 结构体,其中我在任何时候只会使用一个字段,但我正在尝试评估另一种解决方案。 - Sam
你也可以在 F# 中定义结构体 - GeirGrusom
@GeirGrusom 谢谢,我知道。上面的联合类型从内存角度来看更紧凑,也许会避免很多涉及设置不重要语义值的代码(取决于情况)。 - Sam
据我所知,您不能混合使用C联合和F#判别联合。它们不是同一种东西。但是,您可以将类型转换为结构体,从而将其转换为C联合。这将消除GC分配,但我认为您不能再根据类型进行匹配,除非您将其装箱,此时您又回到了GC分配。 - GeirGrusom
2个回答

12

首先,除非我有非常充分的理由,否则我可能不会这样做。在大多数情况下,结构体和引用类型之间的差异并不是很大 - 在我的经验中,只有当您拥有非常大的数组时才会有影响(然后结构体让您可以分配一个大的内存块)。

话虽如此,看起来F#不喜欢你示例中的构造函数代码。我真的不确定为什么(它似乎正在执行一些检查,但对于重叠结构体不太起作用),但以下代码可解决问题:

[<Struct; StructLayout(LayoutKind.Explicit)>]
type MyStruct =
    [<DefaultValue; FieldOffset 0>] 
    val mutable Val1 : float
    [<DefaultValue; FieldOffset 0>] 
    val mutable Int1 : int
    static member Int(a:int) = MyStruct(Int1=a)
    static member Float(f:float) = MyStruct(Val1=f)

如果我真的想要使用这个,我会添加另一个字段 Tag,其中包含10,具体取决于你的结构体表示哪种情况。然后,您可以使用活动模式对其进行模式匹配,并获得某些有歧义联合的安全性:

let (|Float|Int|) (s:MyStruct) = 
  if s.Tag = 0 then Float(s.Val1) else Int(s.Int1)

谢谢!我不太想这样做(因为它不是惯用语),但我已经进行了大量的分析,很明显许多小对象的堆分配成本(显示为JIT_new调用)是性能瓶颈,而我需要性能。除此之外,还有什么其他需要注意的地方吗(由于这是一种低级别的黑客方式,可能会出现问题)?换句话说,上述非常有用的小型数据结构的真正缺点是什么? - Sam
1
我认为(除了缺乏安全性之外),不应该有太多的缺点。这将是一种很好的优化,F#编译器可以对仅包含值类型的DUs进行优化...如果您在那里存储intfloat,也没有太大的复制成本(这可能是较大结构体的情况)。 - Tomas Petricek
我认为活动模式可能会分配一个实例(在返回时),因此在关心分配的地方可能不太可用。 - Tomas Petricek
但它是值类型的实例,所以它不应该对性能产生太大影响(或者这是“active pattern”特定的问题吗?)。无论如何,使用略有不同的代码尝试编译活动模式时会出现以下错误:This expression was expected to have type Choice<'a,'b> but here has type MyStruct。我已将标签存储在第一个可变字段中。 - Sam
哦!我明白了。活动模式并不是创建MyStruct的另一个实例,而是创建了一个choice DU(因此需要堆分配)...嗯... - Sam

0

现在 F# 支持结构联合,请参见 F# RFC FS-1014 了解详情。简而言之:

// 单一情况:
[] type UnionExample = U of int * int * bool
// 多种情况:
[] type Shape = | Circle of radius: double | Square of side: int
结构记录的关键差异:
- 不能有对正在定义的相同类型的循环引用。例如:type T = U of T - 也不能像普通 F# 结构体那样调用默认构造函数。 - 对于多种情况的结构联合,每种情况必须具有唯一的名称。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接