Enums

→ Real .NET enum!

Declaration

Set of integer constants (byte, int...)

type ColorN =
    | Red   = 1
    | Green = 2
    | Blue  = 3

☝️ Note the syntax difference with a enum-like union:

type Color = Red | Green | Blue

Underlying type

  • Unlike C♯, there is no default underlying type in F♯.

  • The underlying type is defined by means of literals defining member values:

    • 1, 2, 3int

    • 1uy, 2uy, 3uybyte

    • Etc. - see Literals

Corollary: you must use the same type for all members, otherwise it won't compile!

type ColorN =
    | Red   = 1
    | Green = 2
    | Blue  = 3uy
// 💥         ~~~
// This expression was expected to have type 'int' but here has type 'byte'

Char

char type can be used as the underlying type, but not the string:

type AnswerChar = Yes='Y' | No='N'  ✅

type AnswerStringKo = Yes="Y" | No="N"  // 💥 Error FS0951
// Literal enumerations must have type
// int, uint, int16, uint16, int64, uint64, byte, sbyte or char

Member naming

Enum members can be in camelCase:

type File =
    | a = 'a'
    | b = 'b'
    | c = 'c'

Usages

⚠️ Unlike unions, the use of an enum literal is necessarily qualified

type AnswerChar = Yes='Y' | No='N'

let answerKo = Yes  // 💥 Error FS0039
//             ~~~     The value or constructor 'Yes' is not defined.

let answer = AnswerChar.Yes   // 👌 OK

💡 We can force the qualification for union types too:

[<RequireQualifiedAccess>] // 👈
type Color = Red | Green | Blue

Conversion

let redValue = int ColorN.Red         // enum -> int
let redAgain = enum<ColorN> redValue  // int -> enum
let red: ColorN = enum redValue       // int -> enum

// ⚠️ Use LanguagePrimitives for char enum
let n: AnswerChar = LanguagePrimitives.EnumOfValue 'N' // char -> enum
let y = LanguagePrimitives.EnumToValue AnswerChar.Yes  // enum -> char

Pattern matching

⚠️ Unlike unions, pattern matching on enums is not exhaustive → See Warning FS0104: Enums may take values outside known cases...

type ColorN =
    | Red   = 1
    | Green = 2
    | Blue  = 3

let toHex color =
    match color with
    | ColorN.Red   -> "#FF0000"
    | ColorN.Green -> "#00FF00"
    | ColorN.Blue  -> "#0000FF"
    | _ -> invalidArg (nameof color) $"Color {color} not supported" // 👈

Flags

Same principle as in C♯:

open System

[<Flags>]
type PermissionFlags =
    | Read    = 1
    | Write   = 2
    | Execute = 4

let permissions = PermissionFlags.Read ||| PermissionFlags.Write
// val permissions: PermissionFlags = Read, Write

let canRead    = permissions.HasFlag PermissionFlags.Read    // true
let canWrite   = permissions.HasFlag PermissionFlags.Write   // true
let canExecute = permissions.HasFlag PermissionFlags.Execute // false

💡 Note the ||| operator called "binary OR" (same as | in C♯)

💡 Hint: use binary notation for flag values:

[<Flags>]
type PermissionFlags =
    | Read    = 0b001
    | Write   = 0b010
    | Execute = 0b100

Values

System.Enum.GetValues() returns the list of members of an enum ⚠️ Weakly typed: Array (non-generic array) 💡 Use a helper like:

let enumValues<'a> () =
    Enum.GetValues(typeof<'a>)
    :?> ('a array)
    |> Array.toList

let allPermissions = enumValues<PermissionFlags>()
// val allPermissions: PermissionFlags list = [Read; Write; Execute]

Enum vs Union

Type
Data inside
Qualification
Exhaustivity

Enum

integers

Required

❌ No

Union

any

Optional

✅ Yes

Recommendation:

  • Prefer Union over Enum in most cases

  • Choose an Enum for:

    • .NET Interop

    • int data

    • Flags feature

Extras

💡 NuGet package FSharpx.Extras → Includes an Enum module with these helpers:

  • parse<'enum>: string -> 'enum

  • tryParse<'enum>: string -> 'enum option

  • getValues<'enum>: unit -> 'enum seq

#r "nuget: FSharpx.Extras"
open FSharpx

type ColorN =
    | Red   = 1
    | Green = 2
    | Blue  = 3

let red = "Red" |> Enum.tryParse<ColorN> // val red: ColorN option = Some Red
let none = "xx" |> Enum.tryParse<ColorN> // val none: ColorN option = None

let blue: ColorN = "Blue" |> Enum.parse // val blue: ColorN = Blue
let ko =
    try
        "Ko" |> Enum.parse<ColorN>
    with ex ->
        printfn "💥 %s %s" (ex.GetType().FullName) ex.Message
        // 💥 System.ArgumentException: Requested value 'Ko' was not found
        enum<ColorN> 0

let colors = Enum.getValues<ColorN> () |> Seq.toList
// val colors: ColorN list = [Red; Green; Blue]

Last updated

Was this helpful?