简体   繁体   中英

How to create a Union type in F# that is a value type?

Normal F# Discriminated Unions are reference types. How can I create a simple (non-recursive and with only value-type fields) union type in F# that is a value type?

Based on some internet searching my current (non-working) attempt looks as follows:

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

The following blog post appears to show what is possible via C#

I'm aware that the above is NOT idiomatic use of F# but I am trying to optimize the performance of a portion of my application and profiling has clearly shown that the cost of heap allocations (JIT_new) is what is causing my performance bottleneck... A simple union type is the perfect data structure for my needs, just not a heap allocated one.

First of all, I would probably not do this, unless I had very good reasons. In most cases, the difference between structs and reference types is not really that big - in my experience, it only matters when you have a very large array of them (then structs let you allocate one big memory chunk).

That said, it looks like F# does not like the constructor code in your example. I'm really not sure why (it seems to be doing some check that does not quite work for overlapping structs), but the following does the trick:

[<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)

If I actually wanted to use this, I would add another field Tag containing 1 or 0 depending on which case your struct represents. Then you could pattern match on it using an active pattern and get some of the safety of discriminated unions back:

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

Struct unions are now supported by F#, see F# RFC FS-1014 for details. In short:

 // Single case: [<Struct>] type UnionExample = U of int * int * bool // Multi-case: [<Struct>] type Shape = | Circle of radius: double | Square of side: int 

Key differences in struct records:

  • You cannot have cyclic references to the same type being defined. ex: type T = U of T
  • You also cannot call the default ctor, like you could with normal F# structs.
  • For multi-case struct unions, each case must have a unique name.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM