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

Как я могу перечислить через возможные "значения" различаемого объединения в F#?

Я хочу знать, если есть что-то вроде Enum.GetValues(Type) Что касается дискриминационных союзов, то я не уверен, какие именно данные я бы перечислил. Я хотел бы создать список или массив различенного объединения с одним элементом для каждого параметра.

4 ответа

Решение

Да, F# имеет свой собственный слой отражения, построенный поверх отражения.NET, чтобы помочь вам разобраться в типах, специфичных для F#, таких как различающие объединения. Вот код, который позволит вам перечислить случаи объединения:

open Microsoft.FSharp.Reflection

type MyDU =
    | One
    | Two
    | Three

let cases = FSharpType.GetUnionCases typeof<MyDU>

for case in cases do printfn "%s" case.Name

Если ваш дискриминируемый союз состоит только из простых идентификаторов (ни в одном случае не хранятся какие-либо данные, это может быть то, что вам нужно: gist

open Microsoft.FSharp.Reflection

module SimpleUnionCaseInfoReflection =

  // will crash if 'T contains members which aren't only tags
  let Construct<'T> (caseInfo: UnionCaseInfo)                   = FSharpValue.MakeUnion(caseInfo, [||]) :?> 'T

  let GetUnionCaseInfoAndInstance<'T> (caseInfo: UnionCaseInfo) = (caseInfo, Construct<'T> caseInfo)

  let AllCases<'T> = 
    FSharpType.GetUnionCases(typeof<'T>)
    |> Seq.map GetUnionCaseInfoAndInstance<'T>
#load "SimpleUnionCaseInfoReflection.fs"

type Foos = Foo | Bar | Baz
SimpleUnionCaseInfoReflection.AllCases<Foos> |> Seq.iter (fun (caseInfo, instance) ->printfn "name: %s instance: %O is Bar? : %b" caseInfo.Name instance (instance.Equals(Foos.Bar)))

(*
> name: Foo instance: FSI_0055+Foos is Bar? : false
> name: Bar instance: FSI_0055+Foos is Bar? : true
> name: Baz instance: FSI_0055+Foos is Bar? : false
*)

Чтобы немного расширить пример Роберта - даже если у вас нет экземпляра дискриминируемого объединения, вы можете использовать отражение F# для получения информации о типе (например, типы аргументов отдельных случаев). Следующее расширяет образец Роберта и печатает типы аргументов:

open Microsoft.FSharp.Reflection

let ty = typeof<option<int>>
let cases = FSharpType.GetUnionCases ty

printfn "type %s =" ty.FullName
for case in cases do 
  printf "| %s" case.Name 
  let fields = case.GetFields()
  if fields.Length > 0 then
    printf " of"
  for fld in fields do
    printf " %s " fld.PropertyType.FullName
  printfn ""

Например, для option<int> типа, вы получите (я немного упростил вывод):

type Microsoft.FSharp.Core.FSharpOption`1[System.Int32] =
  | None
  | Some of System.Int32

Существует много интересных применений этой информации - например, вы можете сгенерировать схему БД из объединений F# или создать функции, которые будут анализировать XML в различимое объединение (которое описывает структуру). Я говорил об образце обработки XML на конференции GOTO в начале этого года.

Трудно понять, как это могло бы работать без наличия экземпляра, поскольку профсоюзы дискриминации могут нести ценности.

Если у вас был такой тип, например:

type Status = Success of string | Error of System.Exception | Timeout

Что бы вы, кроме вашего массива, содержали для успеха или ошибки в этом случае?

Другие вопросы по тегам