F# for Fun and Profit
  • Introduction
  • Getting started
    • Contents of the book
    • "Why use F#?" in one page
    • Installing and using F#
    • F# syntax in 60 seconds
    • Learning F#
    • Troubleshooting F#
    • Low-risk ways to use F# at work
      • Twenty six low-risk ways to use F# at work
      • Using F# for development and devops scripts
      • Using F# for testing
      • Using F# for database related tasks
      • Other interesting ways of using F# at work
  • Why use F#?
    • The "Why use F#?" Series
      • Introduction to the 'Why use F#' series
      • F# syntax in 60 seconds
      • Comparing F# with C#: A simple sum
      • Comparing F# with C#: Sorting
      • Comparing F# with C#: Downloading a web page
      • Four Key Concepts
      • Conciseness
      • Type inference
      • Low overhead type definitions
      • Using functions to extract boilerplate code
      • Using functions as building blocks
      • Pattern matching for conciseness
      • Convenience
      • Out-of-the-box behavior for types
      • Functions as interfaces
      • Partial Application
      • Active patterns
      • Correctness
      • Immutability
      • Exhaustive pattern matching
      • Using the type system to ensure correct code
      • Worked example: Designing for correctness
      • Concurrency
      • Asynchronous programming
      • Messages and Agents
      • Functional Reactive Programming
      • Completeness
      • Seamless interoperation with .NET libraries
      • Anything C# can do...
      • Why use F#: Conclusion
  • Thinking Functionally
    • The "Thinking Functionally" Series
      • Thinking Functionally: Introduction
      • Mathematical functions
      • Function Values and Simple Values
      • How types work with functions
      • Currying
      • Partial application
      • Function associativity and composition
      • Defining functions
      • Function signatures
      • Organizing functions
      • Attaching functions to types
      • Worked example: A stack based calculator
  • Understanding F# ###
    • The "Expressions and syntax" Series
      • Expressions and syntax: Introduction
      • Expressions vs. statements
      • Overview of F# expressions
      • Binding with let, use, and do
      • F# syntax: indentation and verbosity
      • Parameter and value naming conventions
      • Control flow expressions
      • Exceptions
      • Match expressions
      • Formatted text using printf
      • Worked example: Parsing command line arguments
      • Worked example: Roman numerals
    • The "Understanding F# types" Series
      • Understanding F# types: Introduction
      • Overview of types in F#
      • Type abbreviations
      • Tuples
      • Records
      • Discriminated Unions
      • The Option type
      • Enum types
      • Built-in .NET types
      • Units of measure
      • Understanding type inference
    • Choosing between collection functions
    • The "Object-oriented programming in F#" Series
      • Object-oriented programming in F#: Introduction
      • Classes
      • Inheritance and abstract classes
      • Interfaces
      • Object expressions
    • The "Computation Expressions" Series
      • Computation expressions: Introduction
      • Understanding continuations
      • Introducing 'bind'
      • Computation expressions and wrapper types
      • More on wrapper types
      • Implementing a builder: Zero and Yield
      • Implementing a builder: Combine
      • Implementing a builder: Delay and Run
      • Implementing a builder: Overloading
      • Implementing a builder: Adding laziness
      • Implementing a builder: The rest of the standard methods
    • Organizing modules in a project
    • The "Dependency cycles" Series
      • Cyclic dependencies are evil
      • Refactoring to remove cyclic dependencies
      • Cycles and modularity in the wild
    • The "Porting from C#" Series
      • Porting from C# to F#: Introduction
      • Getting started with direct porting
  • Functional Design ###
    • The "Designing with types" Series
      • Designing with types: Introduction
      • Single case union types
      • Making illegal states unrepresentable
      • Discovering new concepts
      • Making state explicit
      • Constrained strings
      • Non-string types
      • Designing with types: Conclusion
    • Algebraic type sizes and domain modelling
    • Thirteen ways of looking at a turtle
      • Thirteen ways of looking at a turtle (part 2)
      • Thirteen ways of looking at a turtle - addendum
  • Functional Patterns ###
    • How to design and code a complete program
    • A functional approach to error handling (Railway oriented programming)
      • Railway oriented programming: Carbonated edition
    • The "Understanding monoids" Series
      • Monoids without tears
      • Monoids in practice
      • Working with non-monoids
    • The "Understanding Parser Combinators" Series
      • Understanding Parser Combinators
      • Building a useful set of parser combinators
      • Improving the parser library
      • Writing a JSON parser from scratch
    • The "Handling State" Series
      • Dr Frankenfunctor and the Monadster
      • Completing the body of the Monadster
      • Refactoring the Monadster
    • The "Map and Bind and Apply, Oh my!" Series
      • Understanding map and apply
      • Understanding bind
      • Using the core functions in practice
      • Understanding traverse and sequence
      • Using map, apply, bind and sequence in practice
      • Reinventing the Reader monad
      • Map and Bind and Apply, a summary
    • The "Recursive types and folds" Series
      • Introduction to recursive types
      • Catamorphism examples
      • Introducing Folds
      • Understanding Folds
      • Generic recursive types
      • Trees in the real world
    • The "A functional approach to authorization" Series
      • A functional approach to authorization
      • Constraining capabilities based on identity and role
      • Using types as access tokens
  • Testing
    • An introduction to property-based testing
    • Choosing properties for property-based testing
  • Examples and Walkthroughs
    • Worked example: Designing for correctness
    • Worked example: A stack based calculator
    • Worked example: Parsing command line arguments
    • Worked example: Roman numerals
    • Commentary on 'Roman Numerals Kata with Commentary'
    • Calculator Walkthrough: Part 1
      • Calculator Walkthrough: Part 2
      • Calculator Walkthrough: Part 3
      • Calculator Walkthrough: Part 4
    • Enterprise Tic-Tac-Toe
      • Enterprise Tic-Tac-Toe, part 2
    • Writing a JSON parser from scratch
  • Other
    • Ten reasons not to use a statically typed functional programming language
    • Why I won't be writing a monad tutorial
    • Is your programming language unreasonable?
    • We don't need no stinking UML diagrams
    • Introvert and extrovert programming languages
    • Swapping type-safety for high performance using compiler directives
