Advanced

This advanced tutorial covers F#’s expert-level patterns and functional architecture through 25 heavily annotated examples. Topics include mailbox processors for concurrency, statically resolved type parameters (SRTP), quotations for metaprogramming, advanced computation expressions, and domain-driven design patterns that make illegal states unrepresentable.

Example 61: MailboxProcessor - Agent-Based Concurrency

MailboxProcessor implements the actor model for concurrency, where agents process messages sequentially from a mailbox, eliminating race conditions without locks.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
graph TD
    A[Client Posts<br/>Messages]:::blue --> B[Mailbox Queue]:::orange
    B --> C[Agent Processes<br/>Sequentially]:::teal
    C --> D[State Updated]:::purple
    D -->|loop| C

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#CC78BC,stroke:#000,color:#fff

Code:

// Example 61: MailboxProcessor - Agent-Based Concurrency
type Message =
    | Increment
    | Decrement
    | GetCount of AsyncReplyChannel<int>
                         // => Message type with async reply channel
                         // => GetCount carries channel for returning result

let counter = MailboxProcessor.Start(fun inbox ->
                         // => MailboxProcessor.Start creates agent
                         // => inbox is the mailbox receiving messages
    let rec loop count =
                         // => Recursive loop maintaining state
                         // => count is current state (no mutable variable!)
        async {
            let! msg = inbox.Receive()
                         // => let! awaits next message
                         // => Blocks until message arrives
            match msg with
            | Increment ->
                         // => Increment case
                return! loop (count + 1)
                         // => Recurse with incremented count
                         // => return! is tail recursion (no stack growth)
            | Decrement ->
                         // => Decrement case
                return! loop (count - 1)
                         // => Recurse with decremented count
            | GetCount replyChannel ->
                         // => GetCount case with reply channel
                replyChannel.Reply(count)
                         // => Send current count back via channel
                return! loop count
                         // => Continue with same count
        }
    loop 0               // => Start loop with initial state 0
)

// Post messages (fire-and-forget)
counter.Post(Increment)  // => Posts Increment message
counter.Post(Increment)  // => Posts another Increment
counter.Post(Decrement)  // => Posts Decrement

// Query current state
let currentCount = counter.PostAndReply(fun replyChannel -> GetCount replyChannel)
                         // => PostAndReply sends message and waits for reply
                         // => Creates reply channel automatically
                         // => currentCount is 1 (2 increments - 1 decrement)

printfn "Count: %d" currentCount
                         // => Outputs: Count: 1

Key Takeaway: MailboxProcessor provides actor-model concurrency with sequential message processing, eliminating race conditions through isolated state and message-passing instead of shared memory.

Why It Matters: Actor-based concurrency prevents data races without locks, deadlocks, or complex synchronization primitives. MailboxProcessor agents can process millions of concurrent updates with zero race conditions, achieving throughput improvements over lock-based implementations while simplifying debugging through message tracing.

Example 62: Agent-Based State Management

Agents encapsulate mutable state safely, enabling concurrent updates without explicit locking.

// Example 62: Agent-Based State Management
type BankMessage =
    | Deposit of amount: float
    | Withdraw of amount: float * AsyncReplyChannel<Result<float, string>>
    | GetBalance of AsyncReplyChannel<float>

let createBankAccount initialBalance =
                         // => Factory function creating bank account agent
    MailboxProcessor.Start(fun inbox ->
        let rec loop balance =
                         // => Loop maintains current balance
            async {
                let! msg = inbox.Receive()
                match msg with
                | Deposit amount ->
                    return! loop (balance + amount)
                         // => Add deposit to balance
                | Withdraw (amount, reply) ->
                         // => Withdraw with reply channel
                    if balance >= amount then
                        reply.Reply(Ok (balance - amount))
                         // => Success: reply with new balance
                        return! loop (balance - amount)
                         // => Continue with updated balance
                    else
                        reply.Reply(Error "Insufficient funds")
                         // => Error: insufficient funds
                        return! loop balance
                         // => Balance unchanged
                | GetBalance reply ->
                    reply.Reply(balance)
                         // => Return current balance
                    return! loop balance
            }
        loop initialBalance
    )

let account = createBankAccount 1000.0
                         // => Create account with $1000 initial balance

account.Post(Deposit 500.0)
                         // => Deposit $500 (balance: $1500)

let withdrawResult = account.PostAndReply(fun reply -> Withdraw (300.0, reply))
                         // => Attempt to withdraw $300
match withdrawResult with
| Ok newBalance ->
    printfn "Withdrawal successful, new balance: %.2f" newBalance
                         // => Outputs: Withdrawal successful, new balance: 1200.00
| Error msg ->
    printfn "Withdrawal failed: %s" msg

let balance = account.PostAndReply(GetBalance)
                         // => Query current balance
printfn "Current balance: %.2f" balance
                         // => Outputs: Current balance: 1200.00

Key Takeaway: Agents encapsulate state and enforce sequential access through message passing, preventing concurrent modification bugs without explicit locks.

Why It Matters: Agent-based state management eliminates many concurrency bugs, as state mutations are serialized automatically. Financial systems use agents to manage account balances with zero race conditions, processing thousands of concurrent transactions per second with correctness guarantees impossible in lock-based systems.

Example 63: Async Parallelism with Async.Parallel

Async.Parallel executes multiple async computations concurrently and collects results, enabling parallel I/O without blocking threads.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
graph TD
    A[Async.Parallel]:::blue --> B[Task 1<br/>runs]:::orange
    A --> C[Task 2<br/>runs]:::orange
    A --> D[Task 3<br/>runs]:::orange
    B --> E[Collect Results]:::teal
    C --> E
    D --> E

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#DE8F05,stroke:#000,color:#fff
    style D fill:#DE8F05,stroke:#000,color:#fff
    style E fill:#029E73,stroke:#000,color:#fff

Code:

// Example 63: Async Parallelism with Async.Parallel
let fetchData url = async {
                         // => Simulates HTTP fetch
    do! Async.Sleep 1000 // => Simulate network delay
    return sprintf "Data from %s" url
                         // => Return fetched data
}

let urls = [
    "https://api1.example.com"
    "https://api2.example.com"
    "https://api3.example.com"
]

let fetchAll = async {
    let! results = urls
                   |> List.map fetchData
                         // => Map each URL to async operation
                         // => Creates list of Async<string>
                   |> Async.Parallel
                         // => Execute all async ops in parallel
                         // => Returns Async<string array>
    return results       // => results is string array
}

let allData = fetchAll |> Async.RunSynchronously
                         // => Run async and wait for completion
                         // => allData is string array

for data in allData do
    printfn "%s" data
                         // => Outputs:
                         // => Data from https://api1.example.com
                         // => Data from https://api2.example.com
                         // => Data from https://api3.example.com
                         // => (executes in ~1 second, not 3)

Key Takeaway: Async.Parallel executes async operations concurrently and collects results into an array, enabling efficient parallel I/O without thread blocking.

Why It Matters: Parallel async operations reduce latency by factor of N for independent I/O operations. Microservices aggregate data from multiple backend APIs using Async.Parallel, achieving faster response times, improving user experience while maintaining thread pool efficiency.

Example 64: Custom Computation Expression Builders

Computation expression builders define custom control flow syntax, enabling domain-specific languages (DSLs) embedded in F#.

// Example 64: Custom Computation Expression Builders
type LoggingBuilder() =
                         // => Custom builder for logging workflow
    member _.Bind(x, f) =
                         // => Bind enables let! syntax
                         // => x is value, f is continuation function
        printfn "Binding value: %A" x
                         // => Log the binding
        f x              // => Apply continuation

    member _.Return(x) =
                         // => Return enables return syntax
        printfn "Returning value: %A" x
                         // => Log the return
        x                // => Return value

    member _.ReturnFrom(x) =
                         // => ReturnFrom enables return! syntax
        printfn "Returning from: %A" x
        x

    member _.Zero() =
                         // => Zero handles empty branches
        printfn "Zero"
        ()

