F# Training
F# Training
F# Training
  • Presentation
  • Fundamentals
    • Introduction
    • Syntax
      • Bases
      • Functions
      • Rules
      • Exceptions
    • First concepts
    • šŸ”Quiz
  • Functions
    • Signature
    • Concepts
    • Syntax
    • Standard functions
    • Operators
    • Addendum
    • šŸ”Quiz
    • šŸ“œSummary
  • Types
    • Overview
    • Tuples
    • Records
    • Unions
    • Enums
    • Anonymous records
    • Value types
    • šŸ“œRecap
    • Addendum
  • Monadic types
    • Intro
    • Option type
    • Result type
    • Smart constructor
    • šŸš€Computation expression (CE)
    • šŸš€CE theoretical basements
    • šŸ“œRecap
  • Pattern matching
    • Patterns
    • Match expression
    • Active patterns
    • šŸš€Fold function
    • šŸ“œRecap
    • šŸ•¹ļøExercises
  • Collections
    • Overview
    • Types
    • Common functions
    • Dedicated functions
    • šŸ”Quiz
    • šŸ“œRecap
  • Asynchronous programming
    • Asynchronous workflow
    • Interop with .NET TPL
    • šŸ“œRecap
  • Module and Namespace
    • Overview
    • Namespace
    • Module
    • šŸ”Quiz
    • šŸ“œRecap
  • Object-oriented
    • Introduction
    • Members
    • Type extensions
    • Class, Struct
    • Interface
    • Object expression
    • Recommendations
Powered by GitBook
On this page
  • Declaration
  • Underlying type
  • Char
  • Member naming
  • Usages
  • Conversion
  • Pattern matching
  • Flags
  • Values
  • Enum vs Union
  • Extras

Was this helpful?

Edit on GitHub
  1. Types

Enums

PreviousUnionsNextAnonymous records

Last updated 1 month ago

Was this helpful?

→ 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, 3 → int

    • 1uy, 2uy, 3uy → byte

    • Etc. - see

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

  • 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]

šŸ’” NuGet package → Includes an Enum module with these helpers:

Literals
FSharpx.Extras