Powered by GitBook
On this page
  • Defining enums
  • Constructing enums
  • Matching enums
  • Summary

Was this helpful?

  1. Understanding F# ###
  2. The "Understanding F# types" Series

Enum types

Not the same as a union type

The enum type in F# is the same as the enum type in C#. Its definition is superficially just like that of a union type, but there are many non-obvious differences to be aware of.

Defining enums

To define an enum you use exactly the same syntax as a union type with empty cases, except that you must specify a constant value for each case, and the constants must all be of the same type.

type SizeUnion = Small | Medium | Large         // union
type ColorEnum = Red=0 | Yellow=1 | Blue=2      // enum

Strings are not allowed, only ints or compatible types such bytes and chars:

type MyEnum = Yes = "Y" | No ="N"  // Error. Strings not allowed.
type MyEnum = Yes = 'Y' | No ='N'  // Ok because char was used.

Union types require that their cases start with an uppercase letter. This is not required for enums.

type SizeUnion = Small | Medium | large      // Error - "large" is invalid.
type ColorEnum = Red=0 | Yellow=1 | blue=2      // Ok

Just as with C#, you can use the FlagsAttribute for bit flags:

[<System.FlagsAttribute>]
type PermissionFlags = Read = 1 | Write = 2 | Execute = 4 
let permission = PermissionFlags.Read ||| PermissionFlags.Write

Constructing enums

Unlike union types, to construct an enum you must always use a qualified name:

let red = Red            // Error. Enums must be qualified
let red = ColorEnum.Red  // Ok 
let small = Small        // Ok.  Unions do not need to be qualified

You can also cast to and from the underlying int type:

let redInt = int ColorEnum.Red  
let redAgain:ColorEnum = enum redInt // cast to a specified enum type 
let yellowAgain = enum<ColorEnum>(1) // or create directly

You can even create values that are not on the enumerated list at all.

let unknownColor = enum<ColorEnum>(99)   // valid

And, unlike unions, you can use the BCL Enum functions to enumerate and parse values, just as with C#. For example:

let values = System.Enum.GetValues(typeof<ColorEnum>)
let redFromString =  
    System.Enum.Parse(typeof<ColorEnum>,"Red") 
    :?> ColorEnum  // downcast needed

Matching enums

To match an enum you must again always use a qualified name:

let unqualifiedMatch x = 
    match x with
    | Red -> printfn "red"             // warning FS0049
    | _ -> printfn "something else" 

let qualifiedMatch x = 
    match x with
    | ColorEnum.Red -> printfn "red"   //OK. qualified name used.
    | _ -> printfn "something else"

Both unions and enums will warn if you have not covered all known cases when pattern matching:

let matchUnionIncomplete x = 
    match x with
    | Small -> printfn "small"   
    | Medium -> printfn "medium"   
    // Warning: Incomplete pattern matches

let matchEnumIncomplete x = 
    match x with
    | ColorEnum.Red -> printfn "red"   
    | ColorEnum.Yellow -> printfn "yellow"   
    // Warning: Incomplete pattern matches

One important difference between unions and enums is that can you make the compiler happy about exhaustive pattern matching by listing all the union types.

Not so for enums. It is possible to create an enum not on the predeclared list, and try to match with it, and get a runtime exception, so the compiler will warn you even if you have explicitly listed all the known enums:

// the compiler is still not happy
let matchEnumIncomplete2 x = 
    match x with
    | ColorEnum.Red -> printfn "red"   
    | ColorEnum.Yellow -> printfn "yellow"   
    | ColorEnum.Blue -> printfn "blue"   
    // the value '3' may indicate a case not covered by the pattern(s).

The only way to fix this is to add a wildcard to the bottom of the cases, to handle enums outside the predeclared range.

// the compiler is finally happy
let matchEnumComplete x = 
    match x with
    | ColorEnum.Red -> printfn "red"   
    | ColorEnum.Yellow -> printfn "yellow"   
    | ColorEnum.Blue -> printfn "blue"   
    | _ -> printfn "something else"   

// test with unknown case    
let unknownColor = enum<ColorEnum>(99)   // valid
matchEnumComplete unknownColor

Summary

In general, you should prefer discriminated union types over enums, unless you really need to have an int value associated with them, or you are writing types that need to be exposed to other .NET languages.

PreviousThe Option typeNextBuilt-in .NET types

Last updated 5 years ago

Was this helpful?