let logging = LoggingBuilder()
                         // => Create builder instance
                         // => Enables logging { ... } syntax

let result = logging {
                         // => Computation expression using custom builder
    let! x = 10          // => Triggers Bind(10, ...)
                         // => Outputs: Binding value: 10
    let! y = 20          // => Triggers Bind(20, ...)
                         // => Outputs: Binding value: 20
    let sum = x + y      // => Regular let (no logging)
    return sum           // => Triggers Return(30)
                         // => Outputs: Returning value: 30
}                        // => result is 30

printfn "Final result: %d" result
                         // => Outputs: Final result: 30

Key Takeaway: Computation expression builders define custom control flow by implementing Bind, Return, and other members, creating domain-specific syntax embedded in F#.

Why It Matters: Custom builders enable domain-specific languages without leaving F#. Financial modeling libraries use builders to create pricing workflows with automatic risk calculations, validation, and logging, making complex financial logic read like business rules while maintaining type safety and composability.

Example 65: Query Expressions (LINQ Integration)

Query expressions integrate LINQ into F#, providing SQL-like syntax for data querying with compile-time type checking.

// Example 65: Query Expressions (LINQ Integration)
type Person = { Name: string; Age: int; City: string }

let people = [
    { Name = "Alice"; Age = 30; City = "New York" }
    { Name = "Bob"; Age = 25; City = "London" }
    { Name = "Charlie"; Age = 35; City = "New York" }
    { Name = "Diana"; Age = 28; City = "Paris" }
]

let newYorkers = query {
                         // => query { } is built-in computation expression
    for person in people do
                         // => Iterate over people
    where (person.City = "New York")
                         // => Filter by city
    sortBy person.Age    // => Sort by age
    select person        // => Select full person record
}                        // => Returns IQueryable<Person>

printfn "New Yorkers:"
for person in newYorkers do
    printfn "%s, age %d" person.Name person.Age
                         // => Outputs:
                         // => Alice, age 30
                         // => Charlie, age 35

// Grouping query
let byCity = query {
    for person in people do
    groupBy person.City into cityGroup
                         // => Group by city
    select (cityGroup.Key, cityGroup.Count())
                         // => Select city and count
}

printfn "\nPeople by city:"
for (city, count) in byCity do
    printfn "%s: %d" city count
                         // => Outputs:
                         // => New York: 2
                         // => London: 1
                         // => Paris: 1

Key Takeaway: Query expressions provide SQL-like syntax for querying collections with compile-time type checking, integrating F# with LINQ for database and collection operations.

Why It Matters: Query expressions enable type-safe database queries that compile to SQL, preventing runtime errors from typos or schema changes. Enterprise applications use query expressions to access SQL databases with 100% type safety, catching bugs at compile time that would manifest as runtime exceptions in string-based SQL.

Example 66: Quotations - Code as Data

Quotations represent F# code as data structures, enabling metaprogramming, code analysis, and runtime code generation.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
graph TD
    A[Source Code]:::blue --> B[Quotation<br/>AST]:::orange
    B --> C[Analyze]:::teal
    B --> D[Transform]:::teal
    B --> E[Execute]:::teal

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#029E73,stroke:#000,color:#fff
    style E fill:#029E73,stroke:#000,color:#fff

Code:

// Example 66: Quotations - Code as Data
open FSharp.Quotations
open FSharp.Quotations.Patterns

let simpleExpr = <@ 1 + 2 @>
                         // => <@ ... @> creates quotation
                         // => Captures expression as AST
                         // => Type: Expr<int>

let inspectQuotation (expr: Expr) =
                         // => Function analyzing quotation
    match expr with
    | Call (None, methodInfo, args) ->
                         // => Pattern match on method call
        printfn "Method call: %s with %d arguments" methodInfo.Name args.Length
    | Value (value, typ) ->
                         // => Pattern match on value
        printfn "Value: %A of type %s" value typ.Name
    | _ ->
        printfn "Other expression: %A" expr

inspectQuotation simpleExpr
                         // => Analyzes 1 + 2 quotation

// More complex quotation
let complexExpr = <@ fun x -> x * 2 + 1 @>
                         // => Lambda quotation

let rec printExpr (expr: Expr) =
                         // => Recursive quotation printer
    match expr with
    | Lambda (var, body) ->
        printfn "Lambda: %s => ..." var.Name
        printExpr body   // => Recurse into body
    | Call (target, methodInfo, args) ->
        printfn "Call: %s" methodInfo.Name
        args |> List.iter printExpr
    | Value (value, _) ->
        printfn "Value: %A" value
    | Var var ->
        printfn "Variable: %s" var.Name
    | _ ->
        printfn "Expression: %A" expr

printExpr complexExpr
                         // => Outputs:
                         // => Lambda: x => ...
                         // => Call: op_Addition
                         // => Call: op_Multiply
                         // => Variable: x
                         // => Value: 2
                         // => Value: 1

Key Takeaway: Quotations capture F# code as abstract syntax trees (AST), enabling code analysis, transformation, and runtime generation through pattern matching on expression structure.

Why It Matters: Quotations enable compiler-verified code generation and domain-specific optimizations. Type providers use quotations to generate optimized database queries from F# expressions, translating <@ person.Age > 25 @> into SQL WHERE Age > 25 with compile-time verification, achieving native database performance with type safety.

Example 67: Reflection and Type Information

F# reflection provides runtime type inspection and metadata access, enabling generic algorithms and dynamic behavior.

// Example 67: Reflection and Type Information
open System.Reflection

type Product = { Id: int; Name: string; Price: float }

let productType = typeof<Product>
                         // => Gets Type object for Product
                         // => Type: System.Type

printfn "Type name: %s" productType.Name
                         // => Outputs: Type name: Product

let properties = productType.GetProperties()
                         // => Get all properties via reflection
                         // => Returns PropertyInfo array

for prop in properties do
    printfn "Property: %s (type: %s)" prop.Name prop.PropertyType.Name
                         // => Outputs:
                         // => Property: Id (type: Int32)
                         // => Property: Name (type: String)
                         // => Property: Price (type: Double)

// Dynamic property access
let product = { Id = 1; Name = "Widget"; Price = 9.99 }

let getPropertyValue (obj: obj) (propName: string) =
                         // => Generic property getter
    let typ = obj.GetType()
    let prop = typ.GetProperty(propName)
                         // => Get property by name
    prop.GetValue(obj)   // => Get property value dynamically

let name = getPropertyValue product "Name"
                         // => Dynamically access Name property
                         // => name is "Widget" (type: obj)

printfn "Product name: %A" name
                         // => Outputs: Product name: "Widget"

// Generic printing
let printRecord (record: 'T) =
                         // => Generic record printer
    let typ = typeof<'T>
    printfn "%s:" typ.Name
    for prop in typ.GetProperties() do
        let value = prop.GetValue(record)
        printfn "  %s = %A" prop.Name value

printRecord product
                         // => Outputs:
                         // => Product:
                         // =>   Id = 1
                         // =>   Name = "Widget"
                         // =>   Price = 9.99

Key Takeaway: Reflection provides runtime type inspection and dynamic property access, enabling generic algorithms that work across types without compile-time knowledge.

Why It Matters: Reflection enables serialization libraries, ORMs, and testing frameworks to work generically across all F# types. JSON serializers use reflection to convert any F# record to JSON automatically, eliminating 90% of boilerplate code required in languages without powerful reflection while maintaining type safety at compile time.

Example 68: Advanced Type Constraints with SRTP

Statically Resolved Type Parameters (SRTP) enable compile-time polymorphism through inline functions and type constraints.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
graph TD
    A[Generic Code<br/>inline add]:::blue --> B[Compile Time<br/>Type Resolution]:::orange
    B --> C[int + int]:::teal
    B --> D[float + float]:::teal
    B --> E[string + string]:::teal

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#029E73,stroke:#000,color:#fff
    style E fill:#029E73,stroke:#000,color:#fff

Code:

// Example 68: Advanced Type Constraints with SRTP
let inline add x y =
                         // => inline enables SRTP
                         // => No type annotation needed
    x + y                // => + operator constraint
                         // => Works for ANY type with + operator

let intSum = add 10 20   // => int addition (30)
let floatSum = add 3.5 2.1
                         // => float addition (5.6)
let stringConcat = add "Hello" " World"
                         // => string concatenation

printfn "%d" intSum      // => Outputs: 30
printfn "%.1f" floatSum  // => Outputs: 5.6
printfn "%s" stringConcat // => Outputs: Hello World

// Explicit SRTP constraint
let inline sumList< ^T when ^T : (static member Zero : ^T)
                           and ^T : (static member (+) : ^T * ^T -> ^T)>
                         // => ^T is SRTP type parameter
                         // => Requires Zero static member
                         // => Requires + operator
    (items: ^T list) : ^T =
    List.fold (+) LanguagePrimitives.GenericZero items
                         // => GenericZero works with SRTP

let intList = [1; 2; 3; 4; 5]
let intTotal = sumList intList
                         // => intTotal is 15

let floatList = [1.5; 2.5; 3.0]
let floatTotal = sumList floatList
                         // => floatTotal is 7.0

printfn "Int total: %d" intTotal
                         // => Outputs: Int total: 15
printfn "Float total: %.1f" floatTotal
                         // => Outputs: Float total: 7.0

Key Takeaway: SRTP with inline functions enables compile-time polymorphism through operator and member constraints, creating generic code that works across types without runtime overhead.

Why It Matters: SRTP provides zero-cost abstractions impossible in languages with runtime generics. Numerical libraries use SRTP to write algorithms once that work efficiently for int, float, BigInteger, and custom numeric types, achieving C++ template-level performance while maintaining F#’s type safety and avoiding code duplication.

Example 69: Custom Operators and Operator Overloading

F# allows defining custom operators for domain-specific syntax, creating expressive DSLs.

// Example 69: Custom Operators and Operator Overloading
type Vector = { X: float; Y: float }

// Custom operators for vector math
let (+.) v1 v2 =
                         // => Custom operator: +.
                         // => Vector addition
    { X = v1.X + v2.X; Y = v1.Y + v2.Y }

let (-.) v1 v2 =
                         // => Custom operator: -.
                         // => Vector subtraction
    { X = v1.X - v2.X; Y = v1.Y - v2.Y }

let ( *. ) scalar v =
                         // => Custom operator: *.
                         // => Scalar multiplication
    { X = scalar * v.X; Y = scalar * v.Y }

let v1 = { X = 3.0; Y = 4.0 }
let v2 = { X = 1.0; Y = 2.0 }

let sum = v1 +. v2       // => Use custom + operator
                         // => sum is { X = 4.0; Y = 6.0 }

let diff = v1 -. v2      // => Use custom - operator
                         // => diff is { X = 2.0; Y = 2.0 }

let scaled = 2.0 *. v1   // => Use custom * operator
                         // => scaled is { X = 6.0; Y = 8.0 }

printfn "Sum: %A" sum
printfn "Diff: %A" diff
printfn "Scaled: %A" scaled

// Pipeline operator alternative
let (|>>) x f = f x |> List.map
                         // => Custom pipeline operator
                         // => Combines pipe and map

let numbers = [1; 2; 3; 4; 5]
let doubled = numbers |>> (fun x -> x * 2)
                         // => Uses custom pipeline
                         // => doubled is [2; 4; 6; 8; 10]

printfn "Doubled: %A" doubled

Key Takeaway: Custom operators create domain-specific syntax for types, enabling expressive code that reads like mathematical notation while maintaining type safety.

Why It Matters: Custom operators make domain logic self-documenting. Physics simulations use vector operators (v1 +. v2) that mirror mathematical notation exactly, reducing transcription errors compared to method calls (v1.Add(v2)) while improving readability for scientists reviewing code.

Example 70: Parameterized Active Patterns

Parameterized active patterns accept arguments, enabling flexible pattern matching with context.

// Example 70: Parameterized Active Patterns
let (|DivisibleBy|_|) divisor n =
                         // => Parameterized partial active pattern
                         // => divisor is parameter
                         // => n is value being matched
    if n % divisor = 0 then
        Some (n / divisor)
                         // => Return Some with quotient
    else
        None             // => Return None if not divisible

let describe n =
    match n with
    | DivisibleBy 15 quotient ->
                         // => Pass 15 as divisor parameter
        sprintf "%d is divisible by 15 (quotient: %d)" n quotient
    | DivisibleBy 3 quotient ->
                         // => Pass 3 as divisor parameter
        sprintf "%d is divisible by 3 (quotient: %d)" n quotient
    | DivisibleBy 5 quotient ->
                         // => Pass 5 as divisor parameter
        sprintf "%d is divisible by 5 (quotient: %d)" n quotient
    | _ ->
        sprintf "%d is not divisible by 3, 5, or 15" n

printfn "%s" (describe 15)
                         // => Outputs: 15 is divisible by 15 (quotient: 1)
printfn "%s" (describe 9)
                         // => Outputs: 9 is divisible by 3 (quotient: 3)
printfn "%s" (describe 10)
                         // => Outputs: 10 is divisible by 5 (quotient: 2)
printfn "%s" (describe 7)
                         // => Outputs: 7 is not divisible by 3, 5, or 15

// Regex parameterized pattern
open System.Text.RegularExpressions

let (|Regex|_|) pattern input =
                         // => Regex active pattern
    let m = Regex.Match(input, pattern)
    if m.Success then
        Some (List.tail [ for g in m.Groups -> g.Value ])
                         // => Return captured groups
    else
        None

let parseEmail email =
    match email with
    | Regex @"(.+)@(.+)\.(.+)" [user; domain; tld] ->
                         // => Match email pattern with captures
        sprintf "User: %s, Domain: %s.%s" user domain tld
    | _ ->
        "Invalid email"

printfn "%s" (parseEmail "alice@example.com")
                         // => Outputs: User: alice, Domain: example.com

Key Takeaway: Parameterized active patterns accept arguments for flexible pattern matching, enabling patterns that adapt to context like divisibility checks or regex matching.

Why It Matters: Parameterized patterns eliminate repetitive conditional logic. Parsing libraries use regex patterns to extract data from structured text, reducing string manipulation code to declarative pattern matches that are easier to test and maintain.

Example 71: Type Extensions and Augmentations

Type extensions add members to existing types, including types from external libraries, without inheritance.

// Example 71: Type Extensions and Augmentations
// Intrinsic extension (same file as type definition)
type Person = { Name: string; Age: int }

type Person with
                         // => Intrinsic extension
                         // => Adds members to Person type
    member this.IsAdult = this.Age >= 18
                         // => Computed property
    member this.Greet() = sprintf "Hello, I'm %s" this.Name
                         // => Instance method

let alice = { Name = "Alice"; Age = 30 }

printfn "%s" (alice.Greet())
                         // => Outputs: Hello, I'm Alice
printfn "Is adult: %b" alice.IsAdult
                         // => Outputs: Is adult: true

// Optional extension (any file)
module StringExtensions =
    type System.String with
                         // => Extend .NET String type
        member this.Reverse() =
                         // => New method on String
            System.String(this.ToCharArray() |> Array.rev)
        member this.IsPalindrome() =
                         // => Another extension method
            this = this.Reverse()

open StringExtensions

let text = "hello"
let reversed = text.Reverse()
                         // => Call extension method
                         // => reversed is "olleh"

printfn "Reversed: %s" reversed

let palindrome = "racecar"
printfn "Is '%s' a palindrome? %b" palindrome (palindrome.IsPalindrome())
                         // => Outputs: Is 'racecar' a palindrome? true

// Extension with operators
type System.Int32 with
                         // => Extend int type
    member this.IsEven() = this % 2 = 0
    member this.IsOdd() = this % 2 <> 0

printfn "Is 10 even? %b" (10.IsEven())
                         // => Outputs: Is 10 even? true

Key Takeaway: Type extensions add members to existing types without inheritance, enabling extension of .NET types and third-party libraries with custom functionality.

Why It Matters: Type extensions adapt external libraries to domain needs without wrapper classes. Enterprise applications extend .NET types like DateTime with domain-specific methods (date.IsBusinessDay(), date.NextQuarter()), improving readability while avoiding the performance cost and ceremony of wrapper objects.

Example 72: Units of Measure - Type-Safe Calculations

Units of measure provide compile-time dimensional analysis, preventing unit conversion errors in calculations.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
graph TD
    A[100.0 meter]:::blue --> B[/ time<br/>5.0 second]:::orange
    B --> C[20.0 meter/second]:::teal

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff

Code:

// Example 72: Units of Measure - Type-Safe Calculations
[<Measure>] type meter
[<Measure>] type second
[<Measure>] type kg

let distance = 100.0<meter>
                         // => distance has type float<meter>
                         // => Type system tracks units

let time = 5.0<second>   // => time has type float<second>

let speed = distance / time
                         // => speed has type float<meter/second>
                         // => Unit division computed automatically

printfn "Speed: %.1f m/s" speed
                         // => Outputs: Speed: 20.0 m/s
                         // => Units erased at runtime (zero cost)

// Type error prevention
// let invalid = distance + time
                         // => COMPILE ERROR: Cannot add meter + second
                         // => Type system prevents nonsensical operations

// Unit conversion
[<Measure>] type cm

let toCentimeters (m: float<meter>) : float<cm> =
                         // => Explicit unit conversion
    m * 100.0<cm/meter>  // => Multiply by conversion factor

let distanceCm = toCentimeters distance
                         // => distanceCm has type float<cm>
                         // => distanceCm is 10000.0<cm>

printfn "Distance: %.0f cm" distanceCm

// Derived units
let mass = 50.0<kg>
let force = mass * (distance / (time * time))
                         // => force has type float<kg meter/second^2>
                         // => Newton's second law: F = ma

printfn "Force: %.1f N" force

// Generic functions with units
let add< [<Measure>] 'u > (x: float<'u>) (y: float<'u>) : float<'u> =
                         // => Generic over unit of measure
                         // => 'u is unit type parameter
    x + y

let total = add 10.0<meter> 20.0<meter>
                         // => total is 30.0<meter>

Key Takeaway: Units of measure provide compile-time dimensional analysis with zero runtime cost, preventing unit conversion errors through type system enforcement.

Why It Matters: The Mars Climate Orbiter was lost due to metric/imperial unit confusion. Units of measure eliminate this entire class of errors at compile time with zero runtime overhead, making F# ideal for scientific computing, physics simulations, and financial calculations where unit correctness is critical.

Example 73: Phantom Types for Type-State Pattern

Phantom types encode state in type system, making illegal state transitions impossible at compile time.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC
graph TD
    A[Connection<Closed>]:::blue --> B[open]:::orange
    B --> C[Connection<Open>]:::teal
    C --> D[query]:::orange
    D --> E[Result]:::purple
    C --> F[close]:::orange
    F --> A

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#DE8F05,stroke:#000,color:#fff
    style E fill:#CC78BC,stroke:#000,color:#fff
    style F fill:#DE8F05,stroke:#000,color:#fff

Code:

// Example 73: Phantom Types for Type-State Pattern
type Closed = class end  // => Phantom type (no instances)
type Open = class end    // => Phantom type

type Connection<'state> = private {
                         // => Generic over state
                         // => 'state is phantom (never instantiated)
    ConnectionString: string
}

module Connection =
    let create connectionString : Connection<Closed> =
                         // => Returns closed connection
        { ConnectionString = connectionString }

    let open' (conn: Connection<Closed>) : Connection<Open> =
                         // => Accepts closed, returns open
                         // => Type system enforces state
        printfn "Opening connection: %s" conn.ConnectionString
        { ConnectionString = conn.ConnectionString }

    let query (sql: string) (conn: Connection<Open>) : string =
                         // => Requires open connection
                         // => COMPILE ERROR if connection closed
        printfn "Executing: %s" sql
        "Query result"   // => Return result

    let close (conn: Connection<Open>) : Connection<Closed> =
                         // => Accepts open, returns closed
        printfn "Closing connection: %s" conn.ConnectionString
        { ConnectionString = conn.ConnectionString }

// Usage
let conn = Connection.create "Server=localhost"
                         // => conn has type Connection<Closed>

// let result = Connection.query "SELECT *" conn
                         // => COMPILE ERROR: conn is Connection<Closed>
                         // => query requires Connection<Open>

let openConn = Connection.open' conn
                         // => openConn has type Connection<Open>

let result = Connection.query "SELECT * FROM users" openConn
                         // => OK: openConn is Connection<Open>
                         // => Outputs: Executing: SELECT * FROM users

let closedConn = Connection.close openConn
                         // => closedConn has type Connection<Closed>

// let result2 = Connection.query "SELECT *" closedConn
                         // => COMPILE ERROR: closedConn is Connection<Closed>

printfn "Result: %s" result

Key Takeaway: Phantom types encode state in the type system, making illegal state transitions impossible at compile time through type-level state tracking.

Why It Matters: Phantom types eliminate entire categories of runtime errors. Database connection pools use phantom types to prevent querying closed connections, file APIs prevent reading closed files, and authentication systems prevent accessing resources without valid tokens—all enforced at compile time with zero runtime cost.

Example 74: GADTs Emulation with Discriminated Unions

F# emulates Generalized Algebraic Data Types (GADTs) using discriminated unions with type constraints.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
graph TD
    A[Expr Tree]:::blue --> B[TLit 10]:::orange
    A --> C[TAdd]:::orange
    C --> D[TLit 20]:::teal
    C --> E[Eval: 30]:::teal

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#DE8F05,stroke:#000,color:#fff
    style D fill:#029E73,stroke:#000,color:#fff
    style E fill:#029E73,stroke:#000,color:#fff

Code:

// Example 74: GADTs Emulation with Discriminated Unions
type Value<'a> =
    | IntValue of int
    | StringValue of string
    | BoolValue of bool

