F# Interview Questions with Answers
Most Asked F# Interview Questions for Software Engineer Roles
Introduction
This page provides a complete collection of F# Interview Questions and Answers designed for software engineers, functional programmers, .NET developers, and candidates preparing for technical interviews. F# is a functional-first programming language that runs on .NET. It combines functional programming with object-oriented and imperative paradigms, offering a powerful type system, pattern matching, type inference, and seamless interoperability with other .NET languages. This interview guide covers beginner, intermediate, and advanced F# concepts including syntax, data types, pattern matching, recursion, modules, functions, discriminated unions, async workflows, type providers, metaprogramming, concurrency, domain modeling, and real-world application development.
Why F#?
- Functional-first – encourages pure functional programming with immutability
- Strong type inference – reduces boilerplate while maintaining safety
- Pattern matching – expressive and concise data manipulation
- Seamless .NET integration – interoperates with C# and other .NET languages
- Asynchronous and parallel programming – built-in support for async and parallelism
- Growing ecosystem – active community, libraries, and tooling
Most Asked F# Interview Questions
F# is a functional-first programming language that runs on .NET. It combines functional programming with object-oriented and imperative programming paradigms.
- Functional-first: Functional programming is the primary paradigm
- Type Inference: Strong type system with automatic type inference
- Immutable by Default: Promotes safer code with immutability
- Concurrency: Built-in support for asynchronous and parallel programming
- Interoperability: Seamless with other .NET languages
// Hello World in F#
open System
[<EntryPoint>]
let main argv =
printfn "Hello, World!"
0F# provides a rich set of data types including primitive types, tuples, lists, arrays, records, and discriminated unions. All types are immutable by default.
- Primitive Types: int, float, string, bool, char, decimal
- Tuples:
(1, "Hello", 3.14) - Lists:
[1; 2; 3; 4; 5] - Arrays:
[|1; 2; 3; 4; 5|] - Records: Named fields with structural equality
// Data Types in F#
open System
let age = 25
let salary = 50000.50
let pi = 3.14159265358979
let grade = 'A'
let isActive = true
let name = "Alice"
let price = 99.99m
printfn $"Age: {age}"
printfn $"Salary: {salary}"
printfn $"Pi: {pi}"
printfn $"Grade: {grade}"
printfn $"Active: {isActive}"
printfn $"Name: {name}"
printfn $"Price: {price}"F# uses let for immutable bindings and let mutable for mutable variables. Constants are defined using the let keyword with literal values.
- Immutable Binding:
let x = 10 - Mutable Binding:
let mutable x = 10 - Type Inference: Types are automatically inferred
- Type Annotations:
let x: int = 10 - Module-level Bindings:
module MyModule = let x = 10
// Variables and Constants in F#
let x = 10
let pi = 3.14159
let val = 3.14
let str = "Hello"
let mutable counter = 0
printfn $"x = {x}"
printfn $"pi = {pi}"
printfn $"val = {val}"
printfn $"str = {str}"
printfn $"counter = {counter}"Pattern Matching is a powerful feature that allows you to destructure and match values against patterns. It's used extensively in F# for control flow and data extraction.
- Match Expression:
match x with | pattern -> result - Tuple Patterns:
(x, y) - List Patterns:
head :: tail - Record Patterns:
{ Name = name; Age = age } - Active Patterns: Custom pattern matching
// Pattern Matching in F#
let describeNumber x =
match x with
| 0 -> "Zero"
| 1 -> "One"
| 2 -> "Two"
| _ -> "Other"
let describeList lst =
match lst with
| [] -> "Empty"
| [x] -> $"One element: {x}"
| [x; y] -> $"Two elements: {x} and {y}"
| head::tail -> $"Head: {head}, Tail: {tail}"
printfn $"{describeNumber 5}"
printfn $"{describeList [1; 2; 3]}"
// Tuple pattern matching
let getCoordinates point =
match point with
| (0, 0) -> "Origin"
| (x, 0) -> $"X-axis: {x}"
| (0, y) -> $"Y-axis: {y}"
| (x, y) -> $"Point: ({x}, {y})"
printfn $"{getCoordinates (3, 4)}"Modules are used to group related functions and values. Functions are defined using the let keyword and are first-class citizens in F#.
- Module Definition:
module MyModule = ... - Function Definition:
let add x y = x + y - Recursive Functions:
let rec factorial n = ... - Higher-order Functions: Functions that take functions as parameters
- Partial Application:
let add5 = add 5
// Modules and Functions in F#
module Math
let add a b = a + b
let subtract a b = a - b
let multiply a b = a * b
let divide a b = a / b
// Partial application
let add5 = add 5
let result = add5 3
// Recursive function
let rec factorial n =
if n <= 1 then 1
else n * factorial (n - 1)
// Higher-order function
let applyTwice f x = f (f x)
printfn $"{add 10 20}"
printfn $"{multiply 5 4}"
printfn $"{factorial 5}"
printfn $"{applyTwice (fun x -> x * 2) 3}"Recursion is a technique where a function calls itself. F# uses recursion extensively and optimizes tail-recursive functions to prevent stack overflow.
- Recursive Function:
let rec factorial n = ... - Tail Recursion: Optimized to avoid stack overflow
- Base Case: Stopping condition
- Recursive Case: Self-call with smaller input
- Accumulator Pattern: Efficient recursion with accumulators
// Recursion in F#
open System
// Factorial
let rec factorial n =
match n with
| 0 | 1 -> 1
| _ -> n * factorial (n - 1)
// Fibonacci
let rec fibonacci n =
match n with
| 0 -> 0
| 1 -> 1
| _ -> fibonacci (n - 1) + fibonacci (n - 2)
// Tail recursion
let rec sumList lst acc =
match lst with
| [] -> acc
| head::tail -> sumList tail (acc + head)
// List processing with recursion
let rec mapList f lst =
match lst with
| [] -> []
| head::tail -> (f head) :: mapList f tail
let rec filterList pred lst =
match lst with
| [] -> []
| head::tail when pred head -> head :: filterList pred tail
| _::tail -> filterList pred tail
printfn $"Factorial 5: {factorial 5}"
printfn $"Fibonacci 8: {fibonacci 8}"
printfn $"Sum [1..5]: {sumList [1..5] 0}"
printfn $"Map [1..5]: {mapList (fun x -> x * 2) [1..5]}"
printfn $"Filter [1..10]: {filterList (fun x -> x % 2 = 0) [1..10]}"F# provides immutable collections like List, Array, Seq, Map, and Set. These collections are functional and support operations like map, filter, and fold.
- List:
[1; 2; 3; 4; 5] - Array:
[|1; 2; 3; 4; 5|] - Seq:
seq { 1 .. 10 } - Map:
Map.ofList [("key1", 1); ("key2", 2)] - Set:
Set.ofList [1; 2; 3; 4; 5]
// List and Collection Operations in F#
let list = [5; 1; 8; 3; 9; 2; 7]
// Map - transform
let doubled = List.map (fun x -> x * 2) list
printfn $"Doubled: {doubled}"
// Filter - select
let evens = List.filter (fun x -> x % 2 = 0) list
printfn $"Evens: {evens}"
// Reduce - aggregate
let sum = List.fold (fun acc x -> acc + x) 0 list
printfn $"Sum: {sum}"
// Sort
let sorted = List.sort list
printfn $"Sorted: {sorted}"
// Pipe operator
let result =
list
|> List.map (fun x -> x * 2)
|> List.filter (fun x -> x > 10)
|> List.sum
printfn $"Result: {result}"
// Seq (lazy sequences)
let seqExample = seq { 1 .. 10 }
printfn $"Seq: {seqExample}"
// Array
let arr = [| 1; 2; 3; 4; 5 |]
printfn $"Array: {arr}"Discriminated Unions are a powerful feature for defining types that can be one of several cases. Each case can have different data associated with it.
- Definition:
type Shape = Circle of float | Rectangle of float * float - Pattern Matching: Match on union cases
- Single Case:
type Email = Email of string - Multiple Cases:
type Color = Red | Green | Blue - Recursive Unions:
type Tree = Leaf of int | Node of Tree * Tree
// Discriminated Unions in F#
type Shape =
| Circle of radius: float
| Rectangle of width: float * height: float
| Square of side: float
| Triangle of base': float * height: float
let area shape =
match shape with
| Circle r -> Math.PI * r * r
| Rectangle (w, h) -> w * h
| Square s -> s * s
| Triangle (b, h) -> 0.5 * b * h
let perimeter shape =
match shape with
| Circle r -> 2.0 * Math.PI * r
| Rectangle (w, h) -> 2.0 * (w + h)
| Square s -> 4.0 * s
| Triangle (b, h) -> b + 2.0 * sqrt (b * b / 4.0 + h * h)
let circle = Circle 5.0
let rect = Rectangle (4.0, 6.0)
printfn $"Circle area: {area circle}"
printfn $"Rectangle area: {area rect}"
printfn $"Circle perimeter: {perimeter circle}"The Option type represents a value that may or may not exist. It's a safer alternative to null and is used extensively in F# for handling optional values.
- Some:
Some value - None:
None - Pattern Matching:
match opt with Some v -> ... | None -> ... - Option Functions:
Option.map,Option.bind,Option.defaultValue - Computation Expressions:
option { ... }
// Option Type in F#
let tryDivide x y =
if y = 0 then None
else Some (x / y)
let parseNumber str =
match Int32.TryParse str with
| (true, n) -> Some n
| _ -> None
let result1 = tryDivide 10 2
let result2 = tryDivide 10 0
match result1 with
| Some v -> printfn $"Result: {v}"
| None -> printfn "Division failed"
match result2 with
| Some v -> printfn $"Result: {v}"
| None -> printfn "Division failed"
// Option functions
let optionValue = Some 42
let defaultValue = Option.defaultValue 0 optionValue
printfn $"Default value: {defaultValue}"
let mapped = Option.map (fun x -> x * 2) optionValue
printfn $"Mapped: {mapped}"The Result type represents a value that can be either a success (Ok) or an error (Error). It's used for error handling without exceptions.
- Ok:
Ok value - Error:
Error errorValue - Pattern Matching:
match result with Ok v -> ... | Error e -> ... - Computation Expressions:
result { ... } - Error Handling: Functional error handling
// Result Type in F#
type Error =
| DivisionByZero
| InvalidInput
let safeDivide x y =
if y = 0 then Error DivisionByZero
else Ok (x / y)
let safeParse str =
match Int32.TryParse str with
| (true, n) -> Ok n
| _ -> Error InvalidInput
let processData str =
result {
let! num = safeParse str
let! result = safeDivide num 2
return result
}
match processData "10" with
| Ok v -> printfn $"Success: {v}"
| Error DivisionByZero -> printfn "Division by zero"
| Error InvalidInput -> printfn "Invalid input"Record Types are immutable data structures with named fields. They provide structural equality and are used for modeling data in a functional style.
- Definition:
type Person = { Name: string; Age: int } - Creation:
{ Name = "Alice"; Age = 25 } - Copy and Update:
{ person with Age = 26 } - Structural Equality: Two records with same values are equal
- Methods: Records can have methods
// Record Types in F#
type Person = {
Name: string
Age: int
Email: string option
}
let person1 = { Name = "Alice"; Age = 25; Email = Some "alice@email.com" }
let person2 = { Name = "Bob"; Age = 30; Email = None }
let printPerson person =
printfn $"Name: {person.Name}, Age: {person.Age}"
match person.Email with
| Some email -> printfn $"Email: {email}"
| None -> printfn "No email"
// Record with methods
type Point = {
X: float
Y: float
}
with
member this.DistanceFromOrigin =
sqrt (this.X * this.X + this.Y * this.Y)
member this.Add(other: Point) =
{ X = this.X + other.X; Y = this.Y + other.Y }
let p1 = { X = 3.0; Y = 4.0 }
let p2 = { X = 1.0; Y = 2.0 }
printfn $"Distance: {p1.DistanceFromOrigin}"
printfn $"Sum: {p1.Add p2}"F# supports object-oriented programming with classes, objects, and interfaces. Classes can have properties, methods, and events.
- Class Definition:
type Car(brand: string) = ... - Properties:
member this.Brand = brand - Methods:
member this.Display() = ... - Inheritance:
type ElectricCar(...) = inherit Car(...) - Interfaces: Implement interfaces with
interface ... with
// Classes and Objects in F#
open System
type Car(brand: string, year: int, price: float) =
// Member variables
let mutable currentSpeed = 0
// Properties
member this.Brand = brand
member this.Year = year
member this.Price = price
// Methods
member this.Display() =
printfn $"Brand: {brand}, Year: {year}, Price: ${price}"
member this.Accelerate() =
currentSpeed <- currentSpeed + 10
printfn $"Current speed: {currentSpeed}"
member this.Brake() =
currentSpeed <- max 0 (currentSpeed - 10)
printfn $"Current speed: {currentSpeed}"
// Inheritance
type ElectricCar(brand: string, year: int, price: float, battery: int) =
inherit Car(brand, year, price)
member this.Battery = battery
member this.Charge() =
printfn $"Charging battery: {battery}%"
let car1 = Car("Toyota", 2022, 25000.0)
let tesla = ElectricCar("Tesla", 2023, 55000.0, 85)
car1.Display()
car1.Accelerate()
tesla.Display()
tesla.Charge()Interfaces in F# define contracts that classes and records can implement. They enable polymorphism and abstraction.
- Interface Definition:
type IShape = abstract Area: float - Implementation:
interface IShape with member this.Area = ... - Object Expression:
{ new IShape with member this.Area = ... } - Interface Inheritance: Interfaces can inherit from other interfaces
- Use Cases: Polymorphism, dependency injection
// Interfaces in F#
open System
// Interface definition
type IShape =
abstract member Area: float
abstract member Perimeter: float
abstract member Draw: unit -> unit
// Interface implementation using object expression
let createCircle radius =
{ new IShape with
member this.Area = Math.PI * radius * radius
member this.Perimeter = 2.0 * Math.PI * radius
member this.Draw() =
printfn $"Drawing Circle with radius {radius}"
}
// Interface implementation using class
type Circle(radius: float) =
interface IShape with
member this.Area = Math.PI * radius * radius
member this.Perimeter = 2.0 * Math.PI * radius
member this.Draw() =
printfn $"Drawing Circle with radius {radius}"
type Rectangle(width: float, height: float) =
interface IShape with
member this.Area = width * height
member this.Perimeter = 2.0 * (width + height)
member this.Draw() =
printfn $"Drawing Rectangle {width}x{height}"
// Interface inheritance
type IShapeWithColor =
inherit IShape
abstract member Color: string
type ColoredCircle(radius: float, color: string) =
interface IShapeWithColor with
member this.Area = Math.PI * radius * radius
member this.Perimeter = 2.0 * Math.PI * radius
member this.Draw() =
printfn $"Drawing Circle with radius {radius}"
member this.Color = color
// Using interfaces
let shapes: IShape list = [
Circle(5.0) :> IShape
Rectangle(4.0, 6.0) :> IShape
createCircle 3.0 :> IShape
]
for shape in shapes do
shape.Draw()
printfn $"Area: {shape.Area:F2}"
printfn $"Perimeter: {shape.Perimeter:F2}"
printfn ""
// Interface with object expression
let printableCircle radius =
{ new IShape with
member this.Area = Math.PI * radius * radius
member this.Perimeter = 2.0 * Math.PI * radius
member this.Draw() =
printfn $"Printable Circle with radius {radius}"
}
printableCircle 4.0 |> fun shape ->
shape.Draw()
printfn $"Area: {shape.Area:F2}"F# supports exception handling with try/with and try/finally blocks. It encourages functional error handling using the Result type.
- try/with:
try ... with | ex -> ... - try/finally:
try ... finally ... - Raising Exceptions:
raise (new Exception("message")) - Pattern Matching: Match on exception types
- Functional Approach: Use Result type instead of exceptions
// Exception Handling in F#
open System
let divide x y =
if y = 0 then
raise (DivideByZeroException("Division by zero"))
x / y
let safeDivide x y =
try
Ok (divide x y)
with
| :? DivideByZeroException as ex ->
Error ex.Message
| ex ->
Error ex.Message
let processNumbers () =
try
printfn "Enter first number:"
let x = Console.ReadLine() |> int
printfn "Enter second number:"
let y = Console.ReadLine() |> int
let result = divide x y
printfn $"Result: {result}"
with
| :? FormatException ->
printfn "Invalid input format"
| :? DivideByZeroException ->
printfn "Cannot divide by zero"
| ex ->
printfn $"Unexpected error: {ex.Message}"
// try-with with pattern matching
let handleError ex =
match ex with
| :? DivideByZeroException -> "Division by zero"
| :? FormatException -> "Invalid format"
| _ -> "Unknown error"
printfn $"{safeDivide 10 2}"
printfn $"{safeDivide 10 0}"F# provides async workflows and task expressions for asynchronous programming. Async is the traditional F# approach, while Task integrates with .NET Task-based async.
- Async Workflow:
async { ... } - Async.AwaitTask: Convert Task to Async
- Task Expression:
task { ... } - Parallel Async:
Async.Parallel - Cancellation: Built-in cancellation support
// Async and Task in F#
open System
open System.Threading.Tasks
// Async workflow
let asyncOperation delay =
async {
printfn $"Starting operation with delay {delay}ms"
do! Async.Sleep delay
printfn $"Completed operation with delay {delay}ms"
return delay * 2
}
// Run async operations
let runAsyncOperations () =
async {
let tasks = [for i in 1..5 -> asyncOperation (i * 200)]
let! results = Async.Parallel tasks
printfn $"Results: {results}"
}
|> Async.RunSynchronously
// Async with cancellation
let cancellableOperation token =
async {
for i in 1..10 do
if token.IsCancellationRequested then
printfn "Operation cancelled"
return!
printfn $"Processing: {i}"
do! Async.Sleep 200
printfn "Operation completed"
}
// Task-based async
let taskOperation delay =
task {
printfn $"Task started with delay {delay}ms"
do! Task.Delay delay
printfn $"Task completed with delay {delay}ms"
return delay * 2
}
let runTasks () =
task {
let! result1 = taskOperation 1000
let! result2 = taskOperation 2000
return result1 + result2
}
|> Task.Run
|> Async.AwaitTask
|> Async.RunSynchronouslySequence Expressions (seq) provide a way to create and process sequences lazily. They are useful for working with large or infinite collections.
- Basic Sequence:
seq { 1 .. 10 } - Comprehensions:
seq { for i in 1 .. 10 do yield i * 2 } - Lazy Evaluation: Elements are computed on demand
- Infinite Sequences:
Seq.initInfinite - Sequence Functions:
Seq.map,Seq.filter,Seq.take
// Sequence Expressions in F#
// Basic sequence
let numbers = seq { 1 .. 10 }
printfn $"Numbers: {numbers}"
// Sequence with step
let evenNumbers = seq { 2 .. 2 .. 20 }
printfn $"Even numbers: {evenNumbers}"
// Sequence with condition
let oddNumbers = seq {
for i in 1 .. 20 do
if i % 2 = 1 then
yield i
}
printfn $"Odd numbers: {oddNumbers}"
// Nested loops
let pairs = seq {
for i in 1 .. 3 do
for j in 1 .. 3 do
yield (i, j)
}
printfn $"Pairs: {pairs}"
// Infinite sequence
let infinite = Seq.initInfinite (fun i -> i * 2)
let first10 = Seq.take 10 infinite
printfn $"First 10: {first10}"
// Sequence processing
let processed =
seq { 1 .. 100 }
|> Seq.filter (fun x -> x % 2 = 0)
|> Seq.map (fun x -> x * x)
|> Seq.take 10
printfn $"Processed: {processed}"Pipe Operator (|>) passes the result of one function to the next. Composition (>>, <<) combines functions into a single function.
- Pipe Operator:
x |> f |> g - Forward Composition:
f >> g - Backward Composition:
f << g - Functional Pipelines: Chain operations together
- Readability: Write code in a natural left-to-right flow
// Pipe and Composition in F#
open System
// Pipe operator (|>)
let result =
5
|> (fun x -> x * 2)
|> (fun x -> x + 10)
|> (fun x -> x * x)
printfn $"Result: {result}"
// Forward composition (>>)
let add10 = (+) 10
let multiply2 = (*) 2
let square = fun x -> x * x
let processFunction = add10 >> multiply2 >> square
printfn $"Composed: {processFunction 5}"
// Backward composition (<<)
let processFunction2 = square << multiply2 << add10
printfn $"Backward composed: {processFunction2 5}"
// Practical example with list processing
let numbers = [1; 2; 3; 4; 5]
let result2 =
numbers
|> List.filter (fun x -> x % 2 = 0)
|> List.map (fun x -> x * 2)
|> List.sum
printfn $"Pipeline result: {result2}"Computation Expressions provide a way to write computations with custom control flow. They are used for async, option, result, and custom workflows.
- Option Workflow:
option { ... } - Result Workflow:
result { ... } - Async Workflow:
async { ... } - Custom Workflows: Define your own computation builder
- Bind:
let! x = ...for sequential composition
// Computation Expressions in F#
// Option computation
let divide x y =
if y = 0 then None
else Some (x / y)
let computeResult x y z =
option {
let! a = divide x y
let! b = divide a z
return b
}
printfn $"{computeResult 10 2 5}" // Some 1
printfn $"{computeResult 10 0 5}" // None
// Result computation
type Error =
| DivisionByZero
| InvalidInput
let safeDiv x y =
if y = 0 then Error DivisionByZero
else Ok (x / y)
let computeResult2 x y =
result {
let! a = safeDiv x y
let! b = safeDiv a 2
return b
}
printfn $"{computeResult2 10 2}"
printfn $"{computeResult2 10 0}"
// Async computation
let asyncAdd x y =
async {
do! Async.Sleep 100
return x + y
}
let asyncCompute x y z =
async {
let! a = asyncAdd x y
let! b = asyncAdd a z
return b
}
async {
let! result = asyncCompute 1 2 3
printfn $"Async result: {result}"
} |> Async.RunSynchronouslyActive Patterns allow you to create custom pattern matching logic. They can be used to partition input data into different cases for pattern matching.
- Single Case:
let (|Even|Odd|) n = ... - Multi-Case:
let (|Positive|Negative|Zero|) n = ... - Parameterized:
let (|DivisibleBy|_|) divisor n = ... - Partial Active Patterns: Return
SomeorNone - Use Cases: Complex pattern matching, parsing
// Active Patterns in F#
// Basic active pattern
let (|Even|Odd|) n =
if n % 2 = 0 then Even else Odd
let describeNumber n =
match n with
| Even -> $"{n} is even"
| Odd -> $"{n} is odd"
printfn $"{describeNumber 5}"
printfn $"{describeNumber 8}"
// Parameterized active pattern
let (|DivisibleBy|_|) divisor n =
if n % divisor = 0 then Some () else None
let fizzBuzz n =
match n with
| DivisibleBy 15 -> "FizzBuzz"
| DivisibleBy 3 -> "Fizz"
| DivisibleBy 5 -> "Buzz"
| _ -> string n
for i in 1..20 do
printf $"{fizzBuzz i} "
printfn ""
// Multi-case active pattern
let (|Positive|Negative|Zero|) n =
if n > 0 then Positive
elif n < 0 then Negative
else Zero
let sign n =
match n with
| Positive -> "Positive"
| Negative -> "Negative"
| Zero -> "Zero"
printfn $"{sign 10}"
printfn $"{sign -5}"
printfn $"{sign 0}"Units of Measure add compile-time type safety for physical quantities. They help prevent errors in scientific and engineering calculations.
- Definition:
[<Measure>] type m - Usage:
let distance = 10.0<m> - Derived Units:
m / s,kg * m / s^2 - Type Safety: Prevent mixing incompatible units
- Conversion: Define conversion functions
// Units of Measure in F#
[<Measure>] type m
[<Measure>] type s
[<Measure>] type kg
[<Measure>] type N = kg * m / s^2
let distance = 10.0<m>
let time = 2.0<s>
let velocity = distance / time // m/s
let acceleration = velocity / time // m/s^2
printfn $"Distance: {distance}"
printfn $"Time: {time}"
printfn $"Velocity: {velocity}"
printfn $"Acceleration: {acceleration}"
// Conversion functions
let kmToM (km: float) = km * 1000.0<m>
let mToKm (m: float<m>) = m / 1000.0
// Temperature with units
[<Measure>] type C
[<Measure>] type F
let celsiusToFahrenheit (c: float<C>) =
(c * 9.0<F> / 5.0<C>) + 32.0<F>
let tempC = 25.0<C>
let tempF = celsiusToFahrenheit tempC
printfn $"{tempC}°C = {tempF}°F"Type Providers generate types at compile time based on external data sources. They enable strongly-typed access to data like JSON, XML, databases, and web services.
- JSON Provider:
JsonProvider - XML Provider:
XmlProvider - SQL Provider:
SqlDataProvider - Database Access: Strongly-typed database queries
- Compile-time Safety: Errors detected at compile time
// Type Providers in F#
open FSharp.Data
// JSON Type Provider
type Person = JsonProvider<"""
{
"name": "Alice",
"age": 25,
"email": "alice@email.com"
}
""">
let jsonData = """
{
"name": "Bob",
"age": 30,
"email": "bob@email.com"
}
"""
let person = Person.Parse(jsonData)
printfn $"Name: {person.Name}"
printfn $"Age: {person.Age}"
printfn $"Email: {person.Email}"
// XML Type Provider
type Book = XmlProvider<"""
<book>
<title>F# Programming</title>
<author>John Doe</author>
<year>2023</year>
</book>
""">
let xml = """
<book>
<title>F# in Action</title>
<author>Jane Smith</author>
<year>2024</year>
</book>
"""
let book = Book.Parse(xml)
printfn $"Title: {book.Title}"
printfn $"Author: {book.Author}"
printfn $"Year: {book.Year}"Quotations represent F# code as data. They allow you to analyze and manipulate code at runtime, enabling metaprogramming and DSLs.
- Quotation Syntax:
<@ 1 + 2 @> - Code Analysis: Analyze expression structure
- Code Generation: Generate code at runtime
- Splicing:
%operator for combining quotations - Use Cases: DSLs, code generation, expression trees
// Quotations in F#
open Microsoft.FSharp.Quotations
// Basic quotations
let expr = <@ 1 + 2 * 3 @>
printfn $"Expression: {expr}"
// Quotation with variables
let x = 5
let expr2 = <@ x * 2 @>
printfn $"Expression2: {expr2}"
// Quotation as function
let add = <@ fun x y -> x + y @>
printfn $"Add: {add}"
// Evaluating quotations
let eval q =
match q with
| Patterns.Call(None, meth, [left; right]) ->
printfn $"Call: {meth.Name}"
| _ -> printfn "Other"
eval <@ 1 + 2 @>
// Quotation with let bindings
let expr3 = <@ let x = 5 in x * 2 @>
printfn $"Let binding: {expr3}"
// Quotation splicing
let multiplyByTwo n = <@ n * 2 @>
let resultExpr = multiplyByTwo <@ 5 @>
printfn $"Spliced: {resultExpr}"Reflection in F# allows you to inspect types, properties, and methods at runtime. It's useful for dynamic programming and serialization.
- Type Inspection:
typeof<'T> - Property Access:
FSharpType.GetRecordFields - Method Invocation:
MethodInfo.Invoke - Union Reflection: Work with discriminated unions
- Use Cases: Serialization, testing, dynamic loading
// Reflection in F#
open System
open System.Reflection
// Reflection on types
let printTypeInfo (t: Type) =
printfn $"Type: {t.Name}"
printfn "Properties:"
for prop in t.GetProperties() do
printfn $" {prop.Name}: {prop.PropertyType.Name}"
printfn "Methods:"
for method in t.GetMethods() do
printfn $" {method.Name}"
type Person = { Name: string; Age: int } with
member this.Greet() =
printfn $"Hello, {this.Name}!"
printTypeInfo typeof<Person>
// Invoke method by name
let person = { Name = "Alice"; Age = 25 }
let method = typeof<Person>.GetMethod("Greet")
method.Invoke(person, null)
// Get assembly info
let assembly = Assembly.GetExecutingAssembly()
printfn $"Assembly: {assembly.FullName}"
printfn $"Location: {assembly.Location}"Type Extensions allow you to add new members to existing types. They provide a way to extend functionality without inheritance.
- Extension Method:
type System.String with member this.WordCount() = ... - Extension Property:
type System.Int32 with member this.IsEven = ... - Scope: Extensions are scoped to the module
- Use Cases: Adding utility methods to existing types
- Limitations: Cannot access private members
// Type Extensions in F#
// Extension methods
type System.String with
member this.WordCount() =
this.Split([|' '; ' '; '
'|], StringSplitOptions.RemoveEmptyEntries).Length
member this.ToTitleCase() =
if String.IsNullOrEmpty(this) then this
else this.[0].ToString().ToUpper() + this.Substring(1).ToLower()
// Extension properties
type System.Int32 with
member this.IsEven = this % 2 = 0
member this.IsOdd = this % 2 <> 0
// Extension for List
type List<'T> with
member this.Second() =
if this.Length >= 2 then Some this.[1]
else None
let text = "Hello World"
printfn $"Word count: {text.WordCount()}"
printfn $"Title case: {text.ToTitleCase()}"
printfn $"5 is even: {5.IsEven}"
printfn $"6 is odd: {6.IsOdd}"
let list = [1; 2; 3]
printfn $"Second element: {list.Second()}"MailboxProcessor is F#'s agent-based concurrency primitive. It processes messages asynchronously and maintains state safely.
- Creation:
MailboxProcessor.Start - Message Handling:
inbox.Receive() - State Management: Maintain state across messages
- Async Messages: Process messages asynchronously
- Use Cases: Actor model, stateful services
// MailboxProcessor in F#
open System
type Message =
| Increment
| Decrement
| GetCount of AsyncReplyChannel<int>
| Reset
let counter = MailboxProcessor.Start(fun inbox ->
let rec loop count =
async {
let! msg = inbox.Receive()
match msg with
| Increment ->
printfn $"Incremented: {count + 1}"
return! loop (count + 1)
| Decrement ->
printfn $"Decremented: {count - 1}"
return! loop (count - 1)
| GetCount replyChannel ->
replyChannel.Reply count
return! loop count
| Reset ->
printfn "Counter reset"
return! loop 0
}
loop 0
)
counter.Post(Increment)
counter.Post(Increment)
counter.Post(Decrement)
let count = counter.PostAndReply(GetCount)
printfn $"Current count: {count}"
counter.Post(Reset)
let newCount = counter.PostAndReply(GetCount)
printfn $"After reset: {newCount}"The Agent Pattern uses MailboxProcessor to create actors that process messages and maintain state. It's similar to the Actor model.
- Agent: Encapsulates state and behavior
- Message Passing: Agents communicate via messages
- State Isolation: Agents maintain their own state
- Concurrency: Agents process messages sequentially
- Use Cases: Stateful services, concurrent systems
// Agent Pattern in F#
open System
type AgentMessage<'T> =
| Post of 'T
| Get of AsyncReplyChannel<'T list>
| Clear
| Count of AsyncReplyChannel<int>
let createAgent () =
MailboxProcessor.Start(fun inbox ->
let rec loop items =
async {
let! msg = inbox.Receive()
match msg with
| Post item ->
return! loop (item :: items)
| Get replyChannel ->
replyChannel.Reply (List.rev items)
return! loop items
| Clear ->
return! loop []
| Count replyChannel ->
replyChannel.Reply items.Length
return! loop items
}
loop []
)
let agent = createAgent()
agent.Post(Post "Hello")
agent.Post(Post "World")
agent.Post(Post "F#")
let items = agent.PostAndReply(Get)
printfn $"Items: {items}"
let count = agent.PostAndReply(Count)
printfn $"Count: {count}"
agent.Post(Clear)
let empty = agent.PostAndReply(Get)
printfn $"After clear: {empty}"Event Handling in F# uses the Event module for functional event processing. Events can be filtered, mapped, and combined.
- Event Definition:
let event = Event<int>() - Event Trigger:
event.Trigger(value) - Event Subscription:
event.Add(fun value -> ...) - Event Operators:
Event.filter,Event.map,Event.merge - Use Cases: GUI applications, reactive programming
// Event Handling in F#
open System
// Event definition
type StockPrice = {
Symbol: string
Price: float
Timestamp: DateTime
}
type Stock(symbol: string, initialPrice: float) =
let mutable price = initialPrice
let priceChanged = Event<StockPrice>()
member this.Symbol = symbol
member this.Price = price
member this.UpdatePrice(newPrice: float) =
let oldPrice = price
price <- newPrice
priceChanged.Trigger({ Symbol = symbol; Price = newPrice; Timestamp = DateTime.Now })
printfn $"Price changed from {oldPrice} to {newPrice}"
member this.PriceChanged = priceChanged.Publish
// Event subscription
let stock = Stock("AAPL", 150.0)
stock.PriceChanged.Add(fun price ->
printfn $"Stock {price.Symbol}: ${price.Price} at {price.Timestamp}"
)
stock.UpdatePrice(155.0)
stock.UpdatePrice(160.0)
// Event with filter
stock.PriceChanged
|> Event.filter (fun p -> p.Price > 155.0)
|> Event.add (fun p ->
printfn $"Price above threshold: {p.Price}"
)
// Event with map
stock.PriceChanged
|> Event.map (fun p -> p.Price * 2.0)
|> Event.add (fun price ->
printfn $"Doubled price: {price}"
)Observables represent streams of data over time. They enable reactive programming and functional event processing.
- Observable:
Observable.interval - Operators:
Observable.map,Observable.filter,Observable.scan - Subscription:
observable.Subscribe(fun value -> ...) - Subjects:
Subjectfor creating observable streams - Use Cases: Reactive UI, streaming data
// Observable in F#
open System
open System.Collections.Generic
type Observable<'T>(initialState: 'T) =
let mutable state = initialState
let changed = Event<'T>()
member this.State
with get() = state
and set(value) =
state <- value
changed.Trigger(state)
member this.Changed = changed.Publish
// Observable usage
let obs = Observable(0)
// Subscribe
obs.Changed.Add(fun newState ->
printfn $"State changed to: {newState}"
)
obs.State <- 10
obs.State <- 20
// Observable with filter
obs.Changed
|> Observable.filter (fun s -> s > 15)
|> Observable.add (fun s ->
printfn $"Filtered state: {s}"
)
// Observable with map
obs.Changed
|> Observable.map (fun s -> s * 2)
|> Observable.add (fun s ->
printfn $"Mapped state: {s}"
)
// Observable with scan
let cumulative = Observable.scan (fun acc x -> acc + x) 0 obs.Changed
cumulative.Add(fun total ->
printfn $"Cumulative: {total}"
)Lazy Evaluation delays computation until the value is needed. It's useful for expensive operations and infinite data structures.
- Lazy Type:
Lazy<int> - Creation:
lazy (expensive()) - Forcing:
lazyValue.Value - Lazy Sequences:
LazyListfor lazy lists - Use Cases: Expensive computations, infinite sequences
// Lazy Evaluation in F#
open System
// Lazy values
let lazyValue = lazy (
printfn "Computing lazy value..."
42
)
printfn "Before lazy computation"
let result = lazyValue.Value
printfn $"Result: {result}"
// Lazy with delay
let expensiveComputation =
lazy (
printfn "Expensive computation..."
let result = [1..1000000] |> List.sum
result
)
printfn "Before expensive computation"
let sum = expensiveComputation.Value
printfn $"Sum: {sum}"
// Lazy sequences
let infiniteSeq = Seq.initInfinite (fun i -> i)
let first5 = infiniteSeq |> Seq.take 5 |> Seq.toList
printfn $"First 5: {first5}"
// Lazy list (F# PowerPack)
// let lazyList = LazyList.ofSeq [1..100]
// let first10 = lazyList |> LazyList.take 10 |> LazyList.toList
// Lazy with functions
let lazyMap f lst =
lazy (
lst |> List.map f
)
let lazyResult = lazyMap (fun x -> x * 2) [1; 2; 3; 4; 5]
printfn $"Lazy map result: {lazyResult.Value}"Memoization caches function results to avoid recomputation. It's useful for expensive functions that are called multiple times.
- Implementation: Use a dictionary for caching
- Function:
let memoize f = ... - Use Cases: Fibonacci, complex calculations
- Trade-offs: Memory usage vs performance
- Recursive Functions: Can be memoized for performance
// Memoization in F#
open System
// Memoization function
let memoize f =
let cache = System.Collections.Generic.Dictionary<_, _>()
fun x ->
match cache.TryGetValue(x) with
| true, v -> v
| false, _ ->
let v = f x
cache.Add(x, v)
v
// Expensive function
let expensiveFunction x =
printfn $"Computing for {x}..."
x * 2
let memoizedFunction = memoize expensiveFunction
printfn $"First call: {memoizedFunction 5}"
printfn $"Second call: {memoizedFunction 5}"
printfn $"Third call: {memoizedFunction 10}"
// Memoized Fibonacci
let rec fibonacci n =
match n with
| 0 | 1 -> n
| _ -> fibonacci (n - 1) + fibonacci (n - 2)
let memoizedFib =
let cache = System.Collections.Generic.Dictionary<_, _>()
fun n ->
match cache.TryGetValue(n) with
| true, v -> v
| false, _ ->
let v = fibonacci n
cache.Add(n, v)
v
printfn $"Memoized Fib 40: {memoizedFib 40}"
printfn $"Memoized Fib 40 again: {memoizedFib 40}"Currying transforms a function with multiple parameters into a series of functions each taking one parameter. Partial Application applies some but not all arguments.
- Curried Function:
let add x y = x + y - Partial Application:
let add5 = add 5 - Benefits: Function composition, code reuse
- Use Cases: Configuration, dependency injection
// Currying and Partial Application in F#
// Currying
let add x y = x + y
let add5 = add 5
let result1 = add5 3
// Partial application with pipe
let multiply x y = x * y
let double = multiply 2
let result2 = double 5
// Multiple partial applications
let divide x y = x / y
let divideBy2 = divide 2
let result3 = divideBy2 10
// Currying with tuples
let addTuple (x, y) = x + y
let result4 = addTuple (5, 3)
// Partial application with lambda
let applyTwice f x = f (f x)
let addThree = applyTwice (fun x -> x + 1)
let result5 = addThree 5
// Currying in practice
let numbers = [1..10]
let evens = numbers |> List.filter (fun x -> x % 2 = 0)
let doubled = numbers |> List.map (fun x -> x * 2)
// Function composition with currying
let add10 = (+) 10
let multiply2 = (*) 2
let processNumber = add10 >> multiply2
let result6 = processNumber 5Tail Call Optimization optimizes recursive functions by reusing the current stack frame, preventing stack overflow for deep recursion.
- Tail Position: Last operation in a function
- Accumulator: Pass accumulated result
- Tail Recursion:
let rec func acc = ... - Optimization: F# compiler optimizes tail calls
- Use Cases: Deep recursion, tree traversal
// Tail Call Optimization in F#
open System
// Tail recursion with accumulator
let rec factorialTail n acc =
if n <= 1 then acc
else factorialTail (n - 1) (n * acc)
let result1 = factorialTail 5 1
// Tail recursion with list processing
let rec sumListTail lst acc =
match lst with
| [] -> acc
| head::tail -> sumListTail tail (acc + head)
let result2 = sumListTail [1..1000000] 0
// Tail recursion with continuation
let rec factorialCont n cont =
if n <= 1 then cont 1
else factorialCont (n - 1) (fun x -> cont (n * x))
let result3 = factorialCont 5 id
// Non-tail recursive (stack overflow for large n)
let rec factorial n =
if n <= 1 then 1
else n * factorial (n - 1)
// Tail recursion for Fibonacci
let fibonacciTail n =
let rec fib a b count =
if count = n then a
else fib b (a + b) (count + 1)
fib 0 1 0
let result4 = fibonacciTail 40
// Tail recursion with continuation for Fibonacci
let rec fibCont n cont =
match n with
| 0 -> cont 0
| 1 -> cont 1
| _ -> fibCont (n - 1) (fun a ->
fibCont (n - 2) (fun b ->
cont (a + b)))
let result5 = fibCont 40 idType Inference automatically determines the types of expressions based on usage. F# has a powerful type inference system that reduces the need for type annotations.
- Automatic Types: Compiler infers types
- Generic Types: Infers generic types when possible
- Type Annotations: Optional for disambiguation
- Benefits: Less verbose code
- Limitations: Some cases require explicit types
// Type Inference in F#
// Implicit typing
let x = 5 // int
let y = 3.14 // float
let z = "Hello" // string
let list = [1; 2; 3] // int list
let tuple = (1, "two", 3.0) // int * string * float
// Function type inference
let add a b = a + b // 'a -> 'a -> 'a (generic)
let addInt (a: int) b = a + b // int -> int -> int
let addFloat a (b: float) = a + b // float -> float -> float
// Generic functions
let identity x = x // 'a -> 'a
let map f list = List.map f list // ('a -> 'b) -> 'a list -> 'b list
// Type annotations
let (xInt: int) = 5
let (yFloat: float) = 3.14
let (zString: string) = "Hello"
// Custom type inference
let createPair a b = (a, b) // 'a -> 'b -> 'a * 'b
let pair = createPair 5 "Hello" // int * string
// Higher-order function inference
let apply f x = f x // ('a -> 'b) -> 'a -> 'b
let result = apply (fun x -> x * 2) 5Generic Types allow you to define types and functions that work with any type. They provide type safety and code reuse.
- Generic Class:
type Stack<'T>() = ... - Generic Function:
let swap<'T> x y = (y, x) - Constraints:
when 'T : equality - Benefits: Type safety without sacrificing performance
- Use Cases: Collections, algorithms
// Generic Types in F#
// Generic class
type Stack<'T>() =
let mutable items = []
member this.Push(item: 'T) =
items <- item :: items
member this.Pop() =
match items with
| head::tail ->
items <- tail
Some head
| [] -> None
member this.Peek() =
match items with
| head::_ -> Some head
| [] -> None
member this.IsEmpty = items = []
member this.Count = items.Length
// Using generic class
let intStack = Stack<int>()
intStack.Push(1)
intStack.Push(2)
intStack.Push(3)
printfn $"Int stack pop: {intStack.Pop()}"
let stringStack = Stack<string>()
stringStack.Push("Hello")
stringStack.Push("World")
printfn $"String stack pop: {stringStack.Pop()}"
// Generic function
let swap<'T> (x: 'T) (y: 'T) = (y, x)
let swapped = swap 5 10
// Generic with constraints
let addGeneric<'T when 'T: (static member (+) : 'T * 'T -> 'T)> (a: 'T) (b: 'T) =
a + b
let result = addGeneric 5 10Structs are value types stored on the stack. Records are immutable reference types with structural equality. F# supports both with [<Struct>] attribute.
- Record:
type Person = { Name: string; Age: int } - Struct Record:
[<Struct>] type Point = { X: float; Y: float } - Struct Discriminated Unions:
[<Struct>] type Result<'T,'E> = Ok of 'T | Error of 'E - Performance: Structs are more efficient for small types
- Use Cases: Performance-critical code, small data structures
// Structs and Records in F#
// Record type
type Person = {
Name: string
Age: int
Email: string option
}
// Struct record
[<Struct>]
type Point = {
X: float
Y: float
}
// Struct tuple
[<Struct>]
type Result<'T, 'Error> =
| Ok of 'T
| Error of 'Error
// Using records
let person1 = { Name = "Alice"; Age = 25; Email = None }
let person2 = { person1 with Age = 26; Email = Some "alice@email.com" }
printfn $"Person1: {person1}"
printfn $"Person2: {person2}"
// Using structs
let point1 = { X = 3.0; Y = 4.0 }
let point2 = { X = 1.0; Y = 2.0 }
printfn $"Point1: ({point1.X}, {point1.Y})"
// Struct discriminated unions
let ok = Ok 42
let error = Error "Something went wrong"
printfn $"Ok: {ok}"
printfn $"Error: {error}"Nullable Types in F# are used for interoperability with .NET where null values are common. F# typically uses Option types instead.
- Nullable:
Nullable<int>orint? - Conversion: Option to Nullable conversion
- Interoperability: Use with .NET libraries
- Pattern Matching:
if nullable.HasValue then ... - Best Practice: Prefer Option over Nullable
// Nullable Types in F#
open System
// Nullable value types
let nullableInt: Nullable<int> = Nullable(42)
let nullableInt2: int? = Nullable(42)
// Check for value
if nullableInt.HasValue then
printfn $"Value: {nullableInt.Value}"
// Convert to option
let optionValue =
if nullableInt.HasValue then
Some nullableInt.Value
else
None
printfn $"Option value: {optionValue}"
// Nullable with operators
let getValueOrDefault (n: Nullable<int>) defaultValue =
if n.HasValue then n.Value
else defaultValue
let result = getValueOrDefault nullableInt 0
// Working with null in F#
let mightBeNull: string = null
let result2 = if mightBeNull = null then "Empty" else mightBeNull
// Option vs Nullable
let optionToNullable opt =
match opt with
| Some v -> Nullable(v)
| None -> Nullable()
let nullableToOption (n: Nullable<int>) =
if n.HasValue then Some n.Value
else NoneAsync Workflows provide a way to write asynchronous code that is both efficient and readable. They use the async { ... } syntax.
- Creation:
async { ... } - Await:
do! Async.Sleep(ms) - Parallelism:
Async.Parallel - Cancellation: Built-in support
- Error Handling:
try ... with ...
// Async Workflows in F#
open System
open System.Net.Http
// Basic async workflow
let asyncOperation delay =
async {
printfn $"Starting operation with delay {delay}ms"
do! Async.Sleep delay
printfn $"Completed operation with delay {delay}ms"
return delay * 2
}
// Async with error handling
let asyncWithError delay =
async {
try
if delay < 0 then
failwith "Invalid delay"
do! Async.Sleep delay
return delay * 2
with
| ex -> return -1
}
// Async parallel
let runParallel () =
let tasks = [
asyncOperation 1000
asyncOperation 2000
asyncOperation 3000
]
async {
let! results = Async.Parallel tasks
printfn $"Parallel results: {results}"
}
|> Async.RunSynchronously
// Async with cancellation
let cancellableOperation delay token =
async {
for i in 1..10 do
if token.IsCancellationRequested then
printfn "Cancelled"
return -1
printfn $"Processing {i}"
do! Async.Sleep delay
printfn "Completed"
return 1
}
// Async with timeout
let withTimeout timeout operation =
async {
let child = Async.StartChild(operation, timeout)
try
let! result = child
return Some result
with
| :? TimeoutException ->
return None
}
// HTTP async
let fetchUrl url =
async {
use client = new HttpClient()
let! response = client.GetStringAsync(url) |> Async.AwaitTask
return response.Length
}The Task Parallel Library (TPL) in .NET is used in F# for parallel programming. F# provides task { ... } expressions for integration.
- Task Expression:
task { ... } - Await:
do! Task.Delay(ms) - Parallel:
Task.WhenAll - Cancellation:
CancellationTokenSource - Interoperability: Works with .NET tasks
// Task Parallel Library in F#
open System
open System.Threading.Tasks
// Task creation
let taskOperation delay =
task {
printfn $"Task started with delay {delay}ms"
do! Task.Delay delay
printfn $"Task completed with delay {delay}ms"
return delay * 2
}
// Task with error handling
let taskWithError delay =
task {
try
if delay < 0 then
failwith "Invalid delay"
do! Task.Delay delay
return delay * 2
with
| ex -> return -1
}
// Parallel tasks
let runParallelTasks () =
let tasks = [
taskOperation 1000
taskOperation 2000
taskOperation 3000
]
task {
let! results = Task.WhenAll tasks
return results
}
// Task with cancellation
let cancellableTask delay token =
task {
for i in 1..10 do
if token.IsCancellationRequested then
printfn "Cancelled"
return -1
printfn $"Processing {i}"
do! Task.Delay delay
printfn "Completed"
return 1
}
// Task with timeout
let withTimeoutTask timeout operation =
task {
use cts = new CancellationTokenSource()
let! completed = Task.WhenAny(operation, Task.Delay(timeout, cts.Token))
if completed = operation then
return Some (operation.Result)
else
cts.Cancel()
return None
}Parallel Programming in F# includes parallel loops, PLINQ, and task-based parallelism for multi-core processing.
- Parallel.For:
Parallel.For - PLINQ:
Seq.asParallel - Parallel Aggregations: Thread-local data
- Performance: Utilize multiple cores
- Use Cases: Data processing, CPU-intensive operations
// Parallel Programming in F#
open System
open System.Threading.Tasks
// Parallel.For
let parallelFor () =
Parallel.For(0, 10, fun i ->
printfn $"Processing {i} on thread {Thread.CurrentThread.ManagedThreadId}"
i * i
) |> ignore
// Parallel.ForEach
let parallelForEach () =
let data = [1..10]
Parallel.ForEach(data, fun item ->
printfn $"Processing {item}"
item * item
) |> ignore
// PLINQ (Parallel LINQ)
let plinqExample () =
let data = [1..100]
let result =
data
|> Seq.asParallel
|> Seq.map (fun x -> x * x)
|> Seq.filter (fun x -> x % 2 = 0)
|> Seq.take 10
|> Seq.toList
printfn $"PLINQ result: {result}"
// Parallel aggregations
let parallelAggregate () =
let data = [1..1000]
let sum =
data
|> Seq.asParallel
|> Seq.sum
printfn $"Sum: {sum}"
// Parallel with thread-local data
let parallelThreadLocal () =
let results = System.Collections.Concurrent.ConcurrentBag<int>()
Parallel.For(0, 100, fun () -> 0,
fun i state localSum ->
localSum + i,
fun localSum ->
results.Add(localSum)
) |> ignore
printfn $"Total: {results.Sum()}"
// Parallel options
let parallelWithOptions () =
let options = ParallelOptions()
options.MaxDegreeOfParallelism <- 4
Parallel.For(0, 100, options, fun i ->
i * i
) |> ignoreF# provides both immutable and mutable data structures including List, Array, Map, Set, and Dictionary.
- List: Immutable linked list
- Array: Mutable contiguous memory
- Map: Immutable key-value pairs
- Set: Immutable unique values
- Dictionary: Mutable key-value pairs
// Data Structures in F#
open System.Collections.Generic
// List
let list1 = [1; 2; 3; 4; 5]
let list2 = 0 :: list1
let list3 = list1 @ [6; 7; 8]
printfn $"List1: {list1}"
printfn $"List2: {list2}"
printfn $"List3: {list3}"
// Array
let array1 = [|1; 2; 3; 4; 5|]
let array2 = Array.create 5 0
Array.set array2 2 10
printfn $"Array1: {array1}"
printfn $"Array2: {array2}"
// Map
let map1 = Map.empty
let map2 = map1.Add("key1", "value1")
let map3 = map2.Add("key2", "value2")
printfn $"Map: {map3}"
printfn $"Key1: {map3.["key1"]}"
// Set
let set1 = Set.empty
let set2 = set1.Add(1).Add(2).Add(3)
let set3 = Set.ofList [1; 2; 3; 4; 5]
printfn $"Set: {set3}"
printfn $"Contains 3: {set3.Contains(3)}"
// Dictionary
let dict = Dictionary<string, int>()
dict.Add("one", 1)
dict.Add("two", 2)
dict.Add("three", 3)
printfn $"Dictionary: {dict}"
printfn $"Value of two: {dict.["two"]}"The Collections Module provides functions for working with collections including List, Array, Seq, Map, and Set modules.
- List Module:
List.map,List.filter,List.fold - Array Module:
Array.map,Array.filter - Seq Module:
Seq.map,Seq.filter - Map Module:
Map.map,Map.filter - Set Module:
Set.map,Set.filter
// Collections Module in F#
open System
open System.Collections.Generic
// List module functions
let list = [1; 2; 3; 4; 5]
let sum = List.sum list
let product = List.fold (fun acc x -> acc * x) 1 list
let average = float (List.sum list) / float (List.length list)
printfn $"Sum: {sum}"
printfn $"Product: {product}"
printfn $"Average: {average}"
// Array module functions
let arr = [|1; 2; 3; 4; 5|]
let arrSum = Array.sum arr
let arrMax = Array.max arr
let arrMin = Array.min arr
printfn $"Array sum: {arrSum}"
printfn $"Array max: {arrMax}"
printfn $"Array min: {arrMin}"
// Seq module functions
let seq1 = seq { 1..10 }
let evenSeq = Seq.filter (fun x -> x % 2 = 0) seq1
let mappedSeq = Seq.map (fun x -> x * 2) evenSeq
let takenSeq = Seq.take 5 mappedSeq
printfn $"Seq: {takenSeq}"
// Map module functions
let map1 = Map.ofList [("a", 1); ("b", 2); ("c", 3)]
let map2 = Map.map (fun key value -> value * 2) map1
let map3 = Map.filter (fun key value -> value > 2) map1
printfn $"Original map: {map1}"
printfn $"Mapped map: {map2}"
printfn $"Filtered map: {map3}"LINQ (Language Integrated Query) in F# is supported through query { ... } expressions, providing SQL-like syntax for data queries.
- Query Expression:
query { ... } - Operations:
where,select,join,groupBy - Sorting:
sortBy,sortByDescending - Aggregation:
sum,average,count - Use Cases: Database queries, collection queries
// LINQ in F#
open System
open System.Linq
// LINQ queries using query expressions
let data = [1..100]
let query1 =
query {
for x in data do
where (x % 2 = 0)
select x
}
let query2 =
query {
for x in data do
where (x % 2 = 0)
sortBy x
select (x * x)
take 10
}
printfn $"Query1: {query1}"
printfn $"Query2: {query2}"
// LINQ with joins
let left = [1; 2; 3]
let right = [2; 3; 4]
let joinQuery =
query {
for l in left do
join r in right on (l = r)
select l
}
printfn $"Join: {joinQuery}"
// LINQ with grouping
let data2 = ["Apple"; "Banana"; "Cherry"; "Date"; "Elderberry"]
let groupQuery =
query {
for item in data2 do
groupBy item.Length into g
select (g.Key, g)
}
printfn $"Grouping: {groupQuery}"Enumerations in F# are similar to C# enums. They define a set of named values with underlying integer types.
- Definition:
type Color = Red = 0 | Green = 1 | Blue = 2 - Conversion:
int color,enum<Color> value - Pattern Matching: Match on enum values
- Use Cases: Status codes, states, configuration
- Interoperability: Works with C# enums
// Enumerations in F#
// Basic enumeration
type Color =
| Red = 0
| Green = 1
| Blue = 2
let color = Color.Red
printfn $"Color: {color}"
printfn $"Color value: {int color}"
// Enum with methods
type Status =
| Active = 0
| Inactive = 1
| Pending = 2
with
static member FromString str =
match str with
| "Active" -> Status.Active
| "Inactive" -> Status.Inactive
| "Pending" -> Status.Pending
| _ -> Status.Pending
let status = Status.FromString("Active")
printfn $"Status: {status}"
// Enum conversion
let intToColor value =
match value with
| 0 -> Some Color.Red
| 1 -> Some Color.Green
| 2 -> Some Color.Blue
| _ -> None
let color1 = intToColor 1
printfn $"Color from int: {color1}"Attributes in F# are used to add metadata to code elements. They are similar to C# attributes and are used for various purposes including serialization and testing.
- Definition:
[<AttributeUsage(...)>] type MyAttribute = ... - Usage:
[<MyAttribute>] let myFunction ... - Common Attributes:
[<Obsolete>],[<CLIMutable>] - Reflection: Read attributes at runtime
- Use Cases: Testing, serialization, code generation
// Attribute Usage in F#
open System
open System.Reflection
// Custom attribute
[<AttributeUsage(AttributeTargets.Class ||| AttributeTargets.Method)>]
type AuthorAttribute(name: string, version: string) =
inherit Attribute()
member this.Name = name
member this.Version = version
// Using attribute
[<Author("John Doe", "1.0")>]
type Calculator() =
[<Author("Jane Smith", "2.0")>]
member this.Add(x: int, y: int) = x + y
[<Author("John Doe", "1.0")>]
member this.Multiply(x: int, y: int) = x * y
// Read attributes
let readAttributes (t: Type) =
let attrs = t.GetCustomAttributes<AuthorAttribute>()
for attr in attrs do
printfn $"Class Author: {attr.Name} (v{attr.Version})"
let methods = t.GetMethods()
for method in methods do
let attrs = method.GetCustomAttributes<AuthorAttribute>()
for attr in attrs do
printfn $"Method {method.Name}: {attr.Name} (v{attr.Version})"
readAttributes typeof<Calculator>
// Compiler attributes
[<Obsolete("Use new method instead")>]
let oldMethod x = x * 2
[<CLIMutable>]
type Person = {
Name: string
Age: int
}F# has seamless interoperability with other .NET languages. You can use any .NET library, implement interfaces, and inherit from .NET classes.
- Using .NET Libraries: Reference and use any .NET library
- Implementing Interfaces:
interface IMyInterface with ... - Inheritance:
inherit MyClass(...) - Events: Handle .NET events
- Use Cases: Access to .NET ecosystem
// Interop with .NET in F#
open System
open System.Collections.Generic
// Using .NET collections
let list = List<int>()
list.Add(1)
list.Add(2)
list.Add(3)
printfn $"List: {list}"
// Using .NET dictionary
let dict = Dictionary<string, int>()
dict.Add("one", 1)
dict.Add("two", 2)
dict.Add("three", 3)
printfn $"Dictionary: {dict}"
// Using .NET interfaces
let disposeResource (resource: IDisposable) =
try
printfn "Using resource"
finally
resource.Dispose()
// Using .NET events
let event = new Event<int>()
event.Add(fun value -> printfn $"Event received: {value}")
event.Trigger(42)
// Using .NET reflection
let getTypeInfo (t: Type) =
printfn $"Type: {t.Name}"
printfn "Properties:"
for prop in t.GetProperties() do
printfn $" {prop.Name}: {prop.PropertyType.Name}"
getTypeInfo typeof<string>
// Using .NET attributes
[<Serializable>]
type MyData = {
Id: int
Name: string
}F# Interactive (FSI) is a REPL (Read-Eval-Print-Loop) for F#. It allows you to execute F# code interactively for testing and exploration.
- Launch:
dotnet fsiorfsi - Commands:
#help,#quit,#load - Interactive Development: Test code interactively
- Scripting: Create F# scripts (.fsx)
- Use Cases: Exploration, testing, scripting
// F# Interactive (FSI) in F#
// FSI commands
#r "System.Text.Json"
#load "Module.fs"
#time
// FSI functions
let printType (x: obj) =
printfn $"Type: {x.GetType().Name}"
printfn $"Value: {x}"
// FSI with interactive output
let interactiveFunction x =
printfn $"Processing: {x}"
x * 2
// FSI with help
// #help
// #quit
// FSI with references
let jsonExample =
"""
{
"name": "Alice",
"age": 25
}
"""
// FSI with scripts
let runScript file =
printfn $"Running script: {file}"
#load file
// FSI with custom printer
fsi.AddPrinter(fun (p: Person) ->
sprintf $"{p.Name}, {p.Age} years old"
)File I/O in F# uses the .NET System.IO namespace. F# provides functional wrappers for common file operations.
- Reading:
File.ReadAllText,File.ReadAllLines - Writing:
File.WriteAllText,File.WriteAllLines - Streams:
StreamReader,StreamWriter - Directories:
Directory.CreateDirectory,Directory.GetFiles - Use Cases: Data persistence, logging, configuration
// File I/O in F#
open System
open System.IO
// Read from file
let readFile path =
try
File.ReadAllText path
with
| ex -> printfn $"Error reading file: {ex.Message}"; ""
let readLines path =
try
File.ReadAllLines path |> Array.toList
with
| ex -> printfn $"Error reading file: {ex.Message}"; []
// Write to file
let writeFile path content =
try
File.WriteAllText(path, content)
true
with
| ex -> printfn $"Error writing file: {ex.Message}"; false
let writeLines path lines =
try
File.WriteAllLines(path, lines)
true
with
| ex -> printfn $"Error writing file: {ex.Message}"; false
// Append to file
let appendToFile path content =
try
File.AppendAllText(path, content)
true
with
| ex -> printfn $"Error appending to file: {ex.Message}"; false
// File exists
let fileExists path = File.Exists path
// Directory operations
let createDirectory path =
try
Directory.CreateDirectory path |> ignore
true
with
| ex -> printfn $"Error creating directory: {ex.Message}"; false
let getFiles path =
try
Directory.GetFiles path |> Array.toList
with
| ex -> printfn $"Error getting files: {ex.Message}"; []
// Streaming file
let processFileStream path =
use reader = new StreamReader(path)
while not reader.EndOfStream do
let line = reader.ReadLine()
printfn $"Line: {line}"JSON Serialization in F# can be done using System.Text.Json, Newtonsoft.Json, or FSharp.Json for functional JSON handling.
- System.Text.Json:
JsonSerializer.Serialize - FSharp.Json: Functional JSON library
- Newtonsoft.Json:
JsonConvert.SerializeObject - Records: Serialize records and unions
- Use Cases: APIs, configuration, data exchange
// JSON Serialization in F#
open System.Text.Json
open System.Text.Json.Serialization
// JSON with System.Text.Json
type Person = {
Name: string
Age: int
Email: string option
}
let serializePerson person =
let options = JsonSerializerOptions()
options.WriteIndented <- true
JsonSerializer.Serialize(person, options)
let deserializePerson json =
try
JsonSerializer.Deserialize<Person>(json)
with
| ex ->
printfn $"Error deserializing: {ex.Message}"
null
// JSON with FSharp.Json
// open FSharp.Json
// type Person = {
// name: string
// age: int
// email: string option
// }
// let json = Json.serialize { name = "Alice"; age = 25; email = None }
// let person = Json.deserialize<Person> json
// JSON with Newtonsoft.Json (Json.NET)
// open Newtonsoft.Json
// let json = JsonConvert.SerializeObject(person)
// let person = JsonConvert.DeserializeObject<Person>(json)
// Working with JSON arrays
type PersonList = Person list
let serializeList people =
JsonSerializer.Serialize(people)
let deserializeList json =
JsonSerializer.Deserialize<PersonList>(json)
// JSON with custom converter
type JsonConverterOptions =
{
DateFormat: string
NumberFormat: string
}XML Processing in F# can be done using XDocument, XmlDocument, or XmlProvider for type-safe XML access.
- XDocument: LINQ to XML
- XmlDocument: DOM-based XML processing
- XmlProvider: Type-safe XML access
- LINQ to XML: Query XML with LINQ
- Use Cases: Configuration, data exchange, web services
// XML Processing in F#
open System.Xml
open System.Xml.Linq
// XML with XDocument
let createXml () =
let doc = XDocument(
XElement("library",
XElement("book",
XElement("title", "F# Programming"),
XElement("author", "John Doe"),
XElement("year", "2023")
),
XElement("book",
XElement("title", "F# in Action"),
XElement("author", "Jane Smith"),
XElement("year", "2024")
)
)
)
doc.ToString()
// XML with XmlDocument
let createXmlWithXmlDocument () =
let doc = XmlDocument()
let root = doc.CreateElement("library")
doc.AppendChild(root) |> ignore
let book = doc.CreateElement("book")
root.AppendChild(book) |> ignore
let title = doc.CreateElement("title")
title.InnerText <- "F# Programming"
book.AppendChild(title) |> ignore
doc.OuterXml
// Parse XML
let parseXml xml =
let doc = XDocument.Parse(xml)
let books = doc.Descendants("book")
for book in books do
let title = book.Element("title")
let author = book.Element("author")
printfn $"Book: {title.Value} by {author.Value}"
// LINQ to XML
let queryXml doc =
let books =
doc.Descendants("book")
|> Seq.map (fun b ->
(b.Element("title").Value, b.Element("author").Value)
)
|> Seq.toList
booksDatabase Access in F# can be done using SqlDataProvider, Dapper, Entity Framework, or raw ADO.NET.
- SqlDataProvider: Type-safe SQL queries
- Dapper: Micro-ORM for .NET
- Entity Framework: Full ORM
- ADO.NET:
SqlConnection,SqlCommand - Use Cases: Data persistence, reporting, analytics
// Database Access in F#
open System
open System.Data
open System.Data.SqlClient
// SQL connection
let connectionString = "Server=localhost;Database=test;Integrated Security=true"
// Execute query
let executeQuery query =
use conn = new SqlConnection(connectionString)
use cmd = new SqlCommand(query, conn)
conn.Open()
use reader = cmd.ExecuteReader()
let results = ResizeArray<obj[]>()
while reader.Read() do
let row = [| for i in 0..reader.FieldCount-1 -> reader.GetValue(i) |]
results.Add(row)
results
// Execute non-query
let executeNonQuery query =
use conn = new SqlConnection(connectionString)
use cmd = new SqlCommand(query, conn)
conn.Open()
cmd.ExecuteNonQuery()
// Parameterized query
let getUserById id =
use conn = new SqlConnection(connectionString)
let query = "SELECT * FROM Users WHERE Id = @Id"
use cmd = new SqlCommand(query, conn)
cmd.Parameters.AddWithValue("@Id", id) |> ignore
conn.Open()
use reader = cmd.ExecuteReader()
if reader.Read() then
Some (reader.GetString(1), reader.GetInt32(2))
else
None
// Transaction
let updateWithTransaction () =
use conn = new SqlConnection(connectionString)
conn.Open()
use trans = conn.BeginTransaction()
try
let query1 = "UPDATE Users SET Age = 25 WHERE Id = 1"
use cmd1 = new SqlCommand(query1, conn, trans)
cmd1.ExecuteNonQuery() |> ignore
let query2 = "INSERT INTO Logs (Message) VALUES ('Updated user')"
use cmd2 = new SqlCommand(query2, conn, trans)
cmd2.ExecuteNonQuery() |> ignore
trans.Commit()
true
with
| ex ->
trans.Rollback()
falseHTTP Client in F# uses HttpClient for making HTTP requests. F# provides async wrappers for HTTP operations.
- GET:
HttpClient.GetStringAsync - POST:
HttpClient.PostAsync - Headers: Add headers to requests
- Async:
Async.AwaitTaskfor async support - Use Cases: API calls, web scraping, integrations
// HTTP Client in F#
open System
open System.Net.Http
open System.Text.Json
// Basic HTTP GET
let httpClient = new HttpClient()
let getData url =
async {
try
let! response = httpClient.GetAsync(url) |> Async.AwaitTask
response.EnsureSuccessStatusCode() |> ignore
let! content = response.Content.ReadAsStringAsync() |> Async.AwaitTask
return Some content
with
| ex ->
printfn $"Error: {ex.Message}"
return None
}
// HTTP POST
let postData url data =
async {
try
let json = JsonSerializer.Serialize(data)
let content = new StringContent(json, System.Text.Encoding.UTF8, "application/json")
let! response = httpClient.PostAsync(url, content) |> Async.AwaitTask
response.EnsureSuccessStatusCode() |> ignore
let! result = response.Content.ReadAsStringAsync() |> Async.AwaitTask
return Some result
with
| ex ->
printfn $"Error: {ex.Message}"
return None
}
// HTTP with headers
let getDataWithHeaders url headers =
async {
try
use request = new HttpRequestMessage(HttpMethod.Get, url)
for (key, value) in headers do
request.Headers.Add(key, value)
let! response = httpClient.SendAsync(request) |> Async.AwaitTask
response.EnsureSuccessStatusCode() |> ignore
let! content = response.Content.ReadAsStringAsync() |> Async.AwaitTask
return Some content
with
| ex ->
printfn $"Error: {ex.Message}"
return None
}
// HTTP with timeout
let getDataWithTimeout url timeout =
async {
use cts = new CancellationTokenSource()
cts.CancelAfter(timeout)
try
let! response = httpClient.GetAsync(url, cts.Token) |> Async.AwaitTask
response.EnsureSuccessStatusCode() |> ignore
let! content = response.Content.ReadAsStringAsync() |> Async.AwaitTask
return Some content
with
| :? OperationCanceledException ->
printfn "Request timed out"
return None
| ex ->
printfn $"Error: {ex.Message}"
return None
}Web API in F# can be built using Giraffe, Saturn, or ASP.NET Core. These frameworks provide functional approaches to web development.
- Giraffe: Functional web framework
- Saturn: Web framework with MVC support
- ASP.NET Core: Full-featured web framework
- Routing: Functional route handling
- JSON: Built-in JSON support
// Web API in F#
open System
open System.Net
open System.Text.Json
// HTTP Listener (simple web server)
let startServer port =
let listener = new HttpListener()
listener.Prefixes.Add($"http://localhost:{port}/")
listener.Start()
printfn $"Server started on port {port}"
async {
while true do
let! context = listener.GetContextAsync() |> Async.AwaitTask
async {
use response = context.Response
let responseString = "Hello, F# World!"
let buffer = System.Text.Encoding.UTF8.GetBytes(responseString)
response.ContentLength64 <- buffer.LongLength
response.OutputStream.Write(buffer, 0, buffer.Length)
} |> Async.Start
} |> Async.Start
// REST API handler
let handleRequest (context: HttpListenerContext) =
async {
let request = context.Request
let response = context.Response
match request.Url.AbsolutePath with
| "/api/users" ->
let users = [| {| Name = "Alice"; Age = 25 |}; {| Name = "Bob"; Age = 30 |} |]
let json = JsonSerializer.Serialize(users)
let buffer = System.Text.Encoding.UTF8.GetBytes(json)
response.ContentType <- "application/json"
response.ContentLength64 <- buffer.LongLength
response.OutputStream.Write(buffer, 0, buffer.Length)
| "/api/health" ->
let json = JsonSerializer.Serialize({| Status = "OK" |})
let buffer = System.Text.Encoding.UTF8.GetBytes(json)
response.ContentType <- "application/json"
response.ContentLength64 <- buffer.LongLength
response.OutputStream.Write(buffer, 0, buffer.Length)
| _ ->
response.StatusCode <- 404
}
// Giraffe web framework
// open Giraffe
//
// let webApp =
// choose [
// GET >=> route "/" >=> text "Hello, World!"
// GET >=> route "/api/users" >=> json [ { Name = "Alice"; Age = 25 } ]
// setStatusCode 404 >=> text "Not Found"
// ]Dependency Injection in F# uses function parameters and partial application. It promotes loose coupling and testability.
- Function Parameters: Pass dependencies as functions
- Partial Application: Inject dependencies via partial application
- Service Container: Use .NET DI container
- Interfaces: Use interfaces for abstraction
- Use Cases: Testability, flexibility, maintainability
// Dependency Injection in F#
open System
// Interface
type ILogger =
abstract member Log: string -> unit
// Implementation
type ConsoleLogger() =
interface ILogger with
member this.Log(message) =
printfn $"[LOG] {message}"
type FileLogger(filePath: string) =
interface ILogger with
member this.Log(message) =
System.IO.File.AppendAllText(filePath, $"{message}
")
// Service with dependency
type UserService(logger: ILogger) =
member this.CreateUser(name: string, age: int) =
logger.Log($"Creating user: {name}")
{ Name = name; Age = age }
// Dependency injection container
type ServiceContainer() =
let mutable logger: ILogger option = None
member this.RegisterLogger(l: ILogger) =
logger <- Some l
member this.ResolveUserService() =
match logger with
| Some l -> new UserService(l)
| None -> failwith "Logger not registered"
// Using DI
let container = ServiceContainer()
container.RegisterLogger(ConsoleLogger())
let userService = container.ResolveUserService()
let user = userService.CreateUser("Alice", 25)
// Constructor injection with record
type App(logger: ILogger) =
member this.Run() =
logger.Log("Application started")
printfn "Running application..."
// Manual DI
let app = App(ConsoleLogger())
app.Run()Configuration Management in F# uses .NET configuration providers with F# record types for strongly-typed configuration.
- Configuration Providers: JSON, XML, environment variables
- Strong Types: Use records for configuration
- Validation: Validate configuration on load
- Environment Overrides: Override with environment variables
- Use Cases: Application settings, feature flags
// Configuration Management in F#
open System
open System.IO
// Configuration types
type DbConfig = {
ConnectionString: string
Timeout: int
}
type AppConfig = {
Db: DbConfig
Logging: bool
Environment: string
}
// Configuration from environment variables
let getConfigFromEnv () =
{
Db = {
ConnectionString = Environment.GetEnvironmentVariable("DB_CONNECTION") ?? "DefaultConnection"
Timeout = Environment.GetEnvironmentVariable("DB_TIMEOUT") |> int |> Option.defaultValue 30
}
Logging = Environment.GetEnvironmentVariable("LOGGING_ENABLED") = "true"
Environment = Environment.GetEnvironmentVariable("ENVIRONMENT") ?? "Development"
}
// Configuration from JSON file
open System.Text.Json
let loadConfigFromFile (path: string) =
try
let json = File.ReadAllText(path)
JsonSerializer.Deserialize<AppConfig>(json)
with
| ex ->
printfn $"Error loading config: {ex.Message}"
{ Db = { ConnectionString = "DefaultConnection"; Timeout = 30 }; Logging = true; Environment = "Development" }
// Configuration with fallbacks
let getConfig () =
let envConfig = getConfigFromEnv ()
let fileConfig = loadConfigFromFile "appsettings.json"
{
Db = {
ConnectionString = envConfig.Db.ConnectionString
Timeout = envConfig.Db.Timeout
}
Logging = envConfig.Logging
Environment = envConfig.Environment
}
// Configuration with validation
let validateConfig (config: AppConfig) =
if String.IsNullOrEmpty(config.Db.ConnectionString) then
failwith "Connection string is required"
if config.Db.Timeout < 0 then
failwith "Timeout must be positive"
let safeGetConfig () =
try
let config = getConfig ()
validateConfig config
Some config
with
| ex ->
printfn $"Invalid configuration: {ex.Message}"
NoneLogging in F# uses ILogger from Microsoft.Extensions.Logging or custom loggers with functional composition.
- ILogger: Standard .NET logging
- Custom Logger: Functional logging with records
- Log Levels: Debug, Info, Warning, Error
- Structured Logging: Log structured data
- Use Cases: Debugging, monitoring, auditing
// Logging in F#
open System
// Simple logger
type LogLevel =
| Info
| Warning
| Error
| Debug
type Logger() =
member this.Log(level: LogLevel, message: string) =
let timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
printfn $"[{timestamp}] [{level}] {message}"
member this.Info(message) = this.Log(Info, message)
member this.Warning(message) = this.Log(Warning, message)
member this.Error(message) = this.Log(Error, message)
member this.Debug(message) = this.Log(Debug, message)
// Logger with categories
type CategoryLogger(category: string) =
let logger = Logger()
member this.Log(level: LogLevel, message: string) =
logger.Log(level, $"[{category}] {message}")
member this.Info(message) = this.Log(Info, message)
member this.Warning(message) = this.Log(Warning, message)
member this.Error(message) = this.Log(Error, message)
// Logger with file output
type FileLogger(filePath: string) =
let logger = Logger()
let writeToFile message =
try
System.IO.File.AppendAllText(filePath, $"{message}
")
with
| ex -> printfn $"Error writing to log file: {ex.Message}"
member this.Log(level: LogLevel, message: string) =
let timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
let logMessage = $"[{timestamp}] [{level}] {message}"
writeToFile logMessage
logger.Log(level, message)
member this.Info(message) = this.Log(Info, message)
member this.Warning(message) = this.Log(Warning, message)
member this.Error(message) = this.Log(Error, message)
// Usage
let logger = Logger()
logger.Info("Application started")
logger.Warning("Low memory warning")
logger.Error("An error occurred")Performance Optimization in F# includes using structs, avoiding boxing, leveraging tail recursion, and using efficient data structures.
- Structs: Use structs for small data
- Tail Recursion: Use tail recursion for deep recursion
- Lazy Evaluation: Use lazy for expensive computations
- Parallelism: Use parallel programming
- Use Cases: High-performance applications
// Performance Optimization in F#
open System
open System.Diagnostics
// Timing functions
let timeOperation operation =
let sw = Stopwatch()
sw.Start()
let result = operation()
sw.Stop()
printfn $"Elapsed: {sw.ElapsedMilliseconds}ms"
result
// Optimized loops
let sumNumbers n =
let mutable sum = 0
for i in 1..n do
sum <- sum + i
sum
let sumNumbersFunctional n =
[1..n] |> List.sum
let sumNumbersOptimized n =
n * (n + 1) / 2
// Lazy evaluation
let expensiveList = lazy (
printfn "Building list..."
[1..1000000]
)
let first10 =
expensiveList.Value
|> List.take 10
|> List.map (fun x -> x * 2)
// Memory optimization
let processLargeData data =
use enumerator = data.GetEnumerator()
let mutable sum = 0
while enumerator.MoveNext() do
sum <- sum + enumerator.Current
sum
// Parallel performance
let parallelMap data f =
data
|> Seq.asParallel
|> Seq.map f
|> Seq.toList
// Caching
let memoize f =
let cache = System.Collections.Generic.Dictionary<_, _>()
fun x ->
match cache.TryGetValue(x) with
| true, v -> v
| false, _ ->
let v = f x
cache.Add(x, v)
v
// Struct optimization
[<Struct>]
type FastPoint = {
X: float
Y: float
}
// Inline functions
let inline addInline a b = a + bTesting in F# can be done using xUnit, NUnit, Expecto, or FsCheck for property-based testing.
- xUnit:
[<Fact>]and[<Theory>] - Expecto: Functional testing library
- FsCheck: Property-based testing
- Mocking: Use Moq or NSubstitute
- Use Cases: Unit testing, integration testing, property testing
// Testing in F#
open System
open Xunit
// Unit tests
module MathTests =
[<Fact>]
let "Add should return sum of two numbers" () =
let result = Math.add 5 3
Assert.Equal(8, result)
[<Fact>]
let "Subtract should return difference of two numbers" () =
let result = Math.subtract 10 4
Assert.Equal(6, result)
// Property-based testing
module PropertyTests =
[<Fact>]
let "Multiplication is commutative" () =
let result = Math.multiply 5 3
let result2 = Math.multiply 3 5
Assert.Equal(result, result2)
// Exception testing
module ExceptionTests =
[<Fact>]
let "Divide by zero throws exception" () =
Assert.Throws<DivideByZeroException>(fun () ->
Math.divide 10 0 |> ignore
)
// Test with setup
module SetupTests =
let setup () =
{ Name = "Alice"; Age = 25 }
[<Fact>]
let "Person has correct name" () =
let person = setup ()
Assert.Equal("Alice", person.Name)
// Async testing
module AsyncTests =
[<Fact>]
let "Async operation completes successfully" () =
async {
let! result = Math.asyncAdd 5 3
Assert.Equal(8, result)
} |> Async.RunSynchronously
// Theory tests
module TheoryTests =
[<Theory>]
[<InlineData(1, 2, 3)>]
[<InlineData(5, 7, 12)>]
[<InlineData(10, 20, 30)>]
let "Add works with multiple inputs" a b expected =
let result = Math.add a b
Assert.Equal(expected, result)This question is currently missing. Please add the appropriate question text and content.
Code Contracts in F# use types like Option and Result to encode invariants, preconditions, and postconditions in the type system.
- Option: Represent optional values
- Result: Represent success or failure
- Preconditions: Validate inputs with Option/Result
- Postconditions: Validate outputs
- Use Cases: Error handling, validation, invariants
// Code Contracts in F#
open System
// Contract using option type
let divideOption x y =
if y = 0 then None
else Some (x / y)
// Contract using result type
type DivisionError =
| DivisionByZero
| InvalidInput
let divideResult x y =
if y = 0 then Error DivisionByZero
else Ok (x / y)
// Contract with validation
let validateAge age =
if age < 0 then
Error "Age cannot be negative"
elif age > 150 then
Error "Age must be less than 150"
else
Ok age
// Contract with preconditions
let factorialWithPrecondition n =
if n < 0 then
failwith "n must be non-negative"
elif n > 20 then
failwith "n must be less than or equal to 20"
else
let rec fact acc n =
if n <= 1 then acc
else fact (acc * n) (n - 1)
fact 1 n
// Contract with postconditions
let divideWithPostcondition x y =
let result = x / y
if result < 0 then
failwith "Result must be non-negative"
result
// Contract with invariants
type BankAccount(balance: int) =
let mutable balance = balance
member this.Balance = balance
member this.Deposit amount =
if amount <= 0 then
failwith "Amount must be positive"
balance <- balance + amount
member this.Withdraw amount =
if amount <= 0 then
failwith "Amount must be positive"
if amount > balance then
failwith "Insufficient funds"
balance <- balance - amount
if balance < 0 then
failwith "Balance cannot be negative"Domain Modeling in F# uses types like discriminated unions, records, and single-case unions to model domain concepts precisely.
- Domain Types: Use discriminated unions and records
- Single-Case Unions:
type Email = Email of string - Validation: Use Result type for validation
- Domain Events: Model events with discriminated unions
- Use Cases: DDD, business logic, domain modeling
// Domain Modeling in F#
open System
// Domain types
type UserId = UserId of Guid
type Email = Email of string
type Name = Name of string
type User = {
Id: UserId
Name: Name
Email: Email
CreatedAt: DateTime
}
// Domain validation
module Email =
let create email =
if String.IsNullOrEmpty(email) then
Error "Email cannot be empty"
elif not (email.Contains "@") then
Error "Invalid email format"
else
Ok (Email email)
module Name =
let create name =
if String.IsNullOrEmpty(name) then
Error "Name cannot be empty"
elif name.Length < 2 then
Error "Name must be at least 2 characters"
else
Ok (Name name)
// Domain service
module UserService =
let createUser name email =
result {
let! name = Name.create name
let! email = Email.create email
return {
Id = UserId (Guid.NewGuid())
Name = name
Email = email
CreatedAt = DateTime.UtcNow
}
}
// Domain events
type UserEvent =
| UserCreated of User
| UserUpdated of User
| UserDeleted of UserId
// Event handling
module EventHandler =
let handleUserEvent event =
match event with
| UserCreated user ->
printfn $"User created: {user.Name}"
| UserUpdated user ->
printfn $"User updated: {user.Name}"
| UserDeleted id ->
printfn $"User deleted: {id}"Event Sourcing captures state changes as a sequence of events. F# discriminated unions are perfect for modeling events and state transitions.
- Events: Model events as discriminated unions
- Event Store: Store events in an event store
- Projections: Build projections from events
- State: Reconstruct state from events
- Use Cases: Audit trails, CQRS, event-driven systems
// Event Sourcing in F#
open System
// Event types
type AccountEvent =
| AccountOpened of {| AccountId: string; Owner: string; InitialBalance: decimal |}
| MoneyDeposited of {| AccountId: string; Amount: decimal; Timestamp: DateTime |}
| MoneyWithdrawn of {| AccountId: string; Amount: decimal; Timestamp: DateTime |}
| AccountClosed of {| AccountId: string; Reason: string |}
// Event store
type EventStore() =
let mutable events: AccountEvent list = []
member this.SaveEvent(event: AccountEvent) =
events <- event :: events
member this.GetEvents() =
List.rev events
member this.GetEventsForAccount(accountId: string) =
events
|> List.filter (fun e ->
match e with
| AccountOpened args -> args.AccountId = accountId
| MoneyDeposited args -> args.AccountId = accountId
| MoneyWithdrawn args -> args.AccountId = accountId
| AccountClosed args -> args.AccountId = accountId
)
// Projection
type AccountState = {
AccountId: string
Owner: string
Balance: decimal
IsActive: bool
}
let applyEvent state event =
match event with
| AccountOpened args ->
Some { AccountId = args.AccountId; Owner = args.Owner; Balance = args.InitialBalance; IsActive = true }
| MoneyDeposited args ->
state |> Option.map (fun s -> { s with Balance = s.Balance + args.Amount })
| MoneyWithdrawn args ->
state |> Option.map (fun s -> { s with Balance = s.Balance - args.Amount })
| AccountClosed _ ->
state |> Option.map (fun s -> { s with IsActive = false })
// Event handler
let eventHandler store =
let handleEvent event =
store.SaveEvent(event)
printfn $"Event saved: {event}"
handleEvent
// Usage
let store = EventStore()
let handle = eventHandler store
handle (AccountOpened {| AccountId = "123"; Owner = "Alice"; InitialBalance = 1000m |})
handle (MoneyDeposited {| AccountId = "123"; Amount = 500m; Timestamp = DateTime.Now |})
handle (MoneyWithdrawn {| AccountId = "123"; Amount = 200m; Timestamp = DateTime.Now |})CQRS (Command Query Responsibility Segregation) separates read and write operations. F# discriminated unions are ideal for commands and queries.
- Commands: Model commands as discriminated unions
- Queries: Model queries as discriminated unions
- Command Handlers: Process commands
- Query Handlers: Process queries
- Use Cases: Complex domains, scalability
// CQRS Pattern in F#
open System
// Command types
type Command =
| CreateUser of {| Name: string; Email: string |}
| UpdateUser of {| Id: Guid; Name: string; Email: string |}
| DeleteUser of {| Id: Guid |}
// Query types
type Query =
| GetUser of {| Id: Guid |}
| GetAllUsers
| SearchUsers of {| Query: string |}
// Command handler
type CommandHandler() =
member this.Handle(command: Command) =
match command with
| CreateUser args ->
printfn $"Creating user: {args.Name}"
// Create user logic
Ok ()
| UpdateUser args ->
printfn $"Updating user: {args.Id}"
// Update user logic
Ok ()
| DeleteUser args ->
printfn $"Deleting user: {args.Id}"
// Delete user logic
Ok ()
// Query handler
type QueryHandler() =
member this.Handle(query: Query) =
match query with
| GetUser args ->
printfn $"Getting user: {args.Id}"
// Get user logic
Ok {| Id = args.Id; Name = "Alice"; Email = "alice@email.com" |}
| GetAllUsers ->
printfn "Getting all users"
// Get all users logic
Ok [ {| Id = Guid.NewGuid(); Name = "Alice"; Email = "alice@email.com" |} ]
| SearchUsers args ->
printfn $"Searching users: {args.Query}"
// Search users logic
Ok []
// Mediator
type Mediator(commandHandler: CommandHandler, queryHandler: QueryHandler) =
member this.Send(command: Command) =
commandHandler.Handle(command)
member this.Query(query: Query) =
queryHandler.Handle(query)
// Usage
let commandHandler = CommandHandler()
let queryHandler = QueryHandler()
let mediator = Mediator(commandHandler, queryHandler)
let createUser = CreateUser {| Name = "Alice"; Email = "alice@email.com" |}
mediator.Send(createUser)
let getUser = GetUser {| Id = Guid.NewGuid() |}
let user = mediator.Query(getUser)Messaging Patterns in F# use message buses and actors for communication between components. Discriminated unions are used for message types.
- Messages: Model messages as discriminated unions
- Message Bus: Publish/subscribe infrastructure
- Publishing:
bus.Publish(message) - Subscribing:
bus.Subscribe(fun message -> ...) - Use Cases: Event-driven systems, microservices
// Messaging Patterns in F#
open System
open System.Threading.Tasks
// Message types
type Message<'T> = {
Id: Guid
Payload: 'T
Timestamp: DateTime
CorrelationId: Guid option
}
// Message Bus
type MessageBus() =
let subscribers = System.Collections.Concurrent.ConcurrentDictionary<Type, obj list>()
member this.Subscribe<'T>(handler: Message<'T> -> unit) =
let key = typeof<'T>
let handlers = subscribers.GetOrAdd(key, fun _ -> [])
subscribers.[key] <- handler :> obj :: handlers
member this.Publish<'T>(message: Message<'T>) =
let key = typeof<'T>
match subscribers.TryGetValue(key) with
| true, handlers ->
for handler in handlers do
let typedHandler = handler :?> (Message<'T> -> unit)
typedHandler(message)
| false, _ -> ()
// Message Handler
type MessageHandler() =
member this.HandleUserCreated(message: Message<string>) =
printfn $"User created: {message.Payload} with ID: {message.Id}"
member this.HandleUserUpdated(message: Message<string>) =
printfn $"User updated: {message.Payload}"
// Usage
let bus = MessageBus()
let handler = MessageHandler()
bus.Subscribe<string> handler.HandleUserCreated
bus.Subscribe<string> handler.HandleUserUpdated
let message = {
Id = Guid.NewGuid()
Payload = "Alice"
Timestamp = DateTime.Now
CorrelationId = None
}
bus.Publish(message)Saga Pattern manages long-running transactions across multiple services. F# discriminated unions and computation expressions are used for sagas.
- Steps: Define saga steps
- State: Track saga state
- Compensation: Handle rollbacks
- Coordinator: Orchestrate saga steps
- Use Cases: Distributed transactions, workflows
// Saga Pattern in F#
open System
// Saga state
type SagaState<'T> = {
CurrentStep: int
Data: 'T
IsCompleted: bool
IsRollback: bool
}
// Step types
type StepResult<'T> =
| Success of 'T
| Failure of string
| Rollback of 'T
// Saga orchestrator
type SagaOrchestrator<'T>() =
let mutable state: SagaState<'T> option = None
member this.Start(initialData: 'T) =
state <- Some { CurrentStep = 0; Data = initialData; IsCompleted = false; IsRollback = false }
printfn "Saga started"
member this.ExecuteStep(step: 'T -> StepResult<'T>) =
match state with
| Some s when not s.IsCompleted && not s.IsRollback ->
let result = step s.Data
match result with
| Success newData ->
state <- Some { s with CurrentStep = s.CurrentStep + 1; Data = newData }
if s.CurrentStep + 1 >= 3 then
state <- Some { s with IsCompleted = true }
printfn "Saga completed"
else
printfn $"Step {s.CurrentStep + 1} completed"
| Failure error ->
printfn $"Step failed: {error}"
// Start rollback
state <- Some { s with IsRollback = true }
| Rollback data ->
state <- Some { s with Data = data; IsCompleted = false; IsRollback = true }
printfn "Rolling back..."
| _ ->
printfn "Cannot execute step: Saga not in correct state"
// Example steps
let step1 data =
printfn "Executing step 1"
Success (data + " [Step1]")
let step2 data =
printfn "Executing step 2"
Success (data + " [Step2]")
let step3 data =
printfn "Executing step 3"
Success (data + " [Step3]")
let failingStep data =
printfn "Executing failing step"
Failure "Step failed"Specification Pattern defines business rules as predicates. In F#, predicates are composed using function composition and combinators.
- Predicate:
'T -> bool - Composition:
&&&,|||combinators - Reusability: Compose specifications
- Use Cases: Business rules, filtering, validation
// Specification Pattern in F#
open System
// Specification interface
type ISpecification<'T> =
abstract member IsSatisfiedBy: 'T -> bool
abstract member And: ISpecification<'T> -> ISpecification<'T>
abstract member Or: ISpecification<'T> -> ISpecification<'T>
abstract member Not: unit -> ISpecification<'T>
// Base specification
type Specification<'T>() =
abstract member IsSatisfiedBy: 'T -> bool
default this.IsSatisfiedBy _ = true
interface ISpecification<'T> with
member this.IsSatisfiedBy x = this.IsSatisfiedBy x
member this.And(other) = AndSpecification(this, other) :> ISpecification<'T>
member this.Or(other) = OrSpecification(this, other) :> ISpecification<'T>
member this.Not() = NotSpecification(this) :> ISpecification<'T>
// Combined specifications
and AndSpecification<'T>(left: ISpecification<'T>, right: ISpecification<'T>) =
inherit Specification<'T>()
override this.IsSatisfiedBy x =
left.IsSatisfiedBy(x) && right.IsSatisfiedBy(x)
and OrSpecification<'T>(left: ISpecification<'T>, right: ISpecification<'T>) =
inherit Specification<'T>()
override this.IsSatisfiedBy x =
left.IsSatisfiedBy(x) || right.IsSatisfiedBy(x)
and NotSpecification<'T>(spec: ISpecification<'T>) =
inherit Specification<'T>()
override this.IsSatisfiedBy x =
not (spec.IsSatisfiedBy(x))
// Example specifications
type AgeSpecification(minAge: int, maxAge: int) =
inherit Specification<Person>()
override this.IsSatisfiedBy person =
person.Age >= minAge && person.Age <= maxAge
type NameSpecification(contains: string) =
inherit Specification<Person>()
override this.IsSatisfiedBy person =
person.Name.Contains(contains)
// Usage
let adultSpec = AgeSpecification(18, 150)
let nameSpec = NameSpecification("A")
let combinedSpec = adultSpec.And(nameSpec) :?> ISpecification<Person>
let person = { Name = "Alice"; Age = 25 }
let isSatisfied = combinedSpec.IsSatisfiedBy(person)
printfn $"Is satisfied: {isSatisfied}"Strategy Pattern encapsulates algorithms in functions. In F#, strategies are simply functions that can be passed as parameters.
- Strategy:
'T -> 'U - Function Composition: Compose strategies
- Dynamic Selection: Select strategy at runtime
- Use Cases: Algorithms, business rules, policies
// Strategy Pattern in F#
open System
// Strategy interface
type IStrategy<'TInput, 'TOutput> =
abstract member Execute: 'TInput -> 'TOutput
// Concrete strategies
type AddStrategy() =
interface IStrategy<int, int> with
member this.Execute(x) = x + x
type MultiplyStrategy() =
interface IStrategy<int, int> with
member this.Execute(x) = x * 2
type SquareStrategy() =
interface IStrategy<int, int> with
member this.Execute(x) = x * x
// Strategy context
type StrategyContext<'TInput, 'TOutput>() =
let mutable strategy: IStrategy<'TInput, 'TOutput> option = None
member this.SetStrategy(s: IStrategy<'TInput, 'TOutput>) =
strategy <- Some s
member this.Execute(input: 'TInput) =
match strategy with
| Some s -> s.Execute(input)
| None -> failwith "No strategy set"
// Function-based strategy
let createFunctionStrategy f =
{ new IStrategy<_, _> with
member this.Execute(x) = f x }
// Usage
let context = StrategyContext<int, int>()
context.SetStrategy(AddStrategy())
printfn $"Add: {context.Execute(5)}"
context.SetStrategy(MultiplyStrategy())
printfn $"Multiply: {context.Execute(5)}"
context.SetStrategy(SquareStrategy())
printfn $"Square: {context.Execute(5)}"
// Function-based strategy
let doubleStrategy = createFunctionStrategy (fun x -> x * 2)
context.SetStrategy(doubleStrategy)
printfn $"Function strategy: {context.Execute(5)}"Factory Pattern creates objects without specifying the concrete class. In F#, factories are functions that return objects or records.
- Factory Function:
createType : string -> IProduct - Factory Registry: Register and lookup factories
- Functional Factory: Use functions for creation
- Use Cases: Object creation, dependency injection
// Factory Pattern in F#
open System
// Product interface
type IProduct =
abstract member Name: string
abstract member Price: decimal
// Concrete products
type Book(name: string, price: decimal) =
interface IProduct with
member this.Name = name
member this.Price = price
type Electronic(name: string, price: decimal) =
interface IProduct with
member this.Name = name
member this.Price = price
type Clothing(name: string, price: decimal) =
interface IProduct with
member this.Name = name
member this.Price = price
// Factory interface
type IProductFactory =
abstract member CreateProduct: string * decimal -> IProduct
// Concrete factories
type BookFactory() =
interface IProductFactory with
member this.CreateProduct(name, price) =
Book(name, price) :> IProduct
type ElectronicFactory() =
interface IProductFactory with
member this.CreateProduct(name, price) =
Electronic(name, price) :> IProduct
type ClothingFactory() =
interface IProductFactory with
member this.CreateProduct(name, price) =
Clothing(name, price) :> IProduct
// Factory registry
type FactoryRegistry() =
let factories = System.Collections.Generic.Dictionary<string, IProductFactory>()
member this.Register(typeName: string, factory: IProductFactory) =
factories.[typeName] <- factory
member this.Create(typeName: string, name: string, price: decimal) =
match factories.TryGetValue(typeName) with
| true, factory -> Some (factory.CreateProduct(name, price))
| false, _ -> None
// Usage
let registry = FactoryRegistry()
registry.Register("Book", BookFactory())
registry.Register("Electronic", ElectronicFactory())
registry.Register("Clothing", ClothingFactory())
match registry.Create("Book", "F# Programming", 39.99m) with
| Some product -> printfn $"Created: {product.Name} - ${product.Price}"
| None -> printfn "Failed to create product"Builder Pattern constructs complex objects step by step. In F#, builders can be implemented as object builders or functional builders.
- Object Builder: Mutable builder object
- Functional Builder: Functions that set properties
- Fluent Interface: Chain method calls
- Use Cases: Complex object construction, configuration
// Builder Pattern in F#
open System
// Product to build
type Computer = {
CPU: string option
RAM: int option
Storage: int option
GPU: string option
Monitor: string option
}
// Builder type
type ComputerBuilder() =
let mutable cpu = None
let mutable ram = None
let mutable storage = None
let mutable gpu = None
let mutable monitor = None
member this.SetCPU(value: string) =
cpu <- Some value
this
member this.SetRAM(value: int) =
ram <- Some value
this
member this.SetStorage(value: int) =
storage <- Some value
this
member this.SetGPU(value: string) =
gpu <- Some value
this
member this.SetMonitor(value: string) =
monitor <- Some value
this
member this.Build() =
{ CPU = cpu; RAM = ram; Storage = storage; GPU = gpu; Monitor = monitor }
// Functional builder
let createComputer () =
{ CPU = None; RAM = None; Storage = None; GPU = None; Monitor = None }
let withCPU cpu computer =
{ computer with CPU = Some cpu }
let withRAM ram computer =
{ computer with RAM = Some ram }
let withStorage storage computer =
{ computer with Storage = Some storage }
let withGPU gpu computer =
{ computer with GPU = Some gpu }
let withMonitor monitor computer =
{ computer with Monitor = Some monitor }
// Usage with builder
let builder = ComputerBuilder()
let computer1 = builder
.SetCPU("Intel i7")
.SetRAM(16)
.SetStorage(512)
.SetGPU("NVIDIA RTX 3060")
.Build()
printfn $"Computer1: {computer1}"
// Usage with functional builder
let computer2 =
createComputer ()
|> withCPU "Intel i7"
|> withRAM 16
|> withStorage 512
|> withGPU "NVIDIA RTX 3060"
printfn $"Computer2: {computer2}"Observer Pattern notifies observers of state changes. In F#, events and observables implement this pattern.
- Events:
Eventmodule - Observables:
Observablemodule - Subscription: Subscribe to events
- Notification: Trigger event notifications
- Use Cases: GUI, real-time updates, monitoring
// Observer Pattern in F#
open System
// Subject interface
type ISubject<'T> =
abstract member Attach: IObserver<'T> -> unit
abstract member Detach: IObserver<'T> -> unit
abstract member Notify: unit -> unit
// Observer interface
type IObserver<'T> =
abstract member Update: 'T -> unit
// Concrete subject
type Stock(symbol: string, initialPrice: float) =
let mutable price = initialPrice
let observers = System.Collections.Generic.List<IObserver<float>>()
member this.Symbol = symbol
member this.Price = price
member this.UpdatePrice(newPrice: float) =
if newPrice <> price then
price <- newPrice
this.Notify()
interface ISubject<float> with
member this.Attach(observer) =
observers.Add(observer)
member this.Detach(observer) =
observers.Remove(observer) |> ignore
member this.Notify() =
for observer in observers do
observer.Update(price)
// Concrete observer
type Investor(name: string) =
interface IObserver<float> with
member this.Update(price: float) =
printfn $"{name} notified: Stock price is now ${price}"
// Event-based observer
let eventObserver () =
let event = Event<int>()
event.Add(fun value -> printfn $"Event fired: {value}")
event.Trigger(42)
// Functional observer
let functionalObserver () =
let observable =
Observable.create (fun observer ->
for i in 1..5 do
observer.OnNext(i)
observer.OnCompleted()
System.IDisposable.Empty
)
observable.Subscribe(fun value ->
printfn $"Received: {value}"
) |> ignoreDecorator Pattern adds behavior to objects dynamically. In F#, decorators are implemented using function composition or object composition.
- Function Composition: Compose functions for decoration
- Object Composition: Wrap objects
- Functional Decorators:
decorate : (T -> T) -> T -> T - Use Cases: Logging, validation, caching
// Decorator Pattern in F#
open System
// Component interface
type ICoffee =
abstract member Cost: decimal
abstract member Description: string
// Concrete component
type SimpleCoffee() =
interface ICoffee with
member this.Cost = 5.00m
member this.Description = "Simple Coffee"
// Decorator base
type CoffeeDecorator(coffee: ICoffee) =
interface ICoffee with
member this.Cost = coffee.Cost
member this.Description = coffee.Description
// Concrete decorators
type MilkDecorator(coffee: ICoffee) =
inherit CoffeeDecorator(coffee)
interface ICoffee with
member this.Cost = base.Cost + 2.00m
member this.Description = base.Description + ", Milk"
type SugarDecorator(coffee: ICoffee) =
inherit CoffeeDecorator(coffee)
interface ICoffee with
member this.Cost = base.Cost + 1.00m
member this.Description = base.Description + ", Sugar"
type WhipDecorator(coffee: ICoffee) =
inherit CoffeeDecorator(coffee)
interface ICoffee with
member this.Cost = base.Cost + 3.00m
member this.Description = base.Description + ", Whip"
// Functional decorator
let decorateWithMilk coffee =
{ new ICoffee with
member this.Cost = coffee.Cost + 2.00m
member this.Description = coffee.Description + ", Milk" }
let decorateWithSugar coffee =
{ new ICoffee with
member this.Cost = coffee.Cost + 1.00m
member this.Description = coffee.Description + ", Sugar" }
// Usage
let coffee1 = SimpleCoffee() :> ICoffee
let coffee2 = MilkDecorator(coffee1) :> ICoffee
let coffee3 = SugarDecorator(coffee2) :> ICoffee
let coffee4 = WhipDecorator(coffee3) :> ICoffee
printfn $"Coffee1: {coffee1.Description} - ${coffee1.Cost}"
printfn $"Coffee2: {coffee2.Description} - ${coffee2.Cost}"
printfn $"Coffee3: {coffee3.Description} - ${coffee3.Cost}"
printfn $"Coffee4: {coffee4.Description} - ${coffee4.Cost}"Mediator Pattern centralizes communication between objects. In F#, message buses and pipelines implement this pattern.
- Message Bus: Central message routing
- Command/Query: Use discriminated unions
- Handlers: Register message handlers
- Use Cases: Complex systems, decoupling
// Mediator Pattern in F#
open System
// Mediator interface
type IMediator =
abstract member Send: Message -> unit
abstract member Register: MessageHandler -> unit
// Message types
type Message =
| UserCreated of string
| UserUpdated of string
| UserDeleted of string
// Message handler
type MessageHandler = Message -> unit
// Concrete mediator
type Mediator() =
let handlers = System.Collections.Generic.List<MessageHandler>()
interface IMediator with
member this.Send(message: Message) =
for handler in handlers do
handler(message)
member this.Register(handler: MessageHandler) =
handlers.Add(handler)
// Event handlers
let userCreatedHandler message =
match message with
| UserCreated name -> printfn $"User created: {name}"
| _ -> ()
let userUpdatedHandler message =
match message with
| UserUpdated name -> printfn $"User updated: {name}"
| _ -> ()
let userDeletedHandler message =
match message with
| UserDeleted name -> printfn $"User deleted: {name}"
| _ -> ()
// Usage
let mediator = Mediator() :> IMediator
mediator.Register(userCreatedHandler)
mediator.Register(userUpdatedHandler)
mediator.Register(userDeletedHandler)
mediator.Send(UserCreated "Alice")
mediator.Send(UserUpdated "Alice")
mediator.Send(UserDeleted "Alice")Chain of Responsibility passes requests along a chain of handlers. In F#, function composition and pipelines implement this pattern.
- Pipeline: Compose handlers as functions
- Handler:
'T -> 'T option - Chaining:
handler1 >> handler2 - Use Cases: Validation, logging, processing
// Chain of Responsibility Pattern in F#
open System
// Handler interface
type IHandler =
abstract member SetNext: IHandler -> IHandler
abstract member Handle: Request -> bool
// Request type
type Request = {
Type: string
Data: string
}
// Base handler
type BaseHandler() =
let mutable next: IHandler option = None
interface IHandler with
member this.SetNext(handler: IHandler) =
next <- Some handler
handler
member this.Handle(request: Request) =
match next with
| Some handler -> handler.Handle(request)
| None -> false
// Concrete handlers
type AuthHandler() =
inherit BaseHandler()
member this.HandleRequest(request: Request) =
if request.Type = "auth" then
printfn $"AuthHandler: Processing {request.Data}"
true
else
false
interface IHandler with
member this.Handle(request: Request) =
if this.HandleRequest(request) then
true
else
base.Handle(request)
type LogHandler() =
inherit BaseHandler()
member this.HandleRequest(request: Request) =
if request.Type = "log" then
printfn $"LogHandler: Processing {request.Data}"
true
else
false
interface IHandler with
member this.Handle(request: Request) =
if this.HandleRequest(request) then
true
else
base.Handle(request)
type ValidateHandler() =
inherit BaseHandler()
member this.HandleRequest(request: Request) =
if request.Type = "validate" then
printfn $"ValidateHandler: Processing {request.Data}"
true
else
false
interface IHandler with
member this.Handle(request: Request) =
if this.HandleRequest(request) then
true
else
base.Handle(request)
// Usage
let authHandler = AuthHandler() :> IHandler
let logHandler = LogHandler() :> IHandler
let validateHandler = ValidateHandler() :> IHandler
authHandler
.SetNext(logHandler)
.SetNext(validateHandler)
let request = { Type = "validate"; Data = "data" }
authHandler.Handle(request) |> ignoreState Pattern changes behavior based on state. In F#, discriminated unions model states and transitions.
- State Type: Discriminated union for states
- Transition: Functions that change state
- Behavior: Pattern match on state
- Use Cases: State machines, workflows, protocols
// State Pattern in F#
open System
// State interface
type IState =
abstract member Handle: Context -> unit
// Context
type Context() =
let mutable state: IState option = None
member this.SetState(s: IState) =
state <- Some s
printfn $"State changed to: {s.GetType().Name}"
member this.Request() =
match state with
| Some s -> s.Handle(this)
| None -> printfn "No state set"
// Concrete states
type ReadyState() =
interface IState with
member this.Handle(context: Context) =
printfn "Ready: Waiting for input"
context.SetState(ProcessingState())
type ProcessingState() =
interface IState with
member this.Handle(context: Context) =
printfn "Processing: Working on task"
context.SetState(CompletedState())
type CompletedState() =
interface IState with
member this.Handle(context: Context) =
printfn "Completed: Task finished"
context.SetState(ReadyState())
// Functional state
type State =
| Ready
| Processing
| Completed
let transition state =
match state with
| Ready ->
printfn "Ready: Waiting for input"
Processing
| Processing ->
printfn "Processing: Working on task"
Completed
| Completed ->
printfn "Completed: Task finished"
Ready
// Usage with OOP
let context = Context()
context.SetState(ReadyState())
context.Request()
context.Request()
context.Request()
// Usage with functional
let mutable state = Ready
for i in 1..3 do
state <- transition stateCommand Pattern encapsulates requests as objects. In F#, commands are discriminated unions with execute and undo functions.
- Command Type: Discriminated union
- Execute: Function to execute command
- Undo: Function to undo command
- History: Track command history
- Use Cases: Undo/redo, transactions, queuing
// Command Pattern in F#
open System
// Command interface
type ICommand =
abstract member Execute: unit -> unit
abstract member Undo: unit -> unit
// Receiver
type Calculator() =
let mutable currentValue = 0
member this.Add(value: int) =
currentValue <- currentValue + value
printfn $"Add: {value} -> {currentValue}"
member this.Subtract(value: int) =
currentValue <- currentValue - value
printfn $"Subtract: {value} -> {currentValue}"
member this.CurrentValue = currentValue
// Concrete commands
type AddCommand(calculator: Calculator, value: int) =
interface ICommand with
member this.Execute() =
calculator.Add(value)
member this.Undo() =
calculator.Subtract(value)
type SubtractCommand(calculator: Calculator, value: int) =
interface ICommand with
member this.Execute() =
calculator.Subtract(value)
member this.Undo() =
calculator.Add(value)
// Command invoker
type CommandInvoker() =
let history = System.Collections.Generic.List<ICommand>()
member this.Execute(command: ICommand) =
command.Execute()
history.Add(command)
member this.Undo() =
if history.Count > 0 then
let command = history.[history.Count - 1]
command.Undo()
history.RemoveAt(history.Count - 1)
// Usage
let calculator = Calculator()
let invoker = CommandInvoker()
let add5 = AddCommand(calculator, 5)
let add10 = AddCommand(calculator, 10)
let sub3 = SubtractCommand(calculator, 3)
invoker.Execute(add5) // Current: 5
invoker.Execute(add10) // Current: 15
invoker.Execute(sub3) // Current: 12
invoker.Undo() // Current: 15Memento Pattern captures and restores object state. In F#, records and discriminated unions are used for mementos.
- Memento: Capture state as data
- Originator: Create and restore mementos
- Caretaker: Manage memento history
- Use Cases: Undo/redo, snapshots, checkpointing
// Memento Pattern in F#
open System
// Memento
type Memento<'T> = {
State: 'T
Timestamp: DateTime
}
// Originator
type Originator<'T>() =
let mutable state: 'T option = None
member this.State
with get() = state
and set(value) = state <- Some value
member this.CreateMemento() =
match state with
| Some s -> Some { State = s; Timestamp = DateTime.Now }
| None -> None
member this.RestoreMemento(memento: Memento<'T>) =
state <- Some memento.State
// Caretaker
type Caretaker<'T>() =
let history = System.Collections.Generic.List<Memento<'T>>()
let mutable currentIndex = -1
member this.AddMemento(memento: Memento<'T>) =
history.Add(memento)
currentIndex <- history.Count - 1
member this.Undo() =
if currentIndex > 0 then
currentIndex <- currentIndex - 1
Some history.[currentIndex]
else
None
member this.Redo() =
if currentIndex < history.Count - 1 then
currentIndex <- currentIndex + 1
Some history.[currentIndex]
else
None
// Usage
let originator = Originator<string>()
let caretaker = Caretaker<string>()
originator.State <- "State 1"
match originator.CreateMemento() with
| Some m -> caretaker.AddMemento(m)
| None -> ()
originator.State <- "State 2"
match originator.CreateMemento() with
| Some m -> caretaker.AddMemento(m)
| None -> ()
// Undo
match caretaker.Undo() with
| Some m ->
originator.RestoreMemento(m)
printfn $"Restored: {originator.State}"
| None -> ()
// Redo
match caretaker.Redo() with
| Some m ->
originator.RestoreMemento(m)
printfn $"Restored: {originator.State}"
| None -> ()Visitor Pattern separates algorithms from data structures. In F#, pattern matching and active patterns implement this pattern.
- Visitor: Functions that process data
- Pattern Matching: Match on data structure
- Active Patterns: Custom matching logic
- Use Cases: AST traversal, serialization, validation
// Visitor Pattern in F#
open System
// Visitor interface
type IVisitor =
abstract member VisitCircle: Circle -> unit
abstract member VisitRectangle: Rectangle -> unit
abstract member VisitTriangle: Triangle -> unit
// Element interface
type IShape =
abstract member Accept: IVisitor -> unit
// Concrete elements
type Circle(radius: float) =
member this.Radius = radius
interface IShape with
member this.Accept(visitor: IVisitor) =
visitor.VisitCircle(this)
type Rectangle(width: float, height: float) =
member this.Width = width
member this.Height = height
interface IShape with
member this.Accept(visitor: IVisitor) =
visitor.VisitRectangle(this)
type Triangle(base': float, height: float) =
member this.Base = base'
member this.Height = height
interface IShape with
member this.Accept(visitor: IVisitor) =
visitor.VisitTriangle(this)
// Concrete visitors
type AreaVisitor() =
interface IVisitor with
member this.VisitCircle(circle: Circle) =
let area = Math.PI * circle.Radius * circle.Radius
printfn $"Circle area: {area}"
member this.VisitRectangle(rect: Rectangle) =
let area = rect.Width * rect.Height
printfn $"Rectangle area: {area}"
member this.VisitTriangle(tri: Triangle) =
let area = 0.5 * tri.Base * tri.Height
printfn $"Triangle area: {area}"
type PerimeterVisitor() =
interface IVisitor with
member this.VisitCircle(circle: Circle) =
let perimeter = 2.0 * Math.PI * circle.Radius
printfn $"Circle perimeter: {perimeter}"
member this.VisitRectangle(rect: Rectangle) =
let perimeter = 2.0 * (rect.Width + rect.Height)
printfn $"Rectangle perimeter: {perimeter}"
member this.VisitTriangle(tri: Triangle) =
let perimeter = tri.Base + 2.0 * sqrt (tri.Base * tri.Base / 4.0 + tri.Height * tri.Height)
printfn $"Triangle perimeter: {perimeter}"
// Usage
let shapes: IShape list = [
Circle(5.0) :> IShape
Rectangle(4.0, 6.0) :> IShape
Triangle(3.0, 4.0) :> IShape
]
let areaVisitor = AreaVisitor()
let perimeterVisitor = PerimeterVisitor()
for shape in shapes do
shape.Accept(areaVisitor)
shape.Accept(perimeterVisitor)Template Method Pattern defines the skeleton of an algorithm. In F#, higher-order functions implement this pattern.
- Template: Higher-order function
- Steps: Functions for each step
- Composition: Compose steps
- Use Cases: Algorithms, workflows, templates
// Template Method Pattern in F#
open System
// Abstract class with template method
type DataProcessor() =
abstract member LoadData: unit -> string
abstract member ProcessData: string -> string
abstract member SaveData: string -> unit
// Template method
member this.Process() =
let data = this.LoadData()
let processed = this.ProcessData(data)
this.SaveData(processed)
// Concrete implementations
type CSVProcessor() =
inherit DataProcessor()
override this.LoadData() =
printfn "Loading CSV data"
"CSV Data"
override this.ProcessData(data: string) =
printfn $"Processing CSV data: {data}"
"Processed CSV"
override this.SaveData(data: string) =
printfn $"Saving CSV data: {data}"
type XMLProcessor() =
inherit DataProcessor()
override this.LoadData() =
printfn "Loading XML data"
"XML Data"
override this.ProcessData(data: string) =
printfn $"Processing XML data: {data}"
"Processed XML"
override this.SaveData(data: string) =
printfn $"Saving XML data: {data}"
// Functional template method
let processData load process' save =
let data = load()
let processed = process' data
save processed
let csvLoad = fun () -> "CSV Data"
let csvProcess = fun data -> $"Processed CSV: {data}"
let csvSave = fun data -> printfn $"Saving: {data}"
// Usage
let csvProcessor = CSVProcessor()
csvProcessor.Process()
let xmlProcessor = XMLProcessor()
xmlProcessor.Process()
// Functional usage
processData csvLoad csvProcess csvSaveAdapter Pattern converts one interface to another. In F#, function composition and object expressions implement this pattern.
- Adapter: Convert between interfaces
- Function Adapter:
adapt : (A -> B) -> A -> B - Object Expression: Implement interfaces on the fly
- Use Cases: Integration, legacy code, APIs
// Adapter Pattern in F#
open System
// Target interface
type ITarget =
abstract member Request: unit -> string
// Adaptee
type Adaptee() =
member this.SpecificRequest() =
"Specific Request"
// Adapter
type Adapter(adaptee: Adaptee) =
interface ITarget with
member this.Request() =
let result = adaptee.SpecificRequest()
$"Adapted: {result}"
// Functional adapter
let adapt request =
fun () -> $"Adapted: {request()}"
// Usage
let adaptee = Adaptee()
let adapter = Adapter(adaptee) :> ITarget
printfn $"{adapter.Request()}"
// Functional usage
let specificRequest = fun () -> "Specific Request"
let adaptedRequest = adapt specificRequest
printfn $"{adaptedRequest()}"Bridge Pattern decouples abstraction from implementation. In F#, higher-order functions and records implement this pattern.
- Abstraction: Higher-order functions
- Implementation: Functions providing behavior
- Composition: Compose abstraction and implementation
- Use Cases: Cross-platform, flexibility
// Bridge Pattern in F#
open System
// Implementation interface
type IImplementation =
abstract member Operation: unit -> string
// Concrete implementations
type ImplementationA() =
interface IImplementation with
member this.Operation() = "Implementation A"
type ImplementationB() =
interface IImplementation with
member this.Operation() = "Implementation B"
// Abstraction
type Abstraction(implementation: IImplementation) =
member this.Operation() =
$"Abstraction: {implementation.Operation()}"
// Refined abstraction
type RefinedAbstraction(implementation: IImplementation) =
inherit Abstraction(implementation)
member this.ExtendedOperation() =
$"Extended: {implementation.Operation()}"
// Functional bridge
let createAbstraction impl =
fun () -> $"Abstraction: {impl()}"
let createRefinedAbstraction impl =
fun () -> $"Extended: {impl()}"
// Usage
let implA = ImplementationA() :> IImplementation
let implB = ImplementationB() :> IImplementation
let abstraction = Abstraction(implA)
printfn $"{abstraction.Operation()}"
let refined = RefinedAbstraction(implB)
printfn $"{refined.ExtendedOperation()}"
// Functional usage
let implAFunc = fun () -> "Implementation A"
let abstractionFunc = createAbstraction implAFunc
printfn $"{abstractionFunc()}"Composite Pattern composes objects into tree structures. In F#, discriminated unions naturally implement this pattern.
- Composite: Discriminated union
- Leaf: Terminal nodes
- Recursive: Recursive data structures
- Use Cases: Tree structures, UI, file systems
// Composite Pattern in F#
open System
// Component interface
type IComponent =
abstract member Operation: unit -> string
abstract member Add: IComponent -> unit
abstract member Remove: IComponent -> unit
abstract member GetChild: int -> IComponent
// Leaf
type Leaf(name: string) =
interface IComponent with
member this.Operation() = $"Leaf: {name}"
member this.Add(component: IComponent) =
failwith "Cannot add to leaf"
member this.Remove(component: IComponent) =
failwith "Cannot remove from leaf"
member this.GetChild(index: int) =
failwith "Leaf has no children"
// Composite
type Composite(name: string) =
let children = System.Collections.Generic.List<IComponent>()
interface IComponent with
member this.Operation() =
let childResults =
children
|> Seq.map (fun c -> c.Operation())
|> String.concat ", "
$"Composite: {name} [{childResults}]"
member this.Add(component: IComponent) =
children.Add(component)
member this.Remove(component: IComponent) =
children.Remove(component) |> ignore
member this.GetChild(index: int) =
children.[index]
// Usage
let root = Composite("Root") :> IComponent
let leaf1 = Leaf("Leaf 1") :> IComponent
let leaf2 = Leaf("Leaf 2") :> IComponent
let composite = Composite("Composite") :> IComponent
composite.Add(leaf1)
composite.Add(leaf2)
root.Add(composite)
printfn $"{root.Operation()}"
// Functional composite
let createLeaf name = fun () -> $"Leaf: {name}"
let createComposite name children =
fun () ->
let childResults =
children |> Seq.map (fun c -> c()) |> String.concat ", "
$"Composite: {name} [{childResults}]"
let functionalLeaf1 = createLeaf "Leaf 1"
let functionalLeaf2 = createLeaf "Leaf 2"
let functionalComposite = createComposite "Composite" [functionalLeaf1; functionalLeaf2]
printfn $"{functionalComposite()}"Flyweight Pattern shares objects to reduce memory usage. In F#, memoization and caching implement this pattern.
- Flyweight Factory: Cache shared objects
- Shared State: State shared between objects
- Unique State: State unique to each instance
- Use Cases: Large numbers of objects, caching
// Flyweight Pattern in F#
open System
open System.Collections.Generic
// Flyweight interface
type IFlyweight<'T> =
abstract member Operation: 'T -> unit
// Concrete flyweight
type Flyweight<'T>(sharedState: 'T) =
interface IFlyweight<'T> with
member this.Operation(uniqueState: 'T) =
printfn $"Shared: {sharedState}, Unique: {uniqueState}"
// Flyweight factory
type FlyweightFactory<'T>() =
let flyweights = Dictionary<'T, IFlyweight<'T>>()
member this.GetFlyweight(state: 'T) =
match flyweights.TryGetValue(state) with
| true, flyweight -> flyweight
| false, _ ->
let flyweight = Flyweight(state) :> IFlyweight<'T>
flyweights.Add(state, flyweight)
flyweight
// Usage
let factory = FlyweightFactory<string>()
let flyweight1 = factory.GetFlyweight("Shared State")
flyweight1.Operation("Unique State 1")
flyweight1.Operation("Unique State 2")
let flyweight2 = factory.GetFlyweight("Shared State")
flyweight2.Operation("Unique State 3")
printfn $"Are they same? {Object.ReferenceEquals(flyweight1, flyweight2)}"Facade Pattern provides a simplified interface to a complex subsystem. In F#, functions and modules implement this pattern.
- Facade: Simplified API
- Module: Group related functions
- Composition: Compose subsystem operations
- Use Cases: Complex systems, libraries, APIs
// Facade Pattern in F#
open System
// Subsystems
type SubsystemA() =
member this.OperationA() =
printfn "SubsystemA: Operation A"
type SubsystemB() =
member this.OperationB() =
printfn "SubsystemB: Operation B"
type SubsystemC() =
member this.OperationC() =
printfn "SubsystemC: Operation C"
// Facade
type Facade() =
let subsystemA = SubsystemA()
let subsystemB = SubsystemB()
let subsystemC = SubsystemC()
member this.Operation1() =
printfn "Facade: Operation 1"
subsystemA.OperationA()
subsystemB.OperationB()
member this.Operation2() =
printfn "Facade: Operation 2"
subsystemB.OperationB()
subsystemC.OperationC()
// Functional facade
let createFacade () =
let subsystemA = SubsystemA()
let subsystemB = SubsystemB()
let subsystemC = SubsystemC()
let operation1 () =
printfn "Facade: Operation 1"
subsystemA.OperationA()
subsystemB.OperationB()
let operation2 () =
printfn "Facade: Operation 2"
subsystemB.OperationB()
subsystemC.OperationC()
(operation1, operation2)
// Usage
let facade = Facade()
facade.Operation1()
facade.Operation2()
// Functional usage
let (op1, op2) = createFacade()
op1()
op2()Proxy Pattern provides a surrogate for another object. In F#, lazy evaluation and memoization implement this pattern.
- Proxy: Lazy or virtual proxy
- Lazy: Delay object creation
- Virtual: Lazy loading
- Use Cases: Lazy loading, access control, logging
// Proxy Pattern in F#
open System
// Subject interface
type ISubject =
abstract member Request: unit -> unit
// Real subject
type RealSubject() =
interface ISubject with
member this.Request() =
printfn "RealSubject: Handling request"
// Proxy
type Proxy() =
let mutable realSubject: RealSubject option = None
let getRealSubject () =
match realSubject with
| Some rs -> rs
| None ->
let rs = RealSubject()
realSubject <- Some rs
rs
interface ISubject with
member this.Request() =
printfn "Proxy: Checking access..."
let rs = getRealSubject()
rs.Request()
printfn "Proxy: Logging request"
// Virtual proxy
type VirtualProxy() =
let mutable realSubject: RealSubject option = None
interface ISubject with
member this.Request() =
match realSubject with
| Some rs -> rs.Request()
| None ->
printfn "VirtualProxy: Creating real subject..."
let rs = RealSubject()
realSubject <- Some rs
rs.Request()
// Usage
let proxy = Proxy() :> ISubject
proxy.Request()
proxy.Request()Pipeline Pattern processes data through a series of stages. In F#, the pipe operator and function composition implement this pattern.
- Pipeline:
|>operator - Stages: Functions in sequence
- Composition:
>>and<< - Use Cases: Data processing, transformations, ETL
// Pipeline Pattern in F#
open System
// Pipeline step types
type PipelineStep<'T> = 'T -> 'T
// Pipeline builder
type PipelineBuilder<'T>() =
let mutable steps: PipelineStep<'T> list = []
member this.AddStep(step: PipelineStep<'T>) =
steps <- steps @ [step]
this
member this.Build() =
fun (input: 'T) ->
steps |> List.fold (fun acc step -> step acc) input
// Functional pipeline
let createPipeline steps input =
steps |> List.fold (fun acc step -> step acc) input
// Pipeline steps
let add10 x = x + 10
let multiply2 x = x * 2
let square x = x * x
let toString x = x.ToString()
// Usage with builder
let builder = PipelineBuilder<int>()
let pipeline = builder
.AddStep(add10)
.AddStep(multiply2)
.AddStep(square)
.Build()
let result = pipeline 5
printfn $"Pipeline result: {result}"
// Functional pipeline
let steps = [add10; multiply2; square]
let result2 = createPipeline steps 5
printfn $"Functional pipeline: {result2}"
// Pipeline with different types
let stringPipeline = [
(fun (s: string) -> s.ToUpper())
(fun s -> s + "!")
]
let result3 = createPipeline stringPipeline "hello"
printfn $"String pipeline: {result3}"Fluent Interface provides method chaining for readable code. In F#, computation expressions and record updates implement this pattern.
- Method Chaining: Return
this - Computation Expressions:
builder { ... } - Record Updates:
{ record with Field = value } - Use Cases: Configuration, builders, DSLs
// Fluent Interface Pattern in F#
open System
// Fluent builder
type FluentBuilder() =
let mutable name = ""
let mutable age = 0
let mutable email = ""
member this.WithName(n: string) =
name <- n
this
member this.WithAge(a: int) =
age <- a
this
member this.WithEmail(e: string) =
email <- e
this
member this.Build() =
{| Name = name; Age = age; Email = email |}
// Fluent interface for validation
type Validator() =
let mutable errors = []
member this.Required(value: string) =
if String.IsNullOrEmpty(value) then
errors <- "Value is required" :: errors
this
member this.MinLength(value: string, length: int) =
if not (String.IsNullOrEmpty(value)) && value.Length < length then
errors <- $"Minimum length is {length}" :: errors
this
member this.MaxLength(value: string, length: int) =
if not (String.IsNullOrEmpty(value)) && value.Length > length then
errors <- $"Maximum length is {length}" :: errors
this
member this.ValidEmail(value: string) =
if not (String.IsNullOrEmpty(value)) && not (value.Contains "@") then
errors <- "Invalid email format" :: errors
this
member this.Validate() =
errors
// Usage
let builder = FluentBuilder()
let person = builder
.WithName("Alice")
.WithAge(25)
.WithEmail("alice@email.com")
.Build()
printfn $"Person: {person}"
let validator = Validator()
let errors = validator
.Required("")
.MinLength("test", 5)
.ValidEmail("invalid")
.Validate()
printfn $"Validation errors: {errors}"Reactive Extensions (Rx) provide reactive programming with observables. F# has good support for Rx through the System.Reactive library.
- Observables:
Observablemodule - Operators:
map,filter,merge,scan - Subjects:
Subject,BehaviorSubject - Subscriptions:
Subscribeand dispose - Use Cases: Reactive UI, streaming data, event processing
// Reactive Extensions in F#
open System
open System.Reactive
open System.Reactive.Linq
// Observable creation
let numbers = Observable.Range(1, 10)
let strings = Observable.Return("Hello")
// Observable transformations
let doubled = numbers.Select(fun x -> x * 2)
let filtered = numbers.Where(fun x -> x % 2 = 0)
let projected = numbers.Select(fun x -> $"Number: {x}")
// Observable aggregation
let sum = numbers.Sum()
let average = numbers.Average()
let max = numbers.Max()
// Subscription
let subscription =
numbers.Subscribe(
(fun x -> printfn $"Next: {x}"),
(fun ex -> printfn $"Error: {ex}"),
(fun () -> printfn "Completed")
)
// Hot observable
let subject = new Subject<int>()
subject.OnNext(1)
subject.OnNext(2)
// Cold observable
let cold = Observable.Interval(TimeSpan.FromSeconds(1))
let coldSubscription = cold.Subscribe(fun x -> printfn $"Interval: {x}")
// Combining observables
let combined = Observable.CombineLatest(numbers, strings, fun n s -> $"{n}: {s}")
// Buffering
let buffered = numbers.Buffer(2)
// Throttling
let throttled = numbers.Throttle(TimeSpan.FromMilliseconds(500))
// Debouncing
let debounced = numbers.Debounce(TimeSpan.FromMilliseconds(500))
// Error handling
let withError = numbers.Catch(fun ex -> Observable.Return(-1))
// Disposal
subscription.Dispose()
coldSubscription.Dispose()Async Streams are sequences that produce values asynchronously. F# supports async sequences with AsyncSeq and taskSeq.
- AsyncSeq:
asyncSeq { ... } - taskSeq:
taskSeq { ... } - Operations:
map,filter,take - Use Cases: Stream processing, data pipelines, real-time data
// Async Streams in F#
open System
open System.Threading.Tasks
// Async sequence
let asyncNumbers () =
asyncSeq {
for i in 1..10 do
do! Async.Sleep 100
yield i
}
// Processing async streams
let processAsyncNumbers () =
async {
let! result =
asyncNumbers ()
|> AsyncSeq.map (fun x -> x * 2)
|> AsyncSeq.filter (fun x -> x % 2 = 0)
|> AsyncSeq.take 5
|> AsyncSeq.toList
printfn $"Processed: {result}"
}
// Async streams with tasks
let taskNumbers () =
taskSeq {
for i in 1..10 do
do! Task.Delay 100 |> Async.AwaitTask
yield i
}
// Processing task streams
let processTaskNumbers () =
task {
let! result =
taskNumbers ()
|> TaskSeq.map (fun x -> x * 2)
|> TaskSeq.filter (fun x -> x % 2 = 0)
|> TaskSeq.take 5
|> TaskSeq.toList
printfn $"Processed: {result}"
}
// Usage
async {
do! processAsyncNumbers ()
} |> Async.RunSynchronously
task {
do! processTaskNumbers ()
} |> Task.RunSQL Provider generates types for database access. It provides compile-time safety for SQL queries.
- Connection:
SqlDataProvider - Queries:
query { ... } - CRUD: Create, read, update, delete operations
- Stored Procedures: Call stored procedures
- Use Cases: Database access, data persistence
// F# with SQL (SQL Provider)
open FSharp.Data
open System
// SQL Provider
// type db = SqlDataProvider<"Server=localhost;Database=test;Integrated Security=true">
// let ctx = db.GetDataContext()
// Query data
// let getUsers () =
// query {
// for user in ctx.Users do
// select (user.Name, user.Age)
// take 10
// }
// Insert data
// let insertUser name age =
// let user = ctx.Users.Create()
// user.Name <- name
// user.Age <- age
// ctx.SubmitUpdates()
// Update data
// let updateUser userId name age =
// query {
// for user in ctx.Users do
// where (user.Id = userId)
// select user
// }
// |> Seq.iter (fun user ->
// user.Name <- name
// user.Age <- age
// )
// ctx.SubmitUpdates()
// Delete data
// let deleteUser userId =
// query {
// for user in ctx.Users do
// where (user.Id = userId)
// select user
// }
// |> Seq.iter ctx.Users.DeleteOnSubmit
// ctx.SubmitUpdates()
// Stored procedure
// let callStoredProc name =
// ctx.Procedures.GetUserByName name
// |> Seq.map (fun row -> row.Name, row.Age)
// |> Seq.toListEntity Framework is an ORM for .NET. F# works with Entity Framework for database access and mapping.
- DbContext:
DbContextinheritance - Entities:
[<CLIMutable>]record types - Queries:
query { ... }or LINQ - Migrations: Database migrations
- Use Cases: Data access, ORM, database-first
// F# with Entity Framework
open System
open System.Data.Entity
// Entity model
type User() =
member val Id = 0 with get, set
member val Name = "" with get, set
member val Age = 0 with get, set
member val Email = "" with get, set
type Post() =
member val Id = 0 with get, set
member val Title = "" with get, set
member val Content = "" with get, set
member val UserId = 0 with get, set
member val User = null with get, set
// Database context
type AppDbContext() =
inherit DbContext("name=DefaultConnection")
member val Users = base.Set<User>() with get, set
member val Posts = base.Set<Post>() with get, set
// Repository functions
let getUser id (ctx: AppDbContext) =
ctx.Users |> Seq.tryFind (fun u -> u.Id = id)
let getUsers (ctx: AppDbContext) =
ctx.Users |> Seq.toList
let addUser (user: User) (ctx: AppDbContext) =
ctx.Users.Add(user) |> ignore
ctx.SaveChanges() |> ignore
let updateUser (id: int) (name: string) (age: int) (ctx: AppDbContext) =
match getUser id ctx with
| Some user ->
user.Name <- name
user.Age <- age
ctx.SaveChanges() |> ignore
true
| None -> false
let deleteUser (id: int) (ctx: AppDbContext) =
match getUser id ctx with
| Some user ->
ctx.Users.Remove(user) |> ignore
ctx.SaveChanges() |> ignore
true
| None -> false
// Usage
// use ctx = new AppDbContext()
// let users = getUsers ctx
// let user = { Id = 0; Name = "Alice"; Age = 25; Email = "alice@email.com" }
// addUser user ctxDapper is a micro-ORM for .NET. In F#, it provides simple and efficient database access with SQL queries.
- Query:
conn.Query - Execute:
conn.Execute - Mapping: Map to record types
- Parameters: Parameterized queries
- Use Cases: Performance-critical data access
// F# with Dapper
open System
open System.Data
open System.Data.SqlClient
open Dapper
// Connection functions
let connectionString = "Server=localhost;Database=test;Integrated Security=true"
let withConnection f =
use conn = new SqlConnection(connectionString)
conn.Open()
f conn
// Query functions
let getUsers () =
withConnection (fun conn ->
conn.Query<User>("SELECT * FROM Users")
|> Seq.toList
)
let getUser id =
withConnection (fun conn ->
conn.QueryFirstOrDefault<User>("SELECT * FROM Users WHERE Id = @Id", {| Id = id |})
)
let insertUser name age email =
withConnection (fun conn ->
conn.Execute("INSERT INTO Users (Name, Age, Email) VALUES (@Name, @Age, @Email)",
{| Name = name; Age = age; Email = email |})
)
let updateUser id name age email =
withConnection (fun conn ->
conn.Execute("UPDATE Users SET Name = @Name, Age = @Age, Email = @Email WHERE Id = @Id",
{| Id = id; Name = name; Age = age; Email = email |})
)
let deleteUser id =
withConnection (fun conn ->
conn.Execute("DELETE FROM Users WHERE Id = @Id", {| Id = id |})
)
// Usage
// let users = getUsers ()
// let user = getUser 1
// let affected = insertUser "Alice" 25 "alice@email.com"
// let affected = updateUser 1 "Bob" 30 "bob@email.com"
// let affected = deleteUser 1Azure Functions can be written in F# for serverless applications. F# provides a functional approach to serverless development.
- Triggers: HTTP, Timer, Blob, Queue
- Bindings: Input and output bindings
- Async: Use async workflows
- Use Cases: Serverless, event-driven, microservices
// F# with Azure Functions
open System
open System.IO
open Microsoft.Azure.WebJobs
open Microsoft.Extensions.Logging
// Timer trigger
let RunTimer ([<TimerTrigger("0 */5 * * * *")>] timer: TimerInfo, log: ILogger) =
log.LogInformation($"Timer function executed at: {DateTime.Now}")
// HTTP trigger
let RunHttp ([<HttpTrigger("GET", "POST")>] req: HttpRequestMessage, log: ILogger) =
async {
log.LogInformation("HTTP trigger function processed a request.")
let response = req.CreateResponse(HttpStatusCode.OK, "Hello, F#!")
return response
} |> Async.StartAsTask
// Blob trigger
let RunBlob ([<BlobTrigger("input/{name}")>] blobStream: Stream, name: string, log: ILogger) =
use reader = new StreamReader(blobStream)
let content = reader.ReadToEnd()
log.LogInformation($"Blob trigger processed file: {name}, Content: {content}")
// Queue trigger
let RunQueue ([<QueueTrigger("myqueue")>] message: string, log: ILogger) =
log.LogInformation($"Queue trigger processed: {message}")
// Output binding
let RunWithOutput ([<QueueTrigger("inputqueue")>] message: string, [<Queue("outputqueue")>] outputQueue: ICollector<string>, log: ILogger) =
log.LogInformation($"Processing: {message}")
outputQueue.Add($"Processed: {message}")Docker containers can run F# applications. Docker provides a consistent environment for development and deployment.
- Dockerfile: Build image configuration
- Multi-stage Builds: Build and runtime stages
- Environment Variables: Configuration via env
- Use Cases: Containerization, deployment, microservices
// F# with Docker
open System
open System.IO
// Dockerfile
// FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
// WORKDIR /app
// COPY . .
// RUN dotnet restore
// RUN dotnet build --configuration Release
// RUN dotnet publish --configuration Release --output out
// FROM mcr.microsoft.com/dotnet/runtime:6.0 AS runtime
// WORKDIR /app
// COPY --from=build /app/out .
// ENTRYPOINT ["dotnet", "MyApp.dll"]
// Docker compose
// version: '3.8'
// services:
// app:
// build: .
// ports:
// - "8080:80"
// environment:
// - ASPNETCORE_ENVIRONMENT=Development
// volumes:
// - ./data:/app/data
// Environment variables
let getEnvVar name =
Environment.GetEnvironmentVariable(name) ?? "default"
let getConfigFromDocker () =
{
ConnectionString = getEnvVar "DB_CONNECTION"
Port = getEnvVar "PORT" |> int |> Option.defaultValue 8080
Environment = getEnvVar "ENVIRONMENT"
}
// Health check
let healthCheck () =
printfn "Health check: OK"
true
// Graceful shutdown
let shutdown () =
printfn "Shutting down gracefully..."
// Cleanup resourcesKubernetes orchestrates containers running F# applications. It provides scaling, service discovery, and deployment management.
- Deployment: Kubernetes deployment YAML
- Service: Expose applications
- ConfigMap: Configuration management
- Secrets: Secure configuration
- Use Cases: Container orchestration, scaling, microservices
// F# with Kubernetes
open System
open System.IO
// Deployment YAML
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: myapp
// spec:
// replicas: 3
// selector:
// matchLabels:
// app: myapp
// template:
// metadata:
// labels:
// app: myapp
// spec:
// containers:
// - name: myapp
// image: myapp:latest
// ports:
// - containerPort: 8080
// env:
// - name: DB_CONNECTION
// valueFrom:
// secretKeyRef:
// name: db-secret
// key: connection
// livenessProbe:
// httpGet:
// path: /health
// port: 8080
// initialDelaySeconds: 10
// periodSeconds: 5
// readinessProbe:
// httpGet:
// path: /ready
// port: 8080
// initialDelaySeconds: 5
// periodSeconds: 5
// Service YAML
// apiVersion: v1
// kind: Service
// metadata:
// name: myapp-service
// spec:
// selector:
// app: myapp
// ports:
// - protocol: TCP
// port: 80
// targetPort: 8080
// type: LoadBalancer
// ConfigMap
// apiVersion: v1
// kind: ConfigMap
// metadata:
// name: myapp-config
// data:
// appsettings.json: |
// {
// "Logging": true,
// "Environment": "Production"
// }
// Secret
// apiVersion: v1
// kind: Secret
// metadata:
// name: db-secret
// type: Opaque
// data:
// connection: Y29ubmVjdGlvbiBzdHJpbmc=CI/CD for F# applications can be set up with GitHub Actions, Azure DevOps, or other CI/CD platforms for automated build, test, and deploy.
- Build:
dotnet build - Test:
dotnet test - Publish:
dotnet publish - Deploy: Deploy to cloud platforms
- Use Cases: Automated builds, testing, deployment
// F# with CI/CD (GitHub Actions)
open System
open System.IO
// GitHub Actions workflow
// name: Build and Test
// on:
// push:
// branches: [ main ]
// pull_request:
// branches: [ main ]
// jobs:
// build:
// runs-on: ubuntu-latest
// steps:
// - uses: actions/checkout@v2
// - name: Setup .NET
// uses: actions/setup-dotnet@v1
// with:
// dotnet-version: 6.0.x
// - name: Restore dependencies
// run: dotnet restore
// - name: Build
// run: dotnet build --configuration Release --no-restore
// - name: Test
// run: dotnet test --configuration Release --no-build --verbosity normal
// - name: Publish
// run: dotnet publish --configuration Release --output out
// Build script
let build () =
printfn "Building application..."
let result = Shell.Exec("dotnet", "build --configuration Release")
if result <> 0 then
failwith "Build failed"
printfn "Build succeeded"
// Test script
let test () =
printfn "Running tests..."
let result = Shell.Exec("dotnet", "test --configuration Release")
if result <> 0 then
failwith "Tests failed"
printfn "Tests succeeded"
// Publish script
let publish () =
printfn "Publishing application..."
let result = Shell.Exec("dotnet", "publish --configuration Release --output out")
if result <> 0 then
failwith "Publish failed"
printfn "Publish succeeded"
// Deploy script
let deploy () =
printfn "Deploying application..."
// Deploy to Azure/AWS/other
printfn "Deployment complete"Monitoring and Logging are essential for production F# applications. Use metrics, structured logging, and application insights.
- Metrics: Track application metrics
- Structured Logging: Log structured data
- Application Insights: Azure monitoring
- Health Checks: Monitor application health
- Use Cases: Production monitoring, debugging, performance
// F# with Monitoring and Logging
open System
open System.Diagnostics
// Application metrics
type Metrics() =
let mutable requestCount = 0
let mutable errorCount = 0
let mutable responseTime = TimeSpan.Zero
member this.IncrementRequests() =
requestCount <- requestCount + 1
member this.IncrementErrors() =
errorCount <- errorCount + 1
member this.RecordResponseTime(time: TimeSpan) =
responseTime <- time
member this.Report() =
printfn $"Requests: {requestCount}"
printfn $"Errors: {errorCount}"
printfn $"Avg Response Time: {responseTime.TotalMilliseconds}ms"
// Custom logger with metrics
type MetricsLogger(metrics: Metrics) =
let mutable logFile = "app.log"
member this.LogInfo(message: string) =
let timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
printfn $"[{timestamp}] INFO: {message}"
metrics.IncrementRequests()
member this.LogError(message: string) =
let timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
printfn $"[{timestamp}] ERROR: {message}"
metrics.IncrementErrors()
member this.LogWithDuration(message: string, duration: TimeSpan) =
this.LogInfo(message)
metrics.RecordResponseTime(duration)
// Performance monitoring
let monitorPerformance operation =
let sw = Stopwatch()
sw.Start()
let result = operation()
sw.Stop()
printfn $"Operation completed in {sw.ElapsedMilliseconds}ms"
result
// Usage
let metrics = Metrics()
let logger = MetricsLogger(metrics)
logger.LogInfo("Application started")
logger.LogInfo("Processing request")
let result = monitorPerformance (fun () ->
System.Threading.Thread.Sleep(100)
"Result"
)
logger.LogWithDuration("Request processed", TimeSpan.FromMilliseconds(100))
metrics.Report()Microservices in F# use functional programming to build small, independent services. F# is well-suited for microservices architecture.
- Services: Independent deployable services
- API Gateway: Route requests to services
- Circuit Breaker: Handle failures
- Service Discovery: Find services
- Use Cases: Distributed systems, scalability, resilience
// F# with Microservices
open System
open System.Net.Http
open System.Text.Json
// Service discovery
type ServiceRegistry() =
let services = System.Collections.Concurrent.ConcurrentDictionary<string, string>()
member this.Register(name: string, url: string) =
services.[name] <- url
member this.GetService(name: string) =
services.TryGetValue(name) |> ignore
services.[name]
// API Gateway
type ApiGateway(serviceRegistry: ServiceRegistry) =
let httpClient = new HttpClient()
member this.Forward(request: HttpRequestMessage, serviceName: string) =
async {
let serviceUrl = serviceRegistry.GetService(serviceName)
let forwardUrl = $"{serviceUrl}{request.RequestUri.PathAndQuery}"
let forwardRequest = new HttpRequestMessage(request.Method, forwardUrl)
// Copy headers
for header in request.Headers do
forwardRequest.Headers.Add(header.Key, header.Value)
try
let! response = httpClient.SendAsync(forwardRequest) |> Async.AwaitTask
return response
with
| ex ->
printfn $"Error forwarding request: {ex.Message}"
return new HttpResponseMessage(System.Net.HttpStatusCode.ServiceUnavailable)
}
// Circuit Breaker
type CircuitBreaker(maxFailures: int, timeout: TimeSpan) =
let mutable failures = 0
let mutable state = "Closed"
let mutable lastFailure = DateTime.MinValue
member this.Execute(operation: unit -> 'T) =
if state = "Open" && (DateTime.Now - lastFailure) > timeout then
state <- "HalfOpen"
printfn "Circuit breaker: Half-open"
if state = "Open" then
failwith "Circuit breaker is open"
try
let result = operation()
if state = "HalfOpen" then
state <- "Closed"
failures <- 0
printfn "Circuit breaker: Closed"
result
with
| ex ->
failures <- failures + 1
lastFailure <- DateTime.Now
if failures >= maxFailures then
state <- "Open"
printfn "Circuit breaker: Open"
raise exgRPC is a high-performance RPC framework. F# can implement gRPC services with protocol buffers for efficient communication.
- Protocol Buffers: Define service contracts
- Server: Implement gRPC services
- Client: Call gRPC services
- Streaming: Bidirectional streaming
- Use Cases: Microservices, high-performance APIs
// F# with gRPC
open System
open System.Threading.Tasks
open Grpc.Core
// Protobuf definition
// syntax = "proto3";
//
// service Greeter {
// rpc SayHello (HelloRequest) returns (HelloReply);
// }
//
// message HelloRequest {
// string name = 1;
// }
//
// message HelloReply {
// string message = 1;
// }
// Server implementation
type GreeterService() =
inherit Greeter.GreeterBase()
override this.SayHello(request: HelloRequest, context: ServerCallContext) =
Task.FromResult(HelloReply(Message = $"Hello, {request.Name}!"))
// Server startup
let startGrpcServer () =
let server = new Server()
server.Services.Add(Greeter.BindService(GreeterService()))
server.Ports.Add(new ServerPort("localhost", 5000, ServerCredentials.Insecure))
server.Start()
printfn "gRPC server started on port 5000"
server
// Client
let createGrpcClient () =
let channel = new Channel("localhost:5000", ChannelCredentials.Insecure)
let client = Greeter.GreeterClient(channel)
client
// Usage
let callGrpcService (name: string) =
async {
let client = createGrpcClient()
let request = HelloRequest(Name = name)
try
let! response = client.SayHelloAsync(request) |> Async.AwaitTask
printfn $"Response: {response.Message}"
with
| ex -> printfn $"Error: {ex.Message}"
}SignalR provides real-time web functionality. F# can use SignalR for building real-time applications with WebSockets.
- Hub: Real-time communication hub
- Clients: Connected clients
- Methods: Client and server methods
- Groups: Group communication
- Use Cases: Chat, live updates, real-time collaboration
// F# with SignalR
open System
open System.Threading.Tasks
open Microsoft.AspNetCore.SignalR
// Hub
type ChatHub() =
inherit Hub()
member this.SendMessage(user: string, message: string) =
this.Clients.All.SendAsync("ReceiveMessage", user, message)
member this.JoinGroup(groupName: string) =
this.Groups.AddToGroupAsync(this.Context.ConnectionId, groupName)
member this.LeaveGroup(groupName: string) =
this.Groups.RemoveFromGroupAsync(this.Context.ConnectionId, groupName)
member this.SendToGroup(groupName: string, user: string, message: string) =
this.Clients.Group(groupName).SendAsync("ReceiveMessage", user, message)
// Hub with authentication
type SecureChatHub() =
inherit Hub()
member this.SendMessage(message: string) =
let user = this.Context.User.Identity.Name
this.Clients.All.SendAsync("ReceiveMessage", user, message)
// Usage in Startup
// public void ConfigureServices(IServiceCollection services)
// {
// services.AddSignalR();
// }
//
// public void Configure(IApplicationBuilder app)
// {
// app.UseEndpoints(endpoints =>
// {
// endpoints.MapHub<ChatHub>("/chatHub");
// });
// }
// Client usage
// let connection = new HubConnectionBuilder()
// .WithUrl("/chatHub")
// .Build()
//
// connection.On<string, string>("ReceiveMessage", (user, message) =>
// printfn $"{user}: {message}"
// )
//
// connection.StartAsync()
// connection.InvokeAsync("SendMessage", "Alice", "Hello World!")WPF and MAUI are UI frameworks that can be used with F#. F# provides a functional approach to building desktop and mobile applications.
- WPF: Windows desktop applications
- MAUI: Cross-platform mobile and desktop
- MVVM: Model-View-ViewModel pattern
- XAML: UI markup with F# code-behind
- Use Cases: Desktop apps, mobile apps, cross-platform
// F# with WPF/MAUI
open System
open System.Windows
open System.Windows.Controls
// WPF Application
type MainWindow() as this =
inherit Window()
let button = Button(Content = "Click Me", Width = 100, Height = 30)
let label = Label(Content = "Hello, F# WPF!", Margin = Thickness(10))
do
button.Click.Add(fun _ ->
label.Content <- "Button clicked!"
)
let stackPanel = StackPanel()
stackPanel.Children.Add(label) |> ignore
stackPanel.Children.Add(button) |> ignore
this.Content <- stackPanel
this.Title <- "F# WPF Application"
this.Width <- 300
this.Height <- 200
// MAUI Application
type App() =
inherit Application()
do
let page = ContentPage()
let label = Label(Text = "Hello, F# MAUI!", HorizontalOptions = LayoutOptions.Center, VerticalOptions = LayoutOptions.Center)
page.Content <- label
this.MainPage <- page
// View Model
type MainViewModel() =
let mutable text = "Hello, F#!"
let mutable count = 0
member this.Text
with get() = text
and set(value) = text <- value
member this.Count
with get() = count
and set(value) = count <- value
member this.Increment() =
this.Count <- this.Count + 1
this.Text <- $"Clicked: {this.Count}"A Complete E-Commerce System in F# demonstrates functional programming with domain modeling, business logic, and service composition.
- Domain Modeling: Use records and discriminated unions
- Business Logic: Pure functions for operations
- Services: Domain services with dependency injection
- Persistence: Database access with type providers
- Use Cases: E-commerce, inventory, order processing
// Complete E-Commerce System in F#
open System
open System.Collections.Generic
// Domain types
type ProductId = ProductId of Guid
type OrderId = OrderId of Guid
type CustomerId = CustomerId of Guid
type Product = {
Id: ProductId
Name: string
Price: decimal
Stock: int
}
type Customer = {
Id: CustomerId
Name: string
Email: string
Address: string
}
type OrderItem = {
ProductId: ProductId
Quantity: int
Price: decimal
}
type OrderStatus =
| Pending
| Processing
| Shipped
| Delivered
| Cancelled
type Order = {
Id: OrderId
CustomerId: CustomerId
Items: OrderItem list
Status: OrderStatus
CreatedAt: DateTime
UpdatedAt: DateTime
}
// Domain services
module ProductService =
let getProduct id (products: Product list) =
products |> List.tryFind (fun p -> p.Id = id)
let updateStock id quantity products =
products
|> List.map (fun p ->
if p.Id = id then
{ p with Stock = p.Stock - quantity }
else p
)
let isAvailable id quantity products =
products
|> List.tryFind (fun p -> p.Id = id)
|> Option.map (fun p -> p.Stock >= quantity)
|> Option.defaultValue false
module OrderService =
let createOrder customerId items =
{
Id = OrderId (Guid.NewGuid())
CustomerId = customerId
Items = items
Status = Pending
CreatedAt = DateTime.UtcNow
UpdatedAt = DateTime.UtcNow
}
let updateStatus order status =
{ order with Status = status; UpdatedAt = DateTime.UtcNow }
let calculateTotal order =
order.Items |> List.sumBy (fun item -> item.Price * decimal item.Quantity)
module CustomerService =
let registerCustomer name email address =
{
Id = CustomerId (Guid.NewGuid())
Name = name
Email = email
Address = address
}
// Shopping Cart
type Cart = {
CustomerId: CustomerId
Items: Dictionary<ProductId, int>
}
module CartService =
let createCart customerId =
{ CustomerId = customerId; Items = Dictionary<ProductId, int>() }
let addItem cart productId quantity =
if cart.Items.ContainsKey(productId) then
cart.Items.[productId] <- cart.Items.[productId] + quantity
else
cart.Items.Add(productId, quantity)
let removeItem cart productId =
cart.Items.Remove(productId) |> ignore
let updateQuantity cart productId quantity =
if cart.Items.ContainsKey(productId) then
if quantity <= 0 then
cart.Items.Remove(productId) |> ignore
else
cart.Items.[productId] <- quantity
let getTotal cart products =
cart.Items
|> Seq.sumBy (fun kvp ->
let product = ProductService.getProduct kvp.Key products
match product with
| Some p -> p.Price * decimal kvp.Value
| None -> 0m
)
let checkout cart products =
let items =
cart.Items
|> Seq.map (fun kvp ->
let product = ProductService.getProduct kvp.Key products
match product with
| Some p -> Some { ProductId = kvp.Key; Quantity = kvp.Value; Price = p.Price }
| None -> None
)
|> Seq.choose id
|> Seq.toList
if items.IsEmpty then
Error "Cart is empty"
else
let order = OrderService.createOrder cart.CustomerId items
Ok order