āļø 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āÆ)
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: