Как создать тип Union в F#, который является типом значения?

Обычные размеченные объединения 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) является причиной моего узкого места в производительности. , Простой тип объединения — идеальная структура данных для моих нужд, но не выделенная куча.


person Sam    schedule 14.07.2015    source источник
comment
Почему? Вы пытаетесь создать что-то вроде союза C - союзы F # очень разные. Хитрость С#, вероятно, лучше отображает структуру.   -  person John Palmer    schedule 14.07.2015
comment
@JohnPalmer подробно объяснил почему выше. Я мог бы использовать структуру F# с двумя полями, где я буду использовать только одно из полей в любой момент времени, но пытаюсь оценить альтернативное решение.   -  person Sam    schedule 14.07.2015
comment
Вы также можете определять структуры в F#.   -  person GeirGrusom    schedule 14.07.2015
comment
@GeirGrusom спасибо, я знаю. Приведенный выше тип объединения более компактен с точки зрения памяти И, возможно, избавит от большого количества кода, который включает установку значений, которые не являются семантически важными (в зависимости от сценария).   -  person Sam    schedule 14.07.2015
comment
Вы не можете (насколько мне известно) смешивать объединение C и размеченное объединение F#. Это не одно и то же. Однако вы можете превратить свой тип в структуру, которая превратит его в союз C. Это удаляет распределение GC, но я не думаю, что вы можете больше сопоставлять по типу, если вы не упакуете его, и в этот момент вы вернетесь к выделению GC.   -  person GeirGrusom    schedule 14.07.2015


Ответы (2)


Прежде всего, я бы, вероятно, не стал этого делать, если бы у меня не было очень веских причин. В большинстве случаев разница между структурами и ссылочными типами на самом деле не так уж велика — по моему опыту, это имеет значение только тогда, когда у вас очень большой их массив (тогда структуры позволяют вам выделить один большой кусок памяти).

Тем не менее, похоже, что 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, содержащее 1 или 0 в зависимости от того, какой случай представляет ваша структура. Затем вы можете сопоставить его с шаблоном, используя активный шаблон, и вернуть часть безопасности размеченных союзов:

let (|Float|Int|) (s:MyStruct) = 
  if s.Tag = 0 then Float(s.Val1) else Int(s.Int1)
person Tomas Petricek    schedule 14.07.2015
comment
Спасибо! Я не стремлюсь делать это (из-за того, что это не идиоматично), но я профилировал (много), и довольно определенно, что стоимость выделения кучи (отображаемая как вызов JIT_new) многих небольших объектов является узким местом производительности. а мне нужна производительность. Есть ли у них что-то еще, о чем я должен знать, когда это может не сработать (из-за того, что это своего рода взлом низкого уровня)? Другими словами, каковы существенные недостатки приведенной выше, в остальном довольно полезной небольшой структуры данных? - person Sam; 14.07.2015
comment
Думаю, что (кроме отсутствия безопасности) недостатков быть не должно. Это была бы неплохая оптимизация, которую компилятор F# мог бы сделать для DU, содержащих только типы значений... Если вы храните там int и float, это также не требует больших затрат на копирование (что может быть в случае с более крупными структурами). - person Tomas Petricek; 14.07.2015
comment
Я думаю, что активный шаблон может выделять один экземпляр (при возврате), поэтому его нельзя использовать в местах, где вы заботитесь о распределении. - person Tomas Petricek; 14.07.2015
comment
Но это экземпляр типа значения, поэтому он не должен сильно влиять на производительность (или это что-то active pattern конкретное)? В любом случае при попытке скомпилировать активный шаблон (используя немного другой код) возникает следующая ошибка: This expression was expected to have type Choice<'a,'b> but here has type MyStruct. Я сохранил тег в первом изменяемом поле. - person Sam; 14.07.2015
comment
Ой! Я понимаю. Активный шаблон НЕ создает еще один экземпляр MyStruct, он создает choice DU (отсюда выделение кучи)... хм... - person Sam; 14.07.2015

Структурные объединения теперь поддерживаются F#, см. F# RFC FS-1014 для получения подробной информации. Вкратце:

// Single case:

[<Struct>]
type UnionExample = U of int * int * bool

// Multi-case:

[<Struct>]
type Shape =
   | Circle of radius: double
   | Square of side: int

Ключевые отличия в записях структуры:

  • У вас не может быть циклических ссылок на один и тот же определяемый тип. пример: тип T = U из T
  • Вы также не можете вызвать ctor по умолчанию, как вы могли бы с обычными структурами F#.
  • Для объединений структур с несколькими регистрами каждый случай должен иметь уникальное имя.
person cmeeren    schedule 16.08.2018