let eval (value: Value<'a>) : 'a =
                         // => Generic return type
    match value with
    | IntValue i -> box i :?> 'a
                         // => Cast int to 'a
    | StringValue s -> box s :?> 'a
                         // => Cast string to 'a
    | BoolValue b -> box b :?> 'a
                         // => Cast bool to 'a

let intVal: int = eval (IntValue 42)
                         // => intVal is 42 (type: int)

let strVal: string = eval (StringValue "hello")
                         // => strVal is "hello" (type: string)

printfn "Int: %d" intVal
printfn "String: %s" strVal

// Better GADT emulation with type-safe evaluation
type Expr =
    | Lit of int
    | Add of Expr * Expr
    | Eq of Expr * Expr

type 'a TypedExpr =
    | TLit of int
    | TAdd of int TypedExpr * int TypedExpr
    | TEq of int TypedExpr * int TypedExpr

let rec evalTyped (expr: 'a TypedExpr) : 'a =
                         // => Type-safe evaluation
    match expr with
    | TLit i -> box i :?> 'a
    | TAdd (e1, e2) ->
        let v1 = evalTyped e1
        let v2 = evalTyped e2
        box (v1 + v2) :?> 'a
    | TEq (e1, e2) ->
        let v1 = evalTyped e1
        let v2 = evalTyped e2
        box (v1 = v2) :?> 'a

let expr = TAdd(TLit 10, TLit 20)
                         // => expr has type int TypedExpr

let result: int = evalTyped expr
                         // => result is 30

printfn "Expression result: %d" result

let eqExpr = TEq(TLit 5, TLit 5)
                         // => eqExpr has type int TypedExpr

let eqResult: bool = evalTyped eqExpr
                         // => eqResult is true

printfn "Equality result: %b" eqResult

Key Takeaway: F# emulates GADTs using discriminated unions with type parameters and runtime casts, enabling type-safe expression evaluation with heterogeneous return types.

Why It Matters: GADT emulation enables building type-safe interpreters and compilers in F#. Expression evaluators use GADTs to ensure arithmetic expressions return int and boolean expressions return bool, catching type errors at compile time that would be runtime exceptions in dynamically typed languages.

Example 75: Functional Dependency Injection

Dependency injection in functional style uses higher-order functions and closures instead of IoC containers.

// Example 75: Functional Dependency Injection
type ILogger =
    abstract member Log : string -> unit

type ConsoleLogger() =
    interface ILogger with
        member _.Log(msg) = printfn "LOG: %s" msg

type IDatabase =
    abstract member Query : string -> string

type MockDatabase() =
    interface IDatabase with
        member _.Query(sql) = sprintf "Mock result for: %s" sql

// Service using dependency injection
type UserService(logger: ILogger, db: IDatabase) =
    member _.GetUser(id: int) =
        logger.Log(sprintf "Fetching user %d" id)
        let result = db.Query(sprintf "SELECT * FROM users WHERE id = %d" id)
        logger.Log(sprintf "Query returned: %s" result)
        result

// Functional DI: Functions taking dependencies as parameters
let createUserService (logger: ILogger) (db: IDatabase) =
                         // => Factory function
    fun (id: int) ->     // => Returns function with dependencies captured
        logger.Log(sprintf "Fetching user %d" id)
        let result = db.Query(sprintf "SELECT * FROM users WHERE id = %d" id)
        logger.Log(sprintf "Query returned: %s" result)
        result

// Setup dependencies
let logger = ConsoleLogger() :> ILogger
let db = MockDatabase() :> IDatabase

// Object-oriented DI
let service = UserService(logger, db)
let user1 = service.GetUser(42)
                         // => Outputs: LOG: Fetching user 42
                         // => Outputs: LOG: Query returned: Mock result for: SELECT * FROM users WHERE id = 42

// Functional DI
let getUser = createUserService logger db
                         // => getUser is function with dependencies
                         // => getUser has type: int -> string

let user2 = getUser 99   // => Call function with injected dependencies
                         // => Outputs: LOG: Fetching user 99

printfn "User 1: %s" user1
printfn "User 2: %s" user2

// Reader monad for DI
type Reader<'env, 'a> = Reader of ('env -> 'a)

let runReader (Reader f) env = f env

let ask = Reader id      // => Gets environment

let getUserReader userId = Reader(fun (logger: ILogger, db: IDatabase) ->
                         // => Reader computation capturing dependencies
    logger.Log(sprintf "Fetching user %d" userId)
    db.Query(sprintf "SELECT * FROM users WHERE id = %d" userId)
)

let user3 = runReader (getUserReader 123) (logger, db)
                         // => Run reader with environment
printfn "User 3: %s" user3

Key Takeaway: Functional dependency injection uses higher-order functions and closures to inject dependencies, avoiding heavy IoC containers while maintaining testability and composition.

Why It Matters: Functional DI eliminates runtime reflection and container complexity. Microservices inject database connections, loggers, and configuration through function parameters, improving testability (easy to pass mocks) and performance (no reflection overhead) compared to traditional IoC containers.

Example 76: Event Sourcing Pattern

Event sourcing stores state changes as immutable events, enabling complete audit trails and time travel debugging.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC
graph TD
    A[AccountCreated<br/>1000.0]:::blue --> B[MoneyDeposited<br/>+500]:::orange
    B --> C[MoneyWithdrawn<br/>-200]:::teal
    C --> D[Current State<br/>1300.0]:::purple

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#CC78BC,stroke:#000,color:#fff

Code:

// Example 76: Event Sourcing Pattern
type Event =
    | AccountCreated of accountId: string * initialBalance: float
    | MoneyDeposited of amount: float
    | MoneyWithdrawn of amount: float

type AccountState = {
    AccountId: string
    Balance: float
}

let applyEvent state event =
                         // => State transition function
                         // => Pure function: state + event -> new state
    match event with
    | AccountCreated (id, balance) ->
        { AccountId = id; Balance = balance }
    | MoneyDeposited amount ->
        { state with Balance = state.Balance + amount }
    | MoneyWithdrawn amount ->
        { state with Balance = state.Balance - amount }

// Event stream (immutable history)
let events = [
    AccountCreated ("ACC001", 1000.0)
    MoneyDeposited 500.0
    MoneyWithdrawn 200.0
    MoneyDeposited 100.0
]

// Rebuild current state from events
let currentState = events |> List.fold applyEvent { AccountId = ""; Balance = 0.0 }
                         // => Fold applies each event sequentially
                         // => currentState is { AccountId = "ACC001"; Balance = 1400.0 }

printfn "Current state: %A" currentState
                         // => Outputs: Current state: {AccountId = "ACC001"; Balance = 1400.0}

// Time travel: state at any point in history
let stateAfterSecondEvent = events
                            |> List.take 2
                            |> List.fold applyEvent { AccountId = ""; Balance = 0.0 }
                         // => State after first 2 events
                         // => Balance is 1500.0 (1000 + 500)

printfn "State after 2 events: %A" stateAfterSecondEvent

// Projections: different views of same events
let totalDeposited = events
                     |> List.choose (function
                         | MoneyDeposited amt -> Some amt
                         | _ -> None)
                     |> List.sum
                         // => totalDeposited is 600.0 (500 + 100)

let totalWithdrawn = events
                     |> List.choose (function
                         | MoneyWithdrawn amt -> Some amt
                         | _ -> None)
                     |> List.sum
                         // => totalWithdrawn is 200.0

printfn "Total deposited: %.2f" totalDeposited
printfn "Total withdrawn: %.2f" totalWithdrawn

Key Takeaway: Event sourcing stores state as immutable event streams, enabling complete audit trails, time travel debugging, and multiple projections from the same event history.

Why It Matters: Event sourcing provides complete audit trails required by financial regulations and enables debugging production issues by replaying events. Banks use event sourcing for account transactions, storing every state change as an immutable event, enabling regulatory compliance and forensic analysis of issues that manifest only after thousands of operations.

Example 77: CQRS with F# - Command Query Separation

CQRS separates read (query) and write (command) models, optimizing each independently.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC
graph TD
    A[Command]:::blue --> B[Write Model]:::orange
    B --> C[Event Store]:::teal
    C --> D[Read Model]:::purple
    E[Query]:::blue --> D

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#CC78BC,stroke:#000,color:#fff
    style E fill:#0173B2,stroke:#000,color:#fff

Code:

// Example 77: CQRS with F# - Command Query Separation
// Commands (write operations)
type Command =
    | CreateOrder of orderId: string * customerId: string * items: string list
    | AddItem of orderId: string * item: string
    | CancelOrder of orderId: string

// Events (facts about what happened)
type OrderEvent =
    | OrderCreated of orderId: string * customerId: string * items: string list
    | ItemAdded of orderId: string * item: string
    | OrderCancelled of orderId: string

// Write model (command handler)
let handleCommand command =
                         // => Command -> Event
                         // => Validates and produces events
    match command with
    | CreateOrder (id, customerId, items) ->
        Ok [OrderCreated (id, customerId, items)]
    | AddItem (id, item) ->
        Ok [ItemAdded (id, item)]
    | CancelOrder id ->
        Ok [OrderCancelled id]

// Read model (optimized for queries)
type OrderReadModel = {
    OrderId: string
    CustomerId: string
    Items: string list
    Status: string
}

let applyEventToReadModel (model: OrderReadModel option) event =
                         // => Event -> ReadModel
                         // => Builds denormalized read model
    match event with
    | OrderCreated (id, customerId, items) ->
        Some { OrderId = id; CustomerId = customerId; Items = items; Status = "Active" }
    | ItemAdded (id, item) ->
        model |> Option.map (fun m -> { m with Items = item :: m.Items })
    | OrderCancelled id ->
        model |> Option.map (fun m -> { m with Status = "Cancelled" })

// Event store (simplified)
let mutable eventStore: OrderEvent list = []

let executeCommand command =
                         // => Execute command and store events
    match handleCommand command with
    | Ok events ->
        eventStore <- eventStore @ events
                         // => Append events to store
        events
    | Error msg ->
        printfn "Command failed: %s" msg
        []

// Query side (read model projection)
let buildReadModel events =
                         // => Build read model from events
    events |> List.fold applyEventToReadModel None

// Execute commands
executeCommand (CreateOrder ("ORD1", "CUST1", ["Item1"; "Item2"]))
executeCommand (AddItem ("ORD1", "Item3"))

// Query read model
let readModel = buildReadModel eventStore
                         // => Build current read model

match readModel with
| Some model ->
    printfn "Order: %s" model.OrderId
    printfn "Customer: %s" model.CustomerId
    printfn "Items: %A" model.Items
    printfn "Status: %s" model.Status
| None ->
    printfn "No order found"

Key Takeaway: CQRS separates commands (writes) from queries (reads), optimizing write models for consistency and read models for query performance with denormalization.

Why It Matters: CQRS enables independent scaling of read and write workloads. E-commerce platforms handle 100x more reads than writes—CQRS allows caching aggressive read models while maintaining strict consistency in write models, achieving low query latency for product listings while ensuring order transactions remain ACID-compliant.

Example 78: Make Illegal States Unrepresentable

Domain modeling with types makes invalid states impossible to construct, eliminating validation logic.

// Example 78: Make Illegal States Unrepresentable
// BAD: Allows invalid states
type BadOrder = {
    Id: string
    Status: string       // => Can be any string (typos possible)
    ShippingAddress: string option
                         // => Can be None even for shipped orders
}

// GOOD: Invalid states are unrepresentable
type UnshippedOrder = { Id: string }
type ShippedOrder = { Id: string; ShippingAddress: string }
                         // => Shipped MUST have address
type CancelledOrder = { Id: string; Reason: string }

type OrderStatus =
    | Unshipped of UnshippedOrder
    | Shipped of ShippedOrder
    | Cancelled of CancelledOrder

// State transitions enforce business rules
let ship (order: UnshippedOrder) (address: string) : ShippedOrder =
                         // => Can only ship unshipped orders
                         // => MUST provide address
    { Id = order.Id; ShippingAddress = address }

let cancel (order: UnshippedOrder) (reason: string) : CancelledOrder =
                         // => Can only cancel unshipped orders
    { Id = order.Id; Reason = reason }

// Usage
let order = Unshipped { Id = "ORD123" }
                         // => Order starts unshipped

let shippedOrder = match order with
                   | Unshipped o -> ship o "123 Main St"
                         // => Ship with address
                   | _ -> failwith "Cannot ship this order"

printfn "Shipped to: %s" shippedOrder.ShippingAddress

// COMPILE ERROR: Cannot ship already shipped order
// let reshipped = ship shippedOrder "456 Elm St"
                         // => Type error: ship requires UnshippedOrder

// More complex example: Email validation
type UnvalidatedEmail = UnvalidatedEmail of string
type ValidatedEmail = private ValidatedEmail of string
                         // => private constructor prevents direct creation

module Email =
    let create (s: string) : Result<ValidatedEmail, string> =
                         // => Smart constructor
        if s.Contains("@") then
            Ok (ValidatedEmail s)
        else
            Error "Invalid email format"

    let value (ValidatedEmail s) = s
                         // => Unwrap validated email

// Usage
let emailResult = Email.create "alice@example.com"

match emailResult with
| Ok email ->
    printfn "Valid email: %s" (Email.value email)
| Error msg ->
    printfn "Error: %s" msg

// COMPILE ERROR: Cannot create ValidatedEmail directly
// let badEmail = ValidatedEmail "notanemail"
                         // => Constructor is private

Key Takeaway: Design types so invalid states cannot be constructed, moving validation from runtime checks to compile-time guarantees through the type system.

Why It Matters: Making illegal states unrepresentable eliminates much validation logic and prevents bugs that slip through validation. Financial systems model trades with separate types for PendingTrade, ExecutedTrade, and SettledTrade, making it impossible to settle a pending trade or re-execute an executed trade—errors caught at compile time instead of production.

Example 79: Advanced Parser Combinators

Parser combinators compose small parsers into complex grammars using functional composition.

// Example 79: Advanced Parser Combinators
type Parser<'a> = Parser of (string -> Result<'a * string, string>)
                         // => Parser is function: input -> Result

let run (Parser p) input = p input

// Primitive parsers
let pchar c = Parser(fun input ->
                         // => Parse single character
    if String.length input > 0 && input.[0] = c then
        Ok (c, input.[1..])
                         // => Success: return char and remaining input
    else
        Error (sprintf "Expected '%c'" c)
)

let (<|>) (Parser p1) (Parser p2) = Parser(fun input ->
                         // => Choice combinator: try p1, then p2
    match p1 input with
    | Ok result -> Ok result
    | Error _ -> p2 input
)

let (>>=) (Parser p) f = Parser(fun input ->
                         // => Bind combinator: sequence parsers
    match p input with
    | Ok (value, remaining) ->
        let (Parser p2) = f value
        p2 remaining
    | Error msg -> Error msg
)

let preturn value = Parser(fun input -> Ok (value, input))
                         // => Return combinator: always succeeds

// Parse digit
let pdigit = Parser(fun input ->
    if String.length input > 0 && System.Char.IsDigit(input.[0]) then
        Ok (input.[0], input.[1..])
    else
        Error "Expected digit"
)

// Many combinator: 0 or more
let rec many (Parser p) = Parser(fun input ->
    match p input with
    | Ok (value, remaining) ->
        match run (many (Parser p)) remaining with
        | Ok (values, final) -> Ok (value :: values, final)
        | Error _ -> Ok ([value], remaining)
    | Error _ -> Ok ([], input)
)

// Parse integer
let pint = many pdigit >>= (fun digits ->
    let s = System.String(List.toArray digits)
    preturn (int s)
)

// Test parsers
match run (pchar 'a') "abc" with
| Ok (c, remaining) ->
    printfn "Parsed: '%c', Remaining: '%s'" c remaining
                         // => Outputs: Parsed: 'a', Remaining: 'bc'
| Error msg ->
    printfn "Error: %s" msg

match run pint "123abc" with
| Ok (value, remaining) ->
    printfn "Parsed int: %d, Remaining: '%s'" value remaining
                         // => Outputs: Parsed int: 123, Remaining: 'abc'
| Error msg ->
    printfn "Error: %s" msg

Key Takeaway: Parser combinators compose primitive parsers using functional operators, building complex grammars from simple building blocks without parser generators.

Why It Matters: Parser combinators enable embedded DSLs for parsing without external tools. Compilers use parser combinators to parse programming languages entirely in F#, achieving faster development than yacc/bison while maintaining type safety and composability for parser evolution.

Example 80: Metaprogramming with Code Generation

F# type providers and quotations enable compile-time code generation from external schemas.

// Example 80: Metaprogramming with Code Generation
open FSharp.Quotations

// Generate function from quotation
let generateAdder x = <@ fun y -> y + x @>
                         // => Quotation capturing x
                         // => Generates function at compile time

let addFive = generateAdder 5
                         // => addFive is quotation: fun y -> y + 5

// Evaluate quotation (simplified)
let evalQuotation (expr: Expr<'a>) : 'a =
                         // => Evaluate quotation to value
    FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.EvaluateQuotation(expr)
    :?> 'a

let adder = evalQuotation addFive
                         // => adder is function: int -> int

let result = adder 10    // => result is 15

printfn "Result: %d" result

// Code generation pattern
type GeneratedCode = {
    PropertyName: string
    PropertyType: string
}

let generateProperty prop =
                         // => Generate property code string
    sprintf "member this.%s : %s" prop.PropertyName prop.PropertyType

let properties = [
    { PropertyName = "Id"; PropertyType = "int" }
    { PropertyName = "Name"; PropertyType = "string" }
]

let generatedCode = properties |> List.map generateProperty
                         // => Generate code strings

for code in generatedCode do
    printfn "%s" code
                         // => Outputs:
                         // => member this.Id : int
                         // => member this.Name : string

// Quotation manipulation
let replaceValue (expr: Expr) (oldVal: int) (newVal: int) =
                         // => Transform quotation
    let rec replace e =
        match e with
        | Quotations.Patterns.Value (v, t) when v :? int && (v :?> int) = oldVal ->
            Expr.Value(newVal, t)
        | Quotations.Patterns.Call (target, method, args) ->
            Expr.Call(target, method, List.map replace args)
        | _ -> e
    replace expr

let original = <@ 1 + 2 + 3 @>
                         // => Original quotation

let modified = replaceValue original 2 20
                         // => Replace 2 with 20
                         // => Result: 1 + 20 + 3 = 24

printfn "Original: %A" original
printfn "Modified: %A" modified

Key Takeaway: Metaprogramming with quotations and code generation enables compile-time code synthesis from schemas, reducing boilerplate and ensuring generated code type-checks.

Why It Matters: Type providers generate type-safe database access code from schemas at compile time. SQL type providers read database schemas and generate F# types with IntelliSense support, eliminating most hand-written data access code while catching schema mismatches at compile time instead of runtime.

Example 81: C# Interop - Consuming and Exposing APIs

F# seamlessly interoperates with C# libraries while exposing F#-friendly APIs to C# consumers.

// Example 81: C# Interop - Consuming and Exposing APIs
open System.Collections.Generic

// Consuming C# APIs
let dict = Dictionary<string, int>()
                         // => C# Dictionary from BCL
dict.Add("apple", 1)     // => Mutable C# API
dict.Add("banana", 2)

printfn "Apple count: %d" dict.["apple"]
                         // => Outputs: Apple count: 1

// Convert to F# map
let fsharpMap = dict |> Seq.map (|KeyValue|) |> Map.ofSeq
                         // => Convert to immutable F# Map
                         // => |KeyValue| is active pattern

printfn "F# Map: %A" fsharpMap

// Exposing F# code to C#
type FSharpLibrary() =
                         // => Class visible to C#
    member _.Add(x: int, y: int) : int =
                         // => Simple method
        x + y

    member _.ProcessList(items: int list) : int =
                         // => F# list parameter (C# sees IEnumerable)
        items |> List.sum

    member _.GetOption(value: int option) : System.Nullable<int> =
                         // => Option -> Nullable for C#
        match value with
        | Some v -> System.Nullable<int>(v)
        | None -> System.Nullable<int>()

    static member CreatePerson(name: string, age: int) =
                         // => Factory method for C#
        { Name = name; Age = age }

type Person = { Name: string; Age: int }

// Attribute for C# property access
[<CLIMutable>]
type CLIPerson = {
    Name: string
    Age: int
}                        // => [<CLIMutable>] adds default constructor
                         // => Enables C# object initializer syntax

// Extension methods for C#
[<System.Runtime.CompilerServices.Extension>]
module StringExtensions =
    [<System.Runtime.CompilerServices.Extension>]
    let ToUpperInvariant (s: string) =
                         // => Extension method visible to C#
        s.ToUpperInvariant()

let lib = FSharpLibrary()

printfn "Sum: %d" (lib.Add(5, 10))
printfn "List sum: %d" (lib.ProcessList([1; 2; 3; 4; 5]))

let someValue = Some 42
printfn "Nullable: %A" (lib.GetOption(someValue))

Key Takeaway: F# interoperates seamlessly with C# using attribute annotations, converting F# types (options, lists) to C#-friendly equivalents (Nullable, IEnumerable) at API boundaries.

Why It Matters: Seamless C# interop enables incremental F# adoption in existing .NET codebases. Teams introduce F# for domain modeling and business logic (exploiting algebraic types and immutability) while exposing C#-friendly APIs, reducing bugs through F# safety without rewriting entire applications.

Example 82: Performance Optimization - Struct vs Class

F# supports both reference types (classes) and value types (structs) for performance-critical scenarios.

// Example 82: Performance Optimization - Struct vs Class
// Reference type (class) - heap allocated
type PointClass = { X: float; Y: float }

// Value type (struct) - stack allocated
[<Struct>]
type PointStruct = { X: float; Y: float }

// Performance comparison (allocation)
let classPoints = [| for i in 1 .. 1000000 -> { X = float i; Y = float i } |]
                         // => 1M PointClass allocations on heap
                         // => Triggers garbage collection

let structPoints = [| for i in 1 .. 1000000 -> { X = float i; Y = float i } |]
                         // => 1M PointStruct values on stack/inline
                         // => No GC pressure

printfn "Class points: %d" classPoints.Length
printfn "Struct points: %d" structPoints.Length

// Struct with interface (boxed when cast to interface)
[<Struct>]
type Counter = {
    mutable Count: int   // => Mutable field allowed in struct
} with
    interface System.IComparable with
        member this.CompareTo(obj) =
            match obj with
            | :? Counter as other -> this.Count.CompareTo(other.Count)
            | _ -> 1

let mutable counter = { Count = 0 }
                         // => Struct value
counter.Count <- 10      // => Mutation (structs are mutable!)

printfn "Counter: %d" counter.Count

// When to use struct vs class
// Struct: Small (<16 bytes), frequently allocated, numeric-like
// Class: Large, infrequently allocated, reference semantics needed

// Inline functions for performance
let inline add x y = x + y
                         // => inline removes function call overhead
                         // => Code inlined at call site

let sum = add 10 20      // => No function call, direct addition

printfn "Sum: %d" sum

Key Takeaway: Structs reduce GC pressure through stack allocation and inlining, improving performance for small, frequently-allocated types at the cost of value semantics.

Why It Matters: Struct optimization eliminates 90% of garbage collection pauses in numerical computing. Game engines use structs for vectors and points, processing millions per frame with zero GC overhead, achieving high frame rates triggering GC every frame.

Example 83: Profiling and Benchmarking

F# supports profiling tools for identifying performance bottlenecks and memory issues.

// Example 83: Profiling and Benchmarking
open System.Diagnostics

// Simple benchmarking
let benchmark name f =
                         // => Benchmark function execution
    let sw = Stopwatch.StartNew()
    let result = f()
    sw.Stop()
    printfn "%s: %dms" name sw.ElapsedMilliseconds
    result

// Compare list vs sequence performance
let listSum = benchmark "List.sum" (fun () ->
    [1..1000000] |> List.sum
)                        // => Eager evaluation

let seqSum = benchmark "Seq.sum" (fun () ->
    seq { 1..1000000 } |> Seq.sum
)                        // => Lazy evaluation

printfn "List sum: %d" listSum
printfn "Seq sum: %d" seqSum

// Memory profiling pattern
let measureMemory name f =
                         // => Measure GC allocations
    GC.Collect()         // => Force GC before measurement
    GC.WaitForPendingFinalizers()
    let before = GC.GetTotalMemory(true)
    let result = f()
    let after = GC.GetTotalMemory(true)
    printfn "%s allocated: %d bytes" name (after - before)
    result

let listAlloc = measureMemory "List allocation" (fun () ->
    [1..10000] |> List.map (fun x -> x * 2)
)

let seqAlloc = measureMemory "Seq allocation" (fun () ->
    seq { 1..10000 } |> Seq.map (fun x -> x * 2) |> Seq.toList
)

// Performance-critical patterns
// 1. Avoid boxing/unboxing
let boxedSum = [1..100] |> List.map box |> List.sumBy (fun x -> x :?> int)
                         // => Boxing creates heap allocations

let directSum = [1..100] |> List.sum
                         // => No boxing, direct primitive operations

// 2. Use Array for hot paths
let arraySum = [|1..1000000|] |> Array.sum
                         // => Array is faster than List for large data

// 3. Memoization for expensive computations
let memoize f =
                         // => Cache function results
    let cache = System.Collections.Generic.Dictionary<_, _>()
    fun x ->
        match cache.TryGetValue(x) with
        | true, result -> result
        | false, _ ->
            let result = f x
            cache.[x] <- result
            result

let rec fib n =
    if n <= 1 then n
    else fib (n - 1) + fib (n - 2)

let fibMemo = memoize fib

let slow = benchmark "Fibonacci recursive" (fun () -> fib 30)
let fast = benchmark "Fibonacci memoized" (fun () -> fibMemo 30)

printfn "Slow fib: %d" slow
printfn "Fast fib: %d" fast

Key Takeaway: Profile before optimizing using Stopwatch for timing and GC.GetTotalMemory for allocation tracking. Common optimizations: avoid boxing, use arrays for hot paths, memoize expensive computations.

Why It Matters: Profiling reveals actual bottlenecks vs assumed ones. Production systems often discover performance bottlenecks in unexpected places—profiling prevents premature optimization while identifying real issues like unnecessary allocations causing GC pauses.

Example 84: Concurrent Collections and Lock-Free Data Structures

F# uses .NET concurrent collections for thread-safe operations without explicit locks.

// Example 84: Concurrent Collections and Lock-Free Data Structures
open System.Collections.Concurrent
open System.Threading.Tasks

// ConcurrentBag: unordered, thread-safe collection
let bag = ConcurrentBag<int>()

Parallel.For(0, 100, fun i ->
                         // => 100 parallel tasks
    bag.Add(i)           // => Thread-safe add
) |> ignore

printfn "Bag count: %d" bag.Count
                         // => Outputs: Bag count: 100

// ConcurrentDictionary: thread-safe key-value store
let dict = ConcurrentDictionary<string, int>()

let increment key =
                         // => Atomic increment
    dict.AddOrUpdate(key, 1, fun k v -> v + 1)

Parallel.For(0, 1000, fun i ->
    increment "counter" |> ignore
) |> ignore

printfn "Counter value: %d" dict.["counter"]
                         // => Outputs: Counter value: 1000
                         // => All 1000 increments successful (no race conditions)

// ConcurrentQueue: thread-safe FIFO queue
let queue = ConcurrentQueue<string>()

Parallel.For(0, 10, fun i ->
    queue.Enqueue(sprintf "Item %d" i)
) |> ignore

let mutable item = ""
while queue.TryDequeue(&item) do
    printfn "Dequeued: %s" item

// Lock-free counter using Interlocked
open System.Threading

let mutable counter = 0

Parallel.For(0, 10000, fun i ->
    Interlocked.Increment(&counter) |> ignore
                         // => Atomic increment (lock-free)
) |> ignore

printfn "Lock-free counter: %d" counter
                         // => Outputs: Lock-free counter: 10000

// Compare with unsafe increment
let mutable unsafeCounter = 0

Parallel.For(0, 10000, fun i ->
    unsafeCounter <- unsafeCounter + 1
                         // => RACE CONDITION: read-modify-write not atomic
) |> ignore

printfn "Unsafe counter: %d" unsafeCounter
                         // => Outputs: Unsafe counter: < 10000
                         // => Lost updates due to race conditions

Key Takeaway: Use ConcurrentBag, ConcurrentDictionary, and ConcurrentQueue for thread-safe collections. Use Interlocked for lock-free atomic operations on primitives.

Why It Matters: Concurrent collections eliminate explicit locking complexity while maintaining thread safety. Web services use ConcurrentDictionary for request caches shared across threads, achieving significant throughput improvements over lock-based dictionaries without deadlock risk.

Example 85: Advanced Async Patterns - Cancellation and Timeouts

Async workflows support cancellation tokens and timeouts for robust async programming.

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC
graph TD
    A[Start Async]:::blue --> B{Cancelled?}:::orange
    B -->|No| C[Continue Work]:::teal
    B -->|Yes| D[Clean Cancel]:::purple
    C --> E{Timeout?}:::orange
    E -->|No| F[Complete]:::teal
    E -->|Yes| D

    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#fff
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#CC78BC,stroke:#000,color:#fff
    style E fill:#DE8F05,stroke:#000,color:#fff
    style F fill:#029E73,stroke:#000,color:#fff

Code:

// Example 85: Advanced Async Patterns - Cancellation and Timeouts
open System
open System.Threading

// Cancellable async operation
let longRunningOperation (ct: CancellationToken) = async {
                         // => Accept cancellation token
    for i in 1 .. 10 do
        do! Async.Sleep 500
                         // => Simulate work
        ct.ThrowIfCancellationRequested()
                         // => Check for cancellation
        printfn "Step %d completed" i
}

// Execute with cancellation
let cts = new CancellationTokenSource()

let task = Async.Start(longRunningOperation cts.Token)
                         // => Start async with cancellation token

Thread.Sleep(2500)       // => Wait 2.5 seconds
cts.Cancel()             // => Cancel operation
                         // => Operation stops cleanly

Thread.Sleep(500)
printfn "Operation cancelled"

// Timeout pattern
let withTimeout (milliseconds: int) (operation: Async<'a>) : Async<'a option> =
    async {
        let! result = Async.Choice [
                         // => Race multiple async operations
            async {
                let! value = operation
                return Some value
            }
            async {
                do! Async.Sleep milliseconds
                return None  // => Timeout result
            }
        ]
        return result
    }

let slowOperation = async {
    do! Async.Sleep 3000 // => 3 second operation
    return "Completed"
}

let fastOperation = async {
    do! Async.Sleep 500  // => 500ms operation
    return "Completed"
}

// Timeout after 1 second
let slowResult = withTimeout 1000 slowOperation |> Async.RunSynchronously
                         // => slowResult is None (timed out)

let fastResult = withTimeout 1000 fastOperation |> Async.RunSynchronously
                         // => fastResult is Some "Completed"

match slowResult with
| Some value -> printfn "Slow: %s" value
| None -> printfn "Slow operation timed out"

match fastResult with
| Some value -> printfn "Fast: %s" value
| None -> printfn "Fast operation timed out"

// Parallel with timeout
let parallelWithTimeout operations timeout = async {
    let! results = operations
                   |> List.map (withTimeout timeout)
                   |> Async.Parallel
                         // => Run all with timeout in parallel
    return results
}

let ops = [
    async { do! Async.Sleep 500; return 1 }
    async { do! Async.Sleep 2000; return 2 }  // => Will timeout
    async { do! Async.Sleep 300; return 3 }
]

let results = parallelWithTimeout ops 1000 |> Async.RunSynchronously

for result in results do
    match result with
    | Some value -> printfn "Result: %d" value
    | None -> printfn "Result: Timed out"

Key Takeaway: Async workflows integrate cancellation tokens for cooperative cancellation and support timeout patterns through Async.Choice, enabling robust async programming with graceful degradation.

Why It Matters: Cancellation prevents resource leaks when requests are abandoned. Microservices cancel downstream requests when clients disconnect, preventing wasted computation and database queries for results no one will receive, improving throughput during traffic spikes by freeing resources immediately.


Summary

These 25 advanced examples (Examples 61-85) cover 75-95% of F#’s features, building on beginner and intermediate fundamentals with expert patterns:

Concurrency & Parallelism:

  • MailboxProcessor agents for actor-based concurrency
  • Async.Parallel for parallel I/O
  • Concurrent collections and lock-free operations

Type System Mastery:

  • SRTP for compile-time polymorphism
  • Phantom types for type-state patterns
  • Units of measure for dimensional analysis
  • GADTs emulation for type-safe evaluation

Metaprogramming:

  • Quotations for code as data
  • Reflection for runtime type inspection
  • Custom computation expression builders
  • Code generation and type providers

Domain Modeling:

  • Make illegal states unrepresentable
  • Event sourcing and CQRS patterns
  • Functional dependency injection

Performance & Interop:

  • Struct vs class optimization
  • Profiling and benchmarking
  • C# interoperability
  • Parser combinators

Advanced Patterns:

  • Parameterized active patterns
  • Type extensions
  • Custom operators
  • Async cancellation and timeouts

You now have comprehensive F# knowledge spanning 85 examples from Hello World to advanced functional architecture, covering 95% of language features for production F# development.

Last updated