Как написать перечисление на F# без явного назначения числовых литералов?

У меня есть перечисление в F#, например:

type Creature =
   | SmallCreature = 0
   | MediumCreature = 1
   | GiantCreature = 2
   | HumongousCreature = 3
   | CreatureOfNondescriptSize = 4

Мне не нравится вводить числа вручную, и я хочу легко вставлять дополнительные элементы в перечисление позже, не меняя числа.

я пробовал это

type Creature =
   | SmallCreature
   | MediumCreature
   | GiantCreature
   | HumongousCreature
   | CreatureOfNondescriptSize

но это вызвало ошибку The type 'Creature' is not a CLI enum type позже в программе

let input = Int32.Parse(Console.ReadLine())
let output = match EnumOfValue<int, Creature>(input) with // <---Error occurs here
    | Creature.SmallCreature -> "Rat"
    | Creature.MediumCreature -> "Dog"
    | Creature.GiantCreature -> "Elephant"
    | Creature.HumongousCreature -> "Whale"
    | Creature.CreatureOfNondescriptSize -> "Jon Skeet"
    | _ -> "Unacceptably Hideous Monstrosity"

Console.WriteLine(output)
Console.WriteLine()
Console.WriteLine("Press any key to exit...")
Console.Read() |> ignore

Как я могу определить перечисление без ручного присвоения числовых значений каждому элементу?


person Peter Olson    schedule 16.08.2011    source источник
comment
Хотя я не считаю DU заменой перечислений, если вы пойдете по этому пути, вы можете пометить свой тип с помощью [<RequireQualifiedAccess>], поскольку случаи DU являются членами верхнего уровня объемлющего пространства имен.   -  person Daniel    schedule 17.08.2011


Ответы (2)


К сожалению, вы не можете. Важны ли числовые значения? Если это так, это несколько избегает предполагаемого использования перечислений (флаги в сторону). В этом случае вы можете рассмотреть класс или дискриминированный союз.

Ваш второй пример, по сути, является дискриминационным союзом. Но ваше более позднее использование EnumOfValue, которое ожидает перечисление, вызывает ошибку.

Другой вариант — сохранить сопоставление перечисления с числом в словаре и заменить сопоставление с образцом поиском в словаре. Тогда числовое значение перечисления не имеет значения.

Я согласен с тем, что вручную управлять значениями перечисления обременительно. Я надеюсь, что это будет рассмотрено в будущей версии.

person Daniel    schedule 16.08.2011

Как говорит Даниэль, вы не можете определить перечисление, не указав числовые эквиваленты. Однако вы можете определить функцию, которая преобразует число в соответствующий случай размеченного объединения:

open Microsoft.FSharp.Reflection

let intToDU<'t> n =
    if not (FSharpType.IsUnion typeof<'t>) then
        failwithf "%s is not a discriminated union" typeof<'t>.Name
    let cases = FSharpType.GetUnionCases(typeof<'t>)
    if n >= cases.Length || n < 0 then
        failwithf "%i is out of the range of %s's cases (0 - %i)" n typeof<'t>.Name (cases.Length - 1)
    let uc = cases.[n]
    if uc.GetFields().Length > 0 then 
        failwithf "%s.%s requires constructor arguments" typeof<'t>.Name uc.Name
    FSharpValue.MakeUnion(uc, [||]) :?> 't

Затем вы можете использовать эту общую функцию следующим образом:

type Creature =   
| SmallCreature   
| MediumCreature   
| GiantCreature   
| HumongousCreature   
| CreatureOfNondescriptSize

let input = int (System.Console.ReadLine())

let output = 
    match intToDU input with 
    | SmallCreature -> "Rat"    
    | Creature.MediumCreature -> "Dog"    
    | Creature.GiantCreature -> "Elephant"    
    | Creature.HumongousCreature -> "Whale"    
    | Creature.CreatureOfNondescriptSize -> "Jon Skeet"    

Это имеет дополнительное преимущество, заключающееся в том, что совпадение с образцом теперь является полным.

person kvb    schedule 16.08.2011
comment
Есть ли способ получить внутренний класс Tags (или свойство Tag в случае), который виден в C#? Это сделало бы это возможным без использования отражения. - person Daniel; 16.08.2011
comment
@ Даниэль - насколько мне известно, нет. Однако я не понимаю, как это поможет: вы можете использовать значение Tag для преобразования из DU в int, но не наоборот (если я что-то не упустил). - person kvb; 16.08.2011
comment
Для перехода от int к DU по-прежнему потребуется функция, но это можно сделать без отражения или зависимости от порядка регистра. Это не было бы огромным улучшением. Мне в основном просто интересно. - person Daniel; 16.08.2011
comment
Вы не знаете, есть ли планы по решению этой проблемы? Может быть, атрибут [<AutoAssignValues>]? :-) Я полагаю, что синтаксическая двусмысленность с DU все еще остается. Возможно, атрибут [<Enum>] решит эту проблему так же, как [<AbstractClass>] разрешает двусмысленность с интерфейсами. - person Daniel; 16.08.2011
comment
@Daniel - я не знаю, есть ли планы по решению этой проблемы, но я никогда не находил это особенно обременительным - обычно я все равно предпочитаю DU, поскольку они дают вам полное совпадение. - person kvb; 17.08.2011