Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Lang Fsharp Dev

ASecurity

Foundational F# patterns covering functional-first programming, type providers, computation expressions, and domain modeling. Use when writing F# code, understanding functional patterns, working with type providers, or building .NET applications with F#. This is the entry point for F# development.

8 stars
0 votes
0 copies
0 views
Added 2/8/2026
developmentgoc#shellbashsqlexpresstestinggitapidatabase

Works with

cliapi

Security Analysis

A100/100

Scanned 2/12/2026

$npx -y skills add aRustyDev/ai --skill lang-fsharp-dev --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Lang Fsharp Dev?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Lang Fsharp Dev
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/arustydev-lang-fsharp-dev/badge)](https://www.skillsdirectory.com/skills/arustydev-lang-fsharp-dev)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: lang-fsharp-dev
description: Foundational F# patterns covering functional-first programming, type providers, computation expressions, and domain modeling. Use when writing F# code, understanding functional patterns, working with type providers, or building .NET applications with F#. This is the entry point for F# development.
---

# F# Fundamentals

Foundational F# patterns and core language features. This skill serves as both a reference for common patterns and guidance for functional-first .NET development.

## Overview

```
┌─────────────────────────────────────────────────────────────────┐
│                      F# Skill Hierarchy                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                    ┌───────────────────┐                        │
│                    │  lang-fsharp-dev  │ ◄── You are here       │
│                    │   (foundation)    │                        │
│                    └─────────┬─────────┘                        │
│                              │                                  │
│     ┌────────────┬───────────┼───────────┬────────────┐        │
│     │            │           │           │            │        │
│     ▼            ▼           ▼           ▼            ▼        │
│ ┌────────┐ ┌──────────┐ ┌────────┐ ┌─────────┐ ┌──────────┐   │
│ │ type   │ │ domain   │ │ async  │ │ testing │ │ web-api  │   │
│ │providers│ │ modeling │ │ -dev   │ │  -dev   │ │   -dev   │   │
│ └────────┘ └──────────┘ └────────┘ └─────────┘ └──────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

**This skill covers:**
- Functional-first programming patterns
- Type system (records, discriminated unions, options)
- Pattern matching and active patterns
- Computation expressions basics
- Type providers fundamentals
- Domain modeling with types
- Interop with C# and .NET

**This skill does NOT cover (see specialized skills):**
- Advanced type providers usage
- Domain-driven design patterns
- Async/Task programming deep dive
- Testing frameworks (Expecto, xUnit, FsUnit)
- Web development (Giraffe, Saturn, Falco)

---

## Quick Reference

| Task | Pattern |
|------|---------|
| Define record | `type Person = { Name: string; Age: int }` |
| Define union | `type Result<'T,'E> = Ok of 'T \| Error of 'E` |
| Pattern match | `match value with \| Some x -> x \| None -> 0` |
| Define function | `let add x y = x + y` |
| Pipe operator | `value \|> function1 \|> function2` |
| Composition | `let f = function1 >> function2` |
| List comprehension | `[ for i in 1..10 -> i * i ]` |
| Async computation | `async { let! result = fetchData() return result }` |

---

## Core Types

### Records

```fsharp
// Basic record
type Person = {
    FirstName: string
    LastName: string
    Age: int
}

// Creating instances
let person = {
    FirstName = "John"
    LastName = "Doe"
    Age = 30
}

// Copy-and-update
let olderPerson = { person with Age = 31 }

// Pattern matching on records
let getFullName person =
    match person with
    | { FirstName = f; LastName = l } -> $"{f} {l}"

// Shorter: destructuring
let getFullName' { FirstName = f; LastName = l } = $"{f} {l}"
```

### Discriminated Unions

```fsharp
// Simple union
type PaymentMethod =
    | Cash
    | CreditCard of cardNumber: string
    | DebitCard of cardNumber: string * pin: int

// Using unions
let processPayment method =
    match method with
    | Cash -> "Processing cash payment"
    | CreditCard cardNumber -> $"Processing credit card {cardNumber}"
    | DebitCard (cardNumber, _) -> $"Processing debit card {cardNumber}"

// Option type (built-in union)
type Option<'T> =
    | Some of 'T
    | None

// Result type (built-in union)
type Result<'T,'E> =
    | Ok of 'T
    | Error of 'E

// Using Option
let findPerson id =
    if id = 1 then
        Some { FirstName = "John"; LastName = "Doe"; Age = 30 }
    else
        None

// Using Result
let divide x y =
    if y = 0 then
        Error "Division by zero"
    else
        Ok (x / y)
```

### Single-Case Unions (Type Safety)

```fsharp
// Wrap primitives for type safety
type EmailAddress = EmailAddress of string
type CustomerId = CustomerId of int

let sendEmail (EmailAddress email) message =
    printfn $"Sending to {email}: {message}"

let getCustomer (CustomerId id) =
    // Can't accidentally pass wrong ID type
    printfn $"Fetching customer {id}"

// Usage prevents type confusion
let email = EmailAddress "test@example.com"
let customerId = CustomerId 123
sendEmail email "Hello"
// sendEmail customerId "Hello"  // Compile error!
```

---

## Pattern Matching

### Basic Matching

```fsharp
// Match on values
let describe x =
    match x with
    | 0 -> "zero"
    | 1 -> "one"
    | 2 -> "two"
    | n when n > 0 -> "positive"
    | _ -> "negative"

// Match on types
let processValue (value: obj) =
    match value with
    | :? string as s -> $"String: {s}"
    | :? int as i -> $"Int: {i}"
    | _ -> "Unknown type"

// Match on tuples
let point = (3, 4)
match point with
| (0, 0) -> "origin"
| (x, 0) -> $"on x-axis at {x}"
| (0, y) -> $"on y-axis at {y}"
| (x, y) -> $"at ({x}, {y})"
```

### Active Patterns

```fsharp
// Single-case active pattern
let (|Even|Odd|) n =
    if n % 2 = 0 then Even else Odd

match 42 with
| Even -> "even number"
| Odd -> "odd number"

// Partial active pattern
let (|Integer|_|) (str: string) =
    match System.Int32.TryParse(str) with
    | true, value -> Some value
    | false, _ -> None

match "123" with
| Integer n -> $"Number: {n}"
| _ -> "Not a number"

// Multi-case active pattern
let (|Small|Medium|Large|) n =
    if n < 10 then Small
    elif n < 100 then Medium
    else Large

match 42 with
| Small -> "small"
| Medium -> "medium"
| Large -> "large"
```

---

## Functions

### Function Basics

```fsharp
// Simple function
let add x y = x + y

// Type annotations (optional but recommended for public APIs)
let add' (x: int) (y: int) : int = x + y

// Anonymous function (lambda)
let doubled = List.map (fun x -> x * 2) [1; 2; 3]

// Recursive function
let rec factorial n =
    if n <= 1 then 1
    else n * factorial (n - 1)

// Tail-recursive function (optimized)
let factorial' n =
    let rec loop acc n =
        if n <= 1 then acc
        else loop (acc * n) (n - 1)
    loop 1 n
```

### Partial Application and Currying

```fsharp
// All F# functions are curried by default
let add x y = x + y
let add5 = add 5  // Partial application
add5 10  // Returns 15

// Use partial application for configurable functions
let greet greeting name = $"{greeting}, {name}!"
let sayHello = greet "Hello"
let sayHi = greet "Hi"

sayHello "Alice"  // "Hello, Alice!"
sayHi "Bob"       // "Hi, Bob!"
```

### Function Composition

```fsharp
// Forward composition (>>)
let add1 x = x + 1
let double x = x * 2
let add1ThenDouble = add1 >> double

add1ThenDouble 5  // Returns 12

// Backward composition (<<)
let doubleThenAdd1 = add1 << double
doubleThenAdd1 5  // Returns 11

// Pipe operator (|>)
let result =
    5
    |> add1
    |> double
    |> fun x -> x + 10

// Pipe backward (<|)
let sum = (+) <| 1 + 2  // Equivalent to (+) (1 + 2)
```

---

## Collections

### Lists

```fsharp
// List literals
let numbers = [1; 2; 3; 4; 5]
let moreNumbers = [1..10]
let evenNumbers = [2..2..10]

// Cons operator (::)
let newList = 0 :: numbers  // [0; 1; 2; 3; 4; 5]

// List comprehensions
let squares = [ for i in 1..10 -> i * i ]
let evens = [ for i in 1..20 do if i % 2 = 0 then yield i ]

// Common list functions
let doubled = List.map (fun x -> x * 2) numbers
let evens' = List.filter (fun x -> x % 2 = 0) numbers
let sum = List.fold (+) 0 numbers
let sum' = List.sum numbers
let product = List.reduce (*) numbers
```

### Arrays

```fsharp
// Array literals
let arr = [| 1; 2; 3; 4; 5 |]

// Array comprehension
let squares = [| for i in 1..10 -> i * i |]

// Mutable updates (in-place)
arr.[0] <- 10

// Array functions (similar to List)
let doubled = Array.map (fun x -> x * 2) arr
let evens = Array.filter (fun x -> x % 2 = 0) arr
```

### Sequences (Lazy)

```fsharp
// Infinite sequence
let naturals = Seq.initInfinite id  // 0, 1, 2, 3, ...

// Lazy evaluation
let expensiveSeq = seq {
    printfn "Computing..."
    for i in 1..5 do
        printfn $"Yielding {i}"
        yield i * i
}

// Only computed when enumerated
expensiveSeq |> Seq.take 3 |> Seq.toList
```

### Map and Set

```fsharp
// Map (immutable dictionary)
let ages = Map [ ("Alice", 30); ("Bob", 25) ]
let aliceAge = ages.["Alice"]  // 30
let aliceAge' = Map.tryFind "Alice" ages  // Some 30
let updatedAges = Map.add "Charlie" 35 ages

// Set (immutable)
let set1 = Set [1; 2; 3]
let set2 = Set [3; 4; 5]
let union = Set.union set1 set2  // {1, 2, 3, 4, 5}
let intersection = Set.intersect set1 set2  // {3}
```

---

## Computation Expressions

### Option Computation Expression

```fsharp
// Option workflow
type OptionBuilder() =
    member _.Bind(x, f) = Option.bind f x
    member _.Return(x) = Some x
    member _.ReturnFrom(x) = x

let option = OptionBuilder()

let validateAge age =
    option {
        let! validAge =
            if age >= 0 && age <= 120 then Some age
            else None
        return validAge
    }
```

### Result Computation Expression

```fsharp
// Result workflow
type ResultBuilder() =
    member _.Bind(x, f) = Result.bind f x
    member _.Return(x) = Ok x
    member _.ReturnFrom(x) = x

let result = ResultBuilder()

let divideBy x y =
    if y = 0 then Error "Division by zero"
    else Ok (x / y)

let calculate =
    result {
        let! a = divideBy 10 2   // Ok 5
        let! b = divideBy 20 4   // Ok 5
        let! c = divideBy a b    // Ok 1
        return c
    }
```

### Async Computation Expression

```fsharp
// Async workflow (built-in)
let fetchData url = async {
    printfn $"Fetching {url}..."
    do! Async.Sleep 1000
    return $"Data from {url}"
}

let processUrls urls = async {
    let! results =
        urls
        |> List.map fetchData
        |> Async.Parallel

    return results |> Array.toList
}

// Run async computation
let urls = ["url1"; "url2"; "url3"]
processUrls urls |> Async.RunSynchronously
```

---

## Type Providers

### CSV Type Provider

```fsharp
open FSharp.Data

// Type provider infers schema from sample
type StockData = CsvProvider<"stocks.csv">

let data = StockData.Load("stocks.csv")
for row in data.Rows do
    printfn $"{row.Date}: {row.Close}"
```

### JSON Type Provider

```fsharp
open FSharp.Data

// Infer from sample JSON
type Weather = JsonProvider<"""
{
    "temperature": 72,
    "condition": "sunny",
    "humidity": 65
}
""">

let weather = Weather.Load("weather.json")
printfn $"Temperature: {weather.Temperature}°F"
```

### SQL Type Provider

```fsharp
open FSharp.Data.Sql

type Sql = SqlDataProvider<
    ConnectionString = "Server=localhost;Database=mydb",
    DatabaseVendor = Common.DatabaseProviderTypes.MSSQLSERVER>

let ctx = Sql.GetDataContext()
let customers =
    query {
        for customer in ctx.Dbo.Customers do
        where (customer.Age > 18)
        select customer
    }
```

---

## Domain Modeling

### Making Illegal States Unrepresentable

```fsharp
// Bad: Can represent invalid states
type EmailContactBad = {
    EmailAddress: string option
    IsVerified: bool
}
// Problem: IsVerified can be true when EmailAddress is None

// Good: Invalid states impossible
type VerifiedEmail = VerifiedEmail of string
type UnverifiedEmail = UnverifiedEmail of string

type EmailContact =
    | Verified of VerifiedEmail
    | Unverified of UnverifiedEmail

// Can only verify an unverified email
let verify (UnverifiedEmail email) =
    // Verification logic...
    VerifiedEmail email
```

### Smart Constructors

```fsharp
// Constrained types with validation
type EmailAddress = private EmailAddress of string

module EmailAddress =
    let create (email: string) =
        if email.Contains("@") then
            Ok (EmailAddress email)
        else
            Error "Invalid email format"

    let value (EmailAddress email) = email

// Usage
match EmailAddress.create "test@example.com" with
| Ok email -> printfn $"Valid: {EmailAddress.value email}"
| Error msg -> printfn $"Error: {msg}"
```

### Units of Measure

```fsharp
// Define units
[<Measure>] type kg
[<Measure>] type m
[<Measure>] type s

// Type-safe calculations
let distance = 100.0<m>
let time = 10.0<s>
let speed = distance / time  // Type: float<m/s>

// Prevents mixing units
let mass = 50.0<kg>
// let invalid = distance + mass  // Compile error!

// Converting units
let metersToKilometers (x: float<m>) : float =
    float x / 1000.0
```

---

## Interop with C#

### Calling C# from F#

```fsharp
// Use C# classes naturally
open System.Collections.Generic

let dict = Dictionary<string, int>()
dict.Add("one", 1)
dict.Add("two", 2)

// LINQ extension methods
open System.Linq

let numbers = [1..10]
let evens = numbers.Where(fun x -> x % 2 = 0).ToList()
```

### Calling F# from C#

```fsharp
// Design F# types for C# consumption
namespace MyLibrary

// Use [<CLIMutable>] for record types
[<CLIMutable>]
type Person = {
    FirstName: string
    LastName: string
    Age: int
}

// Use classes for OO APIs
type PersonService() =
    member _.GetPerson(id: int) : Person option =
        Some { FirstName = "John"; LastName = "Doe"; Age = 30 }

    // Convert Option to nullable for C#
    member this.TryGetPerson(id: int) : Person =
        match this.GetPerson(id) with
        | Some p -> p
        | None -> Unchecked.defaultof<Person>
```

---

## Module System

F# uses modules and namespaces to organize code. Modules are similar to static classes in C# but are more flexible and support nested modules, functions, and values.

### Namespaces

```fsharp
// File: Domain/User.fs
namespace MyApp.Domain

type User = {
    Id: int
    Name: string
    Email: string
}

// Can have multiple types in same namespace
type UserRepository() =
    member _.GetUser(id: int) : User option =
        None
```

### Modules

```fsharp
// Top-level module (one per file)
module MyApp.Utils

let add x y = x + y
let multiply x y = x * y

// Using from another file
open MyApp.Utils
let result = add 5 10
```

### Nested Modules

```fsharp
module MyApp.Math

// Nested module
module Arithmetic =
    let add x y = x + y
    let subtract x y = x - y

module Geometry =
    let area radius = System.Math.PI * radius * radius
    let circumference radius = 2.0 * System.Math.PI * radius

// Usage
open MyApp.Math
let sum = Arithmetic.add 5 10
let circle = Geometry.area 5.0
```

### Module Attributes

```fsharp
// AutoOpen: automatically opens when parent is opened
[<AutoOpen>]
module Helpers =
    let inline tap f x = f x; x
    let inline tee f x = f x |> ignore; x

// RequireQualifiedAccess: must use module name
[<RequireQualifiedAccess>]
module Config =
    let apiKey = "secret-key"
    let timeout = 30

// Usage:
// Can't do: open Config
// Must do: Config.apiKey
```

### Open Declarations

```fsharp
// Open entire namespace
open System.Collections.Generic

// Open specific module
open MyApp.Domain

// Open with alias
open System.Collections.Generic
open SCG = System.Collections.Generic
let dict = SCG.Dictionary<string, int>()

// Open type (type extensions available)
open type System.Math
let result = PI * 2.0  // Instead of Math.PI
```

### Signature Files (.fsi)

Signature files define the public API of a module, hiding implementation details.

```fsharp
// File: Domain.fsi (signature)
module MyApp.Domain

type User = {
    Id: int
    Name: string
}

val createUser: string -> User
val validateUser: User -> Result<User, string>
// Internal functions not exposed

// File: Domain.fs (implementation)
module MyApp.Domain

type User = {
    Id: int
    Name: string
}

// Public functions (in signature)
let createUser name = { Id = 0; Name = name }
let validateUser user =
    if user.Name.Length > 0 then Ok user
    else Error "Invalid name"

// Private helper (not in signature)
let private generateId() = System.Random().Next()
```

### Module Organization Patterns

```fsharp
// File-per-type pattern
// User.fs
namespace MyApp.Domain

type User = {
    Id: int
    Name: string
}

module User =
    let create name = { Id = 0; Name = name }
    let setName name user = { user with Name = name }

// Module-per-concept pattern
module MyApp.UserManagement

type User = { Id: int; Name: string }
type Role = Admin | User | Guest

let createUser name = { Id = 0; Name = name }
let assignRole user role = (user, role)
```

---

## Error Handling

F# provides powerful error handling through Option and Result types, enabling railway-oriented programming patterns that make error flows explicit and composable.

### Option Type

```fsharp
// Option represents optional values
type Option<'T> =
    | Some of 'T
    | None

// Returning Option from functions
let tryDivide x y =
    if y = 0 then None
    else Some (x / y)

// Pattern matching
match tryDivide 10 2 with
| Some result -> printfn $"Result: {result}"
| None -> printfn "Cannot divide by zero"

// Option module functions
let doubled = Option.map (fun x -> x * 2) (Some 5)  // Some 10
let getOrDefault = Option.defaultValue 0 None  // 0
```

### Result Type

```fsharp
// Result represents success or failure with error details
type Result<'T, 'TError> =
    | Ok of 'T
    | Error of 'TError

// Basic usage
let divide x y =
    if y = 0 then
        Error "Division by zero"
    else
        Ok (x / y)

// Pattern matching
match divide 10 2 with
| Ok result -> printfn $"Success: {result}"
| Error msg -> printfn $"Error: {msg}"

// Result module functions
let doubled = Result.map (fun x -> x * 2) (Ok 5)  // Ok 10
let chained = Ok 10 |> Result.bind (fun x -> divide x 2)  // Ok 5
```

### Railway-Oriented Programming

Railway-oriented programming treats successful and error paths as parallel railway tracks, making composition natural.

```fsharp
// Validation functions that can fail
let validateName name =
    if String.IsNullOrWhiteSpace(name) then
        Error "Name cannot be empty"
    else
        Ok name

let validateAge age =
    if age >= 0 && age <= 120 then
        Ok age
    else
        Error "Age must be between 0 and 120"

let validateEmail email =
    if email.Contains("@") then
        Ok email
    else
        Error "Invalid email format"

// Composition with Result computation expression
let createPerson name age email =
    result {
        let! validName = validateName name
        let! validAge = validateAge age
        let! validEmail = validateEmail email
        return {
            FirstName = validName
            LastName = ""
            Age = validAge
        }
    }

// Usage
match createPerson "Alice" 30 "alice@example.com" with
| Ok person -> printfn $"Created: {person.FirstName}"
| Error msg -> printfn $"Validation failed: {msg}"
```

### Custom Error Types

```fsharp
// Domain-specific errors
type ValidationError =
    | InvalidEmail of string
    | InvalidAge of int
    | InvalidName of string

type PaymentError =
    | InsufficientFunds of decimal
    | CardExpired of System.DateTime
    | NetworkError of string

// Using custom errors
let validateEmail email : Result<string, ValidationError> =
    if email.Contains("@") then
        Ok email
    else
        Error (InvalidEmail email)

// Combining different error types
type AppError =
    | Validation of ValidationError
    | Payment of PaymentError

let processPayment email amount =
    result {
        let! validEmail =
            validateEmail email
            |> Result.mapError Validation
        return "Payment successful"
    }
```

### Applicative Validation

Collect all errors instead of stopping at first failure.

```fsharp
// Validation that accumulates errors
type Validation<'T> = Result<'T, string list>

let validatePersonApplicative name age email : Validation<Person> =
    let nameResult =
        if String.IsNullOrWhiteSpace(name) then
            Error ["Name cannot be empty"]
        else
            Ok name

    let ageResult =
        if age >= 0 && age <= 120 then
            Ok age
        else
            Error ["Age must be between 0 and 120"]

    let emailResult =
        if email.Contains("@") then
            Ok email
        else
            Error ["Invalid email format"]

    // Combine all results
    match nameResult, ageResult, emailResult with
    | Ok n, Ok a, Ok e ->
        Ok { FirstName = n; LastName = ""; Age = a }
    | _ ->
        // Collect all errors
        [nameResult; ageResult; emailResult]
        |> List.choose (function Error errs -> Some errs | Ok _ -> None)
        |> List.concat
        |> Error
```

---

## Concurrency

F# provides first-class support for asynchronous and concurrent programming through async workflows, Task integration, and the MailboxProcessor (agent) model.

### Async Workflows

```fsharp
// Basic async computation
let fetchData url = async {
    printfn $"Fetching {url}..."
    do! Async.Sleep 1000  // Async sleep
    return $"Data from {url}"
}

// Run async computation
let data = fetchData "https://api.example.com" |> Async.RunSynchronously

// Parallel composition
let processParallel urls = async {
    let! results =
        urls
        |> List.map fetchData
        |> Async.Parallel
    return results |> Array.toList
}
```

### Error Handling in Async

```fsharp
// Async with Result
let safeFetchData url = async {
    try
        let! data = fetchData url
        return Ok data
    with
    | ex -> return Error ex.Message
}

// Catching specific exceptions
let fetchWithRetry url maxRetries = async {
    let rec loop retriesLeft =
        async {
            try
                let! data = fetchData url
                return Ok data
            with
            | :? System.Net.WebException when retriesLeft > 0 ->
                do! Async.Sleep 1000
                return! loop (retriesLeft - 1)
            | ex ->
                return Error $"Failed after {maxRetries} retries: {ex.Message}"
        }
    return! loop maxRetries
}
```

### Async.StartChild (Child Workflows)

```fsharp
// Start child async within parent workflow
let parentWorkflow = async {
    // Start child but don't wait yet
    let! childComputation = Async.StartChild(async {
        do! Async.Sleep 1000
        return 42
    })

    // Do other work while child runs
    printfn "Parent doing other work..."
    do! Async.Sleep 500

    // Now wait for child result
    let! result = childComputation
    return result * 2
}

// With timeout for child
let parentWithTimeout = async {
    let! childComputation =
        Async.StartChild(longRunningAsync, millisecondsTimeout = 5000)

    try
        let! result = childComputation
        return Ok result
    with
    | :? System.TimeoutException ->
        return Error "Child computation timed out"
}
```

### Task Integration

```fsharp
// Interop with .NET Task
open System.Threading.Tasks

// Task computation expression (F# 6+)
let fetchDataTask url = task {
    printfn $"Fetching {url}..."
    do! Task.Delay 1000
    return $"Data from {url}"
}

// Convert between Async and Task
let asyncToTask = fetchData "url" |> Async.StartAsTask
let taskToAsync = fetchDataTask "url" |> Async.AwaitTask
```

### Task vs Async Comparison

| Aspect | Async | Task |
|--------|-------|------|
| **Execution** | Cold (doesn't start until run) | Hot (starts immediately) |
| **Composition** | Easy with `async {}` | F# 6+ `task {}` builder |
| **Cancellation** | Built-in via CancellationToken | Via CancellationToken |
| **Exception handling** | Wrapped in Async | Direct exceptions |
| **.NET interop** | Convert with `Async.StartAsTask` | Native .NET |
| **Performance** | Slight overhead | Better for .NET interop |
| **Use when** | F#-centric code, composition | C# interop, ASP.NET Core |

```fsharp
// Prefer Async for F# composition
let processDataAsync inputs = async {
    let! results =
        inputs
        |> List.map processItemAsync
        |> Async.Parallel
    return Array.toList results
}

// Prefer Task for ASP.NET Core controllers
let handleRequest (ctx: HttpContext) = task {
    let! data = ctx.Request.ReadFromJsonAsync<InputData>()
    let result = processData data
    return! ctx.Response.WriteAsJsonAsync(result)
}
```

### MailboxProcessor (Agents)

The MailboxProcessor provides a message-based concurrency model, similar to Erlang's actors.

```fsharp
// Simple counter agent
type CounterMessage =
    | Increment
    | Decrement
    | Get of AsyncReplyChannel<int>

let counterAgent = MailboxProcessor.Start(fun inbox ->
    let rec loop count = async {
        let! msg = inbox.Receive()
        match msg with
        | Increment ->
            return! loop (count + 1)
        | Decrement ->
            return! loop (count - 1)
        | Get replyChannel ->
            replyChannel.Reply(count)
            return! loop count
    }
    loop 0)

// Using the agent
counterAgent.Post Increment
counterAgent.Post Increment
let count = counterAgent.PostAndReply(fun reply -> Get reply)  // 2
```

### Agent Patterns

```fsharp
// Request-reply pattern
type CacheMessage<'K, 'V> =
    | Get of key: 'K * AsyncReplyChannel<'V option>
    | Set of key: 'K * value: 'V
    | Clear

let createCache<'K, 'V when 'K: comparison>() =
    MailboxProcessor.Start(fun inbox ->
        let rec loop (cache: Map<'K, 'V>) = async {
            let! msg = inbox.Receive()
            match msg with
            | Get (key, replyChannel) ->
                replyChannel.Reply(Map.tryFind key cache)
                return! loop cache
            | Set (key, value) ->
                return! loop (Map.add key value cache)
            | Clear ->
                return! loop Map.empty
        }
        loop Map.empty)

// Usage
let cache = createCache<string, int>()
cache.Post (Set ("key1", 42))
let value = cache.PostAndReply(fun reply -> Get ("key1", reply))  // Some 42
```

### CancellationToken Support

```fsharp
open System.Threading

// Async with cancellation
let longRunningTask = async {
    for i in 1..100 do
        printfn $"Step {i}"
        do! Async.Sleep 100
    return "Completed"
}

// Create cancellation token
let cts = new CancellationTokenSource()

// Start with cancellation
Async.Start(longRunningTask, cts.Token)

// Cancel after 500ms
Thread.Sleep 500
cts.Cancel()
```

### Timeout Patterns

```fsharp
open System
open System.Threading

// Timeout with CancellationTokenSource
let withTimeout milliseconds computation = async {
    use cts = new CancellationTokenSource(milliseconds)
    try
        let! result = Async.StartChild(computation, millisecondsTimeout = milliseconds)
        let! value = result
        return Ok value
    with
    | :? TimeoutException -> return Error "Operation timed out"
    | :? OperationCanceledException -> return Error "Operation cancelled"
}

// Using Async.RunSynchronously with timeout
let resultWithTimeout =
    try
        fetchData "url" |> Async.RunSynchronously |> Some
    with
    | :? TimeoutException -> None

// Task timeout with Task.WhenAny
let withTaskTimeout (timeout: TimeSpan) (task: Task<'T>) = task {
    use cts = new CancellationTokenSource()
    let delayTask = Task.Delay(timeout, cts.Token)
    let! completedTask = Task.WhenAny(task, delayTask)
    if completedTask = (task :> Task) then
        cts.Cancel()
        return Ok (task.Result)
    else
        return Error "Task timed out"
}

// Combine cancellation and timeout
let fetchWithCancellationAndTimeout url (parentToken: CancellationToken) = async {
    use cts = CancellationTokenSource.CreateLinkedTokenSource(parentToken)
    cts.CancelAfter(5000)  // 5 second timeout

    try
        let! result = fetchData url
        return Ok result
    with
    | :? OperationCanceledException when parentToken.IsCancellationRequested ->
        return Error "Parent cancelled"
    | :? OperationCanceledException ->
        return Error "Timed out"
}
```

### Parallel Processing

```fsharp
// Parallel map (CPU-bound)
let numbers = [1..1000000]
let results = numbers |> Array.ofList |> Array.Parallel.map (fun x -> x * x)

// Parallel for
open System.Threading.Tasks

Parallel.For(0, 100, fun i ->
    printfn $"Iteration {i}"
) |> ignore
```

### Concurrency Model Comparison

| Model | Use Case | Characteristics |
|-------|----------|-----------------|
| **Async** | I/O-bound operations | Cooperative, composable, cold start |
| **Task** | .NET interop, hot operations | Eager, integrates with C# |
| **MailboxProcessor** | Stateful agents, actors | Message-passing, thread-safe state |
| **Array.Parallel** | CPU-bound data parallelism | Fork-join, automatic partitioning |
| **Parallel.For** | Fine-grained CPU parallelism | Loop-level parallelism |
| **Channels** | Producer-consumer | Bounded/unbounded queues |

```fsharp
// Choosing the right model
let ioWorkload urls =
    // Use Async for I/O-bound work
    urls
    |> List.map fetchAsync
    |> Async.Parallel
    |> Async.RunSynchronously

let cpuWorkload data =
    // Use Array.Parallel for CPU-bound work
    data
    |> Array.Parallel.map expensiveComputation

let statefulProcessor () =
    // Use MailboxProcessor for mutable state
    MailboxProcessor.Start(fun inbox ->
        let rec loop state = async {
            let! msg = inbox.Receive()
            let newState = processMessage state msg
            return! loop newState
        }
        loop initialState)
```

---

## Metaprogramming

F# provides powerful metaprogramming capabilities through type providers, quotations, computation expressions, and active patterns.

### Type Providers

Type providers generate types at compile-time based on external data sources, providing type-safe access to structured data.

```fsharp
// CSV Type Provider
open FSharp.Data

type Stocks = CsvProvider<"sample.csv">
let data = Stocks.Load("data.csv")

for row in data.Rows do
    printfn $"{row.Date}: ${row.Close}"

// JSON Type Provider
type Weather = JsonProvider<"""
{
    "city": "Seattle",
    "temperature": 72,
    "conditions": ["partly cloudy", "windy"]
}
""">

let weather = Weather.Load("weather.json")
printfn $"{weather.City}: {weather.Temperature}°F"
```

### Quotations

Quotations represent F# code as abstract syntax trees (AST) for manipulation and analysis.

```fsharp
open Microsoft.FSharp.Quotations

// Code quotations
let expr = <@ 1 + 2 @>  // Typed quotation
let rawExpr = <@@ 1 + 2 @@>  // Untyped quotation

// Examining quotations
let rec printExpr expr =
    match expr with
    | Patterns.Value(v, _) -> printfn $"Value: {v}"
    | Patterns.Call(_, mi, args) ->
        printfn $"Call: {mi.Name}"
        args |> List.iter printExpr
    | Patterns.Lambda(var, body) ->
        printfn $"Lambda: {var.Name}"
        printExpr body
    | _ -> printfn "Other pattern"
```

### Quotation Patterns

```fsharp
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns

// Pattern matching on quotations
let rec evaluate expr =
    match expr with
    | Value(v, t) when t = typeof<int> ->
        v :?> int
    | Call(None, mi, [left; right]) when mi.Name = "op_Addition" ->
        evaluate left + evaluate right
    | Call(None, mi, [left; right]) when mi.Name = "op_Multiply" ->
        evaluate left * evaluate right
    | _ -> failwith "Unsupported expression"

// Usage
let result = evaluate <@ 2 + 3 * 4 @>  // 14
```

### Computation Expressions

Computation expressions provide syntactic sugar for monadic operations.

```fsharp
// Custom computation expression builder
type MaybeBuilder() =
    member _.Bind(x, f) = Option.bind f x
    member _.Return(x) = Some x
    member _.ReturnFrom(x) = x
    member _.Zero() = None
    member _.Delay(f) = f
    member _.Run(f) = f()

let maybe = MaybeBuilder()

// Usage
let result = maybe {
    let! x = Some 5
    let! y = Some 10
    return x + y
}  // Some 15
```

### Advanced Active Patterns

```fsharp
// Parameterized active patterns
let (|DivisibleBy|_|) divisor n =
    if n % divisor = 0 then Some () else None

match 15 with
| DivisibleBy 3 -> "Divisible by 3"
| DivisibleBy 5 -> "Divisible by 5"
| _ -> "Not divisible"

// Multi-case with computation
let (|Small|Medium|Large|Huge|) value =
    if value < 10 then Small
    elif value < 100 then Medium value
    elif value < 1000 then Large value
    else Huge value

match 150 with
| Small -> "small"
| Medium x -> $"medium: {x}"
| Large x -> $"large: {x}"
| Huge -> "huge"
```

### Reflection

```fsharp
open System.Reflection

// Get type information
let t = typeof<Person>
printfn $"Type: {t.Name}"
printfn $"Properties: {t.GetProperties().Length}"

// Create instance dynamically
let createInstance (t: System.Type) =
    System.Activator.CreateInstance(t)

// Invoke method
let invokeMethod obj methodName args =
    let t = obj.GetType()
    let mi = t.GetMethod(methodName)
    mi.Invoke(obj, args)
```

---

## Zero/Default Values

F# handles default and zero values differently than C#, with a strong emphasis on explicit handling through Option types rather than null references.

### Option.None vs Null

```fsharp
// Preferred: Use Option for optional values
type Person = {
    Name: string
    Email: string option  // Explicitly optional
    Age: int
}

let person = {
    Name = "Alice"
    Email = None  // Explicit absence
    Age = 30
}

// Pattern matching makes absence explicit
match person.Email with
| Some email -> printfn $"Email: {email}"
| None -> printfn "No email provided"
```

### Default Values in Records

```fsharp
// Records don't have default constructors
type Config = {
    Host: string
    Port: int
    EnableLogging: bool
}

// Create default config explicitly
let defaultConfig = {
    Host = "localhost"
    Port = 8080
    EnableLogging = false
}

// Smart constructor pattern for defaults
module Config =
    let create() = {
        Host = "localhost"
        Port = 8080
        EnableLogging = false
    }

    let withHost host config = { config with Host = host }
    let withPort port config = { config with Port = port }

// Usage with fluent API
let config =
    Config.create()
    |> Config.withHost "api.example.com"
    |> Config.withPort 443
```

### Unchecked.defaultof<'T>

```fsharp
// Get CLR default value for a type
let defaultInt = Unchecked.defaultof<int>      // 0
let defaultBool = Unchecked.defaultof<bool>    // false
let defaultString = Unchecked.defaultof<string>  // null
let defaultOption = Unchecked.defaultof<int option>  // None

// Useful for interop with .NET APIs
type MyClass() =
    let mutable value: string = Unchecked.defaultof<string>

    member _.Value
        with get() = value
        and set(v) = value <- v
```

### Null Handling for Interop

```fsharp
// Checking for null from .NET
let safeToUpper (s: string) =
    if isNull s then
        None
    else
        Some (s.ToUpper())

// Option.ofObj: convert null to None
let fromNullable (s: string) =
    Option.ofObj s

let result = fromNullable null  // None
let result2 = fromNullable "test"  // Some "test"

// Option.toObj: convert None to null
let toNullable opt =
    Option.toObj opt

let nullable = toNullable None  // null
let nullable2 = toNullable (Some "test")  // "test"
```

### Array and Collection Defaults

```fsharp
// Empty collections
let emptyList = []
let emptyArray = [||]
let emptySeq = Seq.empty
let emptyMap = Map.empty
let emptySet = Set.empty

// Arrays initialized with default values
let zeros = Array.zeroCreate<int> 10  // [|0; 0; 0; ...|]
let defaults = Array.create 5 "default"  // [|"default"; "default"; ...|]

// Using init for custom defaults
let squares = Array.init 5 (fun i -> i * i)  // [|0; 1; 4; 9; 16|]
```

### Nullable<'T> for Value Types

```fsharp
open System

// Interop with .NET Nullable
let nullableInt: Nullable<int> = Nullable()  // No value
let nullableInt2: Nullable<int> = Nullable(42)  // Has value

// Check for value
if nullableInt.HasValue then
    printfn $"Value: {nullableInt.Value}"
else
    printfn "No value"

// Convert to/from Option
let fromNullable (n: Nullable<'T>) : 'T option =
    if n.HasValue then Some n.Value else None

let toNullable (opt: 'T option) : Nullable<'T> =
    match opt with
    | Some v -> Nullable(v)
    | None -> Nullable()
```

### Best Practices

```fsharp
// Do: Use Option for optional values
type User = {
    Name: string
    Email: string option  // Explicit
    Age: int
}

// Don't: Use null for optional values
type BadUser = {
    Name: string
    Email: string  // Could be null - unclear
    Age: int
}

// Do: Provide explicit default functions
module User =
    let create name = {
        Name = name
        Email = None
        Age = 0
    }

// Don't: Rely on Unchecked.defaultof unless necessary
let badDefault = Unchecked.defaultof<User>  // Null, not a valid User
```

---

## Common Idioms

### Dependency Injection (Reader Pattern)

```fsharp
// Pass dependencies explicitly
type Dependencies = {
    GetTime: unit -> System.DateTime
    Logger: string -> unit
}

let processOrder deps orderId =
    deps.Logger $"Processing order {orderId}"
    let timestamp = deps.GetTime()
    // ... rest of logic

// Usage
let deps = {
    GetTime = fun () -> System.DateTime.Now
    Logger = printfn "%s"
}

processOrder deps 123
```

### Event Sourcing Pattern

```fsharp
// Define events
type AccountEvent =
    | AccountOpened of customerId: string * initialBalance: decimal
    | MoneyDeposited of amount: decimal
    | MoneyWithdrawn of amount: decimal

// Apply events to state
type Account = { Balance: decimal }

let apply state event =
    match event with
    | AccountOpened (_, initialBalance) ->
        { Balance = initialBalance }
    | MoneyDeposited amount ->
        { state with Balance = state.Balance + amount }
    | MoneyWithdrawn amount ->
        { state with Balance = state.Balance - amount }

// Rebuild state from events
let buildState events =
    events |> List.fold apply { Balance = 0m }
```

---

## Troubleshooting

### Type Inference Issues

**Problem:** Compiler can't infer types

```fsharp
// Error: type inference failure
let processItems items =
    items |> List.map (fun x -> x.ToString())
```

**Fix:** Add type annotations
```fsharp
let processItems (items: int list) =
    items |> List.map (fun x -> x.ToString())
```

### Option/Result Unwrapping

**Problem:** Nested Option/Result types

```fsharp
// Bad: nested Some
let findAndProcess id =
    match find id with
    | Some person ->
        match process person with
        | Some result -> Some result
        | None -> None
    | None -> None
```

**Fix:** Use computation expressions or bind
```fsharp
let findAndProcess id =
    find id
    |> Option.bind process
```

### Mutable vs Immutable

**Problem:** Need to update values

```fsharp
// Doesn't work - records are immutable
let person = { FirstName = "John"; LastName = "Doe"; Age = 30 }
person.Age <- 31  // Error!
```

**Fix:** Use copy-and-update or mutable
```fsharp
// Functional: create new record
let olderPerson = { person with Age = 31 }

// Or use mutable if really needed
type MutablePerson = {
    FirstName: string
    LastName: string
    mutable Age: int
}
```

### Recursive Type Definitions

**Problem:** Types reference each other

```fsharp
// Error: types not defined yet
type Folder = { Name: string; Items: Item list }
type Item = File of string | Folder of Folder
```

**Fix:** Use `and` keyword
```fsharp
type Folder = { Name: string; Items: Item list }
and Item = File of string | Folder of Folder
```

---

## Serialization

F# works seamlessly with .NET serialization libraries while maintaining functional idioms. For cross-language serialization patterns, see `patterns-serialization-dev`.

### System.Text.Json

```fsharp
open System.Text.Json
open System.Text.Json.Serialization

// Simple record serialization
type Person = {
    FirstName: string
    LastName: string
    Age: int
}

let person = { FirstName = "Alice"; LastName = "Smith"; Age = 30 }
let json = JsonSerializer.Serialize(person)
// {"FirstName":"Alice","LastName":"Smith","Age":30}

let parsed = JsonSerializer.Deserialize<Person>(json)

// Custom options
let options = JsonSerializerOptions()
options.PropertyNamingPolicy <- JsonNamingPolicy.CamelCase
options.WriteIndented <- true

let camelCaseJson = JsonSerializer.Serialize(person, options)
// {
//   "firstName": "alice",
//   "lastName": "smith",
//   "age": 30
// }
```

### JsonFSharpConverter for F# Types

```fsharp
open System.Text.Json
open System.Text.Json.Serialization

// Configure for F# discriminated unions and options
let options = JsonSerializerOptions()
options.Converters.Add(JsonFSharpConverter())

// Discriminated union serialization
type PaymentMethod =
    | Cash
    | CreditCard of cardNumber: string
    | DebitCard of cardNumber: string * pin: int

let payment = CreditCard "1234-5678-9012-3456"
let json = JsonSerializer.Serialize(payment, options)
// {"Case":"CreditCard","Fields":["1234-5678-9012-3456"]}

// Option type handling
type User = {
    Name: string
    Email: string option
}

let user = { Name = "Bob"; Email = Some "bob@example.com" }
let userJson = JsonSerializer.Serialize(user, options)
```

### Custom Converters

```fsharp
open System
open System.Text.Json
open System.Text.Json.Serialization

// Custom converter for EmailAddress
type EmailAddress = EmailAddress of string

type EmailAddressConverter() =
    inherit JsonConverter<EmailAddress>()

    override _.Read(reader, typeToConvert, options) =
        EmailAddress (reader.GetString())

    override _.Write(writer, value, options) =
        let (EmailAddress email) = value
        writer.WriteStringValue(email)

// Register converter
let options = JsonSerializerOptions()
options.Converters.Add(EmailAddressConverter())

let email = EmailAddress "test@example.com"
let json = JsonSerializer.Serialize(email, options)
// "test@example.com"
```

### FSharp.Json

```fsharp
open FSharp.Json

// Simple serialization
type Config = {
    Port: int
    Host: string
    Debug: bool
}

let config = { Port = 8080; Host = "localhost"; Debug = true }
let json = Json.serialize config
let parsed = Json.deserialize<Config> json

// Custom field names
type ApiResponse = {
    [<JsonField("response_code")>]
    ResponseCode: int

    [<JsonField("response_data")>]
    Data: string
}

// Transform during serialization
type Settings = {
    [<JsonField(Transform=typeof<Transforms.CamelCase>)>]
    DatabaseUrl: string

    [<JsonField(Transform=typeof<Transforms.SnakeCase>)>]
    MaxConnections: int
}
```

### Type Providers for JSON

```fsharp
open FSharp.Data

// Infer schema from sample JSON
type Weather = JsonProvider<"""
{
    "temperature": 72.5,
    "condition": "sunny",
    "humidity": 65,
    "forecast": [
        {"day": "Monday", "high": 75, "low": 60},
        {"day": "Tuesday", "high": 78, "low": 62}
    ]
}
""">

// Use with type safety
let weather = Weather.Load("weather.json")
printfn $"Temperature: {weather.Temperature}°F"
printfn $"Condition: {weather.Condition}"

weather.Forecast
|> Array.iter (fun day ->
    printfn $"{day.Day}: {day.High}°F / {day.Low}°F")

// From URL
let liveWeather = Weather.Load("https://api.weather.com/current")

// Parse from string
let jsonString = """{"temperature":68,"condition":"cloudy","humidity":70}"""
let parsed = Weather.Parse(jsonString)
```

### Validation Patterns

```fsharp
// Validation with Result
type ValidationError = string

let validateEmail (email: string) : Result<string, ValidationError> =
    if email.Contains("@") then
        Ok email
    else
        Error "Invalid email format"

let validateAge (age: int) : Result<int, ValidationError> =
    if age >= 0 && age <= 120 then
        Ok age
    else
        Error "Age must be between 0 and 120"

// Combine validations
type PersonData = {
    Name: string
    Email: string
    Age: int
}

let validatePerson data =
    result {
        let! validEmail = validateEmail data.Email
        let! validAge = validateAge data.Age
        return { data with Email = validEmail; Age = validAge }
    }

// Applicative validation (collect all errors)
type Validation<'T> = Result<'T, ValidationError list>

let validatePersonApplicative data : Validation<PersonData> =
    let validateName name =
        if String.IsNullOrWhiteSpace(name) then
            Error ["Name cannot be empty"]
        else
            Ok name

    match (validateName data.Name, validateEmail data.Email, validateAge data.Age) with
    | Ok n, Ok e, Ok a -> Ok { Name = n; Email = e; Age = a }
    | errors ->
        errors
        |> fun (n, e, a) ->
            [n; e; a]
            |> List.collect (function Error errs -> errs | Ok _ -> [])
        |> Error
```

---

## Build and Dependencies

F# uses the standard .NET build ecosystem with project files, NuGet packages, and the dotnet CLI.

### Project File (.fsproj)

```xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <OutputType>Exe</OutputType>
    <RootNamespace>MyApp</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <!-- Order matters in F#! Files are compiled top-to-bottom -->
    <Compile Include="Types.fs" />
    <Compile Include="Helpers.fs" />
    <Compile Include="Domain.fs" />
    <Compile Include="Program.fs" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="FSharp.Data" Version="6.3.0" />
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
  </ItemGroup>

</Project>
```

### dotnet CLI Commands

```bash
# Create new project
dotnet new console -lang F# -o MyApp
dotnet new classlib -lang F# -o MyLib
dotnet new webapi -lang F# -o MyApi

# Build and run
dotnet build
dotnet run
dotnet run -- arg1 arg2  # Pass arguments

# Watch mode (rebuild on file changes)
dotnet watch run

# Clean build artifacts
dotnet clean

# Restore packages
dotnet restore

# Create solution
dotnet new sln -n MySolution
dotnet sln add MyApp/MyApp.fsproj
dotnet sln add MyLib/MyLib.fsproj
```

### NuGet Package Management

```bash
# Add package
dotnet add package FSharp.Data
dotnet add package Newtonsoft.Json --version 13.0.3

# Remove package
dotnet remove package FSharp.Data

# Update package
dotnet add package FSharp.Data  # Gets latest

# List packages
dotnet list package

# List outdated packages
dotnet list package --outdated
```

### Package References in .fsproj

```xml
<ItemGroup>
  <!-- Exact version -->
  <PackageReference Include="FSharp.Core" Version="8.0.0" />

  <!-- Version range -->
  <PackageReference Include="FSharp.Data" Version="[6.0,7.0)" />

  <!-- Latest stable -->
  <PackageReference Include="Newtonsoft.Json" Version="*" />

  <!-- Development only -->
  <PackageReference Include="FsCheck" Version="2.16.5">
    <PrivateAssets>all</PrivateAssets>
  </PackageReference>
</ItemGroup>
```

### Project References

```xml
<!-- Reference another project -->
<ItemGroup>
  <ProjectReference Include="..\MyLib\MyLib.fsproj" />
</ItemGroup>
```

```bash
# Add project reference via CLI
dotnet add reference ../MyLib/MyLib.fsproj
```

### Paket (Alternative Package Manager)

```bash
# Install Paket
dotnet tool install paket

# Initialize Paket
dotnet paket init

# Add package
dotnet paket add FSharp.Data

# Install dependencies
dotnet paket install

# Update all packages
dotnet paket update
```

**paket.dependencies:**
```
source https://api.nuget.org/v3/index.json

nuget FSharp.Core >= 8.0
nuget FSharp.Data ~> 6.3
nuget Newtonsoft.Json

group Test
  nuget Expecto
  nuget FsCheck
```

**paket.references:**
```
FSharp.Data
Newtonsoft.Json

group Test
  Expecto
  FsCheck
```

### FAKE Build Script

```fsharp
// build.fsx
#r "paket:
nuget Fake.Core.Target
nuget Fake.DotNet.Cli //"
#load ".fake/build.fsx/intellisense.fsx"

open Fake.Core
open Fake.DotNet

let clean _ =
    !! "**/bin"
    ++ "**/obj"
    |> Shell.cleanDirs

let build _ =
    DotNet.build id ""

let test _ =
    DotNet.test id ""

let publish _ =
    DotNet.publish (fun opts ->
        { opts with
            Configuration = DotNet.BuildConfiguration.Release
            OutputPath = Some "./publish" }) ""

// Define targets
Target.create "Clean" clean
Target.create "Build" build
Target.create "Test" test
Target.create "Publish" publish

// Dependencies
open Fake.Core.TargetOperators

"Clean"
  ==> "Build"
  ==> "Test"
  ==> "Publish"

Target.runOrDefault "Build"
```

Run with:
```bash
dotnet fake build
dotnet fake build -t Publish
```

### Multi-Project Structure

```
MySolution/
├── MySolution.sln
├── src/
│   ├── MyApp/
│   │   ├── MyApp.fsproj
│   │   ├── Program.fs
│   │   └── Domain.fs
│   └── MyLib/
│       ├── MyLib.fsproj
│       ├── Types.fs
│       └── Utils.fs
├── tests/
│   └── MyApp.Tests/
│       ├── MyApp.Tests.fsproj
│       └── Tests.fs
├── paket.dependencies
└── build.fsx
```

### Publishing

```bash
# Publish for specific runtime
dotnet publish -r win-x64 -c Release
dotnet publish -r linux-x64 -c Release
dotnet publish -r osx-arm64 -c Release

# Self-contained (includes runtime)
dotnet publish -r linux-x64 -c Release --self-contained

# Framework-dependent (requires .NET runtime installed)
dotnet publish -c Release --no-self-contained

# Single file
dotnet publish -r linux-x64 -c Release /p:PublishSingleFile=true
```

### NuGet Package Creation

```xml
<!-- Add to .fsproj -->
<PropertyGroup>
  <PackageId>MyAwesomeLibrary</PackageId>
  <Version>1.0.0</Version>
  <Authors>Your Name</Authors>
  <Description>An awesome F# library</Description>
  <PackageLicenseExpression>MIT</PackageLicenseExpression>
  <RepositoryUrl>https://github.com/username/repo</RepositoryUrl>
</PropertyGroup>
```

```bash
# Create package
dotnet pack -c Release

# Publish to NuGet
dotnet nuget push bin/Release/MyAwesomeLibrary.1.0.0.nupkg --api-key YOUR_KEY --source https://api.nuget.org/v3/index.json
```

---

## Testing

F# has excellent testing support with Expecto, FsUnit, and FsCheck for property-based testing.

### Expecto

```fsharp
// Tests.fs
module Tests

open Expecto

// Simple test
let simpleTest =
    testCase "addition works" <| fun () ->
        let result = 1 + 1
        Expect.equal result 2 "1 + 1 should equal 2"

// Test list
let mathTests =
    testList "Math operations" [
        testCase "addition" <| fun () ->
            Expect.equal (2 + 2) 4 "2 + 2 = 4"

        testCase "subtraction" <| fun () ->
            Expect.equal (5 - 3) 2 "5 - 3 = 2"

        testCase "multiplication" <| fun () ->
            Expect.equal (3 * 4) 12 "3 * 4 = 12"
    ]

// Run all tests
[<EntryPoint>]
let main args =
    runTestsWithCLIArgs [] args mathTests
```

### Expecto Matchers

```fsharp
open Expecto

let expectTests =
    testList "Expecto expectations" [
        testCase "equal" <| fun () ->
            Expect.equal (1 + 1) 2 "should be equal"

        testCase "not equal" <| fun () ->
            Expect.notEqual 1 2 "should not be equal"

        testCase "is true" <| fun () ->
            Expect.isTrue (5 > 3) "5 should be greater than 3"

        testCase "is false" <| fun () ->
            Expect.isFalse (3 > 5) "3 should not be greater than 5"

        testCase "contains" <| fun () ->
            Expect.contains [1; 2; 3] 2 "list should contain 2"

        testCase "sequence equal" <| fun () ->
            Expect.sequenceEqual [1; 2; 3] [1; 2; 3] "sequences should match"

        testCase "throws" <| fun () ->
            Expect.throws (fun () -> failwith "boom") "should throw"

        testCase "is some" <| fun () ->
            Expect.isSome (Some 5) "should be Some"

        testCase "is none" <| fun () ->
            Expect.isNone None "should be None"
    ]
```

### Async and Task Testing

```fsharp
open Expecto

let asyncTests =
    testList "Async tests" [
        testCaseAsync "async computation" <| async {
            let! result = async { return 42 }
            Expect.equal result 42 "async result"
        }

        testTask "task computation" {
            let! result = task { return 42 }
            Expect.equal result 42 "task result"
        }
    ]
```

### Test Organization

```fsharp
// Nested test groups
let allTests =
    testList "All tests" [
        testList "Domain" [
            testList "User" [
                testCase "create user" <| fun () ->
                    let user = createUser "Alice" "alice@example.com"
                    Expect.equal user.Name "Alice" "name matches"
            ]
            testList "Order" [
                testCase "calculate total" <| fun () ->
                    let total = calculateTotal [10m; 20m; 30m]
                    Expect.equal total 60m "total is sum"
            ]
        ]
        testList "API" [
            // API tests
        ]
    ]

// Run with filters
[<EntryPoint>]
let main args =
    runTestsWithCLIArgs [] args allTests

// Run specific tests:
// dotnet run -- --filter "User"
```

### FsUnit with xUnit

```fsharp
module Tests

open Xunit
open FsUnit.Xunit

[<Fact>]
let ``2 + 2 should equal 4`` () =
    2 + 2 |> should equal 4

[<Fact>]
let ``list should contain element`` () =
    [1; 2; 3] |> should contain 2

[<Fact>]
let ``string should start with`` () =
    "hello world" |> should startWith "hello"

[<Fact>]
let ``option should be Some`` () =
    Some 5 |> should be (ofCase <@ Some @>)

[<Theory>]
[<InlineData(1, 2, 3)>]
[<InlineData(5, 5, 10)>]
[<InlineData(-1, 1, 0)>]
let ``addition works for multiple inputs`` a b expected =
    a + b |> should equal expected
```

### FsCheck Property-Based Testing

```fsharp
open Expecto
open FsCheck

// Property test with Expecto
let propertyTests =
    testList "Property tests" [
        testProperty "reverse twice equals original" <| fun (xs: int list) ->
            List.rev (List.rev xs) = xs

        testProperty "length of reverse equals length" <| fun (xs: int list) ->
            List.length (List.rev xs) = List.length xs

        testProperty "addition is commutative" <| fun (a: int) (b: int) ->
            a + b = b + a

        testProperty "list append length" <| fun (xs: int list) (ys: int list) ->
            List.length (xs @ ys) = List.length xs + List.length ys
    ]

// Custom generator
let positiveInt = Arb.generate<int> |> Gen.map abs

let customGeneratorTest =
    testProperty "square of positive is positive" <| fun () ->
        Prop.forAll (Arb.fromGen positiveInt) (fun n ->
            n * n >= 0)

// Conditional properties
let conditionalTest =
    testProperty "division by non-zero" <| fun (a: float) (b: float) ->
        b <> 0.0 ==> lazy (a / b * b = a)
```

### FsCheck with xUnit

```fsharp
open Xunit
open FsCheck
open FsCheck.Xunit

[<Property>]
let ``reverse twice gives original`` (xs: int list) =
    List.rev (List.rev xs) = xs

[<Property>]
let ``sort is idempotent`` (xs: int list) =
    List.sort (List.sort xs) = List.sort xs

[<Property(Arbitrary = [| typeof<CustomGenerators> |])>]
let ``custom generator property`` (email: string) =
    email.Contains("@")

// Custom generators
type CustomGenerators =
    static member Email() =
        let genEmail =
            gen {
                let! user = Gen.elements ["alice"; "bob"; "charlie"]
                let! domain = Gen.elements ["example.com"; "test.com"]
                return $"{user}@{domain}"
            }
        Arb.fromGen genEmail
```

### Test Setup and Teardown

```fsharp
open Expecto

// Setup/teardown pattern
let withDatabase test =
    let db = setupDatabase()  // Setup
    try
        test db
    finally
        cleanupDatabase db  // Teardown

let databaseTests =
    testList "Database tests" [
        testCase "insert user" <| fun () ->
            withDatabase (fun db ->
                insertUser db "Alice"
                let users = getUsers db
                Expect.contains users "Alice" "user should exist"
            )
    ]

// Shared fixture
type DatabaseFixture() =
    let db = setupDatabase()
    member _.Database = db
    interface System.IDisposable with
        member _.Dispose() = cleanupDatabase db

let fixtureTests =
    testSequenced <| testList "Sequenced tests" [
        let fixture = new DatabaseFixture()
        yield testCase "test 1" <| fun () ->
            // use fixture.Database
            ()
        yield testCase "test 2" <| fun () ->
            // use fixture.Database
            ()
    ]
```

### Mocking and Stubs

```fsharp
// Interface-based mocking
type IUserRepository =
    abstract member GetUser: int -> User option
    abstract member SaveUser: User -> unit

// Create stub for testing
let createStubRepository users =
    { new IUserRepository with
        member _.GetUser(id) =
            users |> List.tryFind (fun u -> u.Id = id)
        member _.SaveUser(user) =
            () // No-op for testing
    }

let testWithStub =
    testCase "get user from stub" <| fun () ->
        let users = [
            { Id = 1; Name = "Alice"; Email = "alice@example.com"; Age = 30 }
            { Id = 2; Name = "Bob"; Email = "bob@example.com"; Age = 25 }
        ]
        let repo = createStubRepository users
        let user = repo.GetUser(1)
        Expect.isSome user "should find user"
        Expect.equal user.Value.Name "Alice" "name should match"
```

### Running Tests

```bash
# Run with dotnet
dotnet run  # If entry point is defined
dotnet test  # If using xUnit/NUnit

# Expecto options
dotnet run -- --help
dotnet run -- --filter "User"
dotnet run -- --sequenced
dotnet run -- --debug
dotnet run -- --fail-on-focused-tests

# Watch mode
dotnet watch run
```

---

## Cross-Cutting Patterns

For cross-language comparison and translation patterns, see:

- `patterns-serialization-dev` - JSON/YAML handling, validation patterns
- `patterns-concurrency-dev` - Async workflows, parallel processing, Mailbox processors
- `patterns-metaprogramming-dev` - Type providers, computation expressions, quotations

---

## References

- [F# Language Reference](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/)
- [F# for Fun and Profit](https://fsharpforfunandprofit.com/)
- [F# Style Guide](https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/)
- [Domain Modeling Made Functional](https://pragprog.com/titles/swdddf/domain-modeling-made-functional/)

Attribution

aRustyDevaRustyDev
View sourceSee grades on GitHubMore from aRustyDev →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Clean Code

Pragmatic coding standards - concise, direct, no over-engineering, no unnecessary comments

304955 votes

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

286712 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2222 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Writing Plans

Use when you have a spec or requirements for a multi-step task, before touching code

2927051 votes
View all in development →