NexusCS

F#

Functional programming
Quick reference for F# - a functional-first language on .NET with type inference, pattern matching, and seamless C# interop.
featured

Getting started

Hello World

// Print to console
printfn "Hello, World!"

// With variable
let name = "World"
printfn "Hello, %s!" name

Basic types

let integer = 42
let float = 3.14
let string = "hello"
let boolean = true
let char = 'a'

Type annotations

let x: int = 42
let y: float = 3.14
let z: string = "text"

Comments

// Single line comment

(* Multi-line
   comment *)

/// XML documentation comment

Values and functions

Immutable values

let x = 10             // Immutable by default
let mutable y = 20     // Mutable value
y <- 30                // Reassignment

Function definition

let add x y = x + y
let result = add 5 3   // 8

// With type annotations
let add (x: int) (y: int): int = x + y

Lambda expressions

let add = fun x y -> x + y
let square = fun x -> x * x

// Shorthand
let double = (*) 2

Pipe operator

[1; 2; 3; 4]
|> List.map (fun x -> x * 2)
|> List.filter (fun x -> x > 4)
|> List.sum
// Result: 14

Composition

let double x = x * 2
let square x = x * x

// Forward composition
let doubleThenSquare = double >> square
doubleThenSquare 3  // 36

// Backward composition
let squareThenDouble = double << square
squareThenDouble 3  // 18

Currying

let add x y = x + y
let add5 = add 5       // Partial application
add5 10                // 15

Collections

Lists

let list1 = [1; 2; 3; 4]
let list2 = [1..10]        // Range
let list3 = [for i in 1..5 -> i * 2]

// Operations
List.head list1            // 1
List.tail list1            // [2; 3; 4]
List.length list1          // 4
1 :: [2; 3]               // Cons operator
[1; 2] @ [3; 4]           // Append

Arrays

let arr = [|1; 2; 3; 4|]
let arr2 = [|1..10|]

// Access
arr.[0]                    // 1
arr.[0] <- 10             // Mutation

// Operations
Array.length arr
Array.map (fun x -> x * 2) arr

Sequences

let seq1 = seq { 1..10 }
let seq2 = seq {
    for i in 1..5 do
        yield i * 2
}

// Lazy evaluation
Seq.take 3 seq1
Seq.filter (fun x -> x % 2 = 0) seq1

Sets and Maps

// Set
let set1 = Set.ofList [1; 2; 3; 3]
Set.add 4 set1
Set.contains 2 set1

// Map (dictionary)
let map1 = Map.ofList [("a", 1); ("b", 2)]
Map.find "a" map1          // 1
Map.add "c" 3 map1

Pattern matching

Basic matching

let describe x =
    match x with
    | 0 -> "zero"
    | 1 -> "one"
    | 2 -> "two"
    | _ -> "many"

describe 1  // "one"

List patterns

let rec sum list =
    match list with
    | [] -> 0
    | head :: tail -> head + sum tail

// Multiple elements
match list with
| [] -> "empty"
| [x] -> sprintf "one: %d" x
| [x; y] -> sprintf "two: %d, %d" x y
| x :: y :: rest -> sprintf "many starting with %d, %d" x y

Guards

let classify x =
    match x with
    | n when n < 0 -> "negative"
    | 0 -> "zero"
    | n when n > 0 -> "positive"
    | _ -> "unknown"

Tuple matching

let point = (3, 4)

match point with
| (0, 0) -> "origin"
| (x, 0) -> sprintf "on x-axis at %d" x
| (0, y) -> sprintf "on y-axis at %d" y
| (x, y) -> sprintf "point at (%d, %d)" x y

Record matching

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

let greet person =
    match person with
    | { Name = "Alice" } -> "Hi Alice!"
    | { Age = age } when age < 18 -> "Hello, young one!"
    | { Name = name; Age = age } -> sprintf "Hello %s (%d)" name age

Active patterns

// Single case
let (|Even|Odd|) n =
    if n % 2 = 0 then Even else Odd

match 4 with
| Even -> "even"
| Odd -> "odd"

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

match "123" with
| Integer i -> printfn "Parsed: %d" i
| _ -> printfn "Not an integer"

Types

Records

type Person = {
    Name: string
    Age: int
}

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

// Copy and update
let older = { alice with Age = 31 }

Discriminated unions

type Shape =
    | Circle of radius: float
    | Rectangle of width: float * height: float
    | Triangle of base_: float * height: float

let area shape =
    match shape with
    | Circle r -> System.Math.PI * r * r
    | Rectangle (w, h) -> w * h
    | Triangle (b, h) -> 0.5 * b * h

Option type

let tryDivide x y =
    if y = 0 then None
    else Some (x / y)

match tryDivide 10 2 with
| Some result -> printfn "Result: %d" result
| None -> printfn "Cannot divide by zero"

Result type

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

match tryDivide 10 0 with
| Ok result -> printfn "Result: %d" result
| Error msg -> printfn "Error: %s" msg

Type aliases

type Name = string
type Age = int
type Person = Name * Age

let alice: Person = ("Alice", 30)

Generic types

type Box<'T> = { Value: 'T }

let intBox = { Value = 42 }
let stringBox = { Value = "hello" }

// Generic function
let wrap x = { Value = x }

Object-oriented features

Classes

type Person(name: string, age: int) =
    member this.Name = name
    member this.Age = age
    member this.Greet() =
        printfn "Hello, I'm %s" this.Name

let alice = Person("Alice", 30)
alice.Greet()

Properties

type Person(firstName: string, lastName: string) =
    member val FirstName = firstName with get, set
    member val LastName = lastName with get, set

    member this.FullName =
        sprintf "%s %s" this.FirstName this.LastName

let person = Person("John", "Doe")
person.FirstName <- "Jane"

Interfaces

type IShape =
    abstract member Area: unit -> float

type Circle(radius: float) =
    interface IShape with
        member this.Area() = System.Math.PI * radius * radius

type Rectangle(width: float, height: float) =
    interface IShape with
        member this.Area() = width * height

Inheritance

type Animal(name: string) =
    member this.Name = name
    abstract member MakeSound: unit -> string
    default this.MakeSound() = "Some sound"

type Dog(name: string) =
    inherit Animal(name)
    override this.MakeSound() = "Woof!"

let dog = Dog("Buddy")
dog.MakeSound()  // "Woof!"

Async and parallel

Async workflows

let downloadAsync url =
    async {
        use client = new System.Net.Http.HttpClient()
        let! content = client.GetStringAsync(url) |> Async.AwaitTask
        return content
    }

// Run async
let content = downloadAsync "https://example.com" |> Async.RunSynchronously

Parallel execution

let tasks = [
    async { return 1 + 1 }
    async { return 2 + 2 }
    async { return 3 + 3 }
]

// Run in parallel
let results = tasks |> Async.Parallel |> Async.RunSynchronously
// [|2; 4; 6|]

Task integration

open System.Threading.Tasks

let taskExample() =
    task {
        let! result = Task.FromResult(42)
        return result * 2
    }

// Run task
let result = taskExample().Result  // 84

Async error handling

let riskyOperation() =
    async {
        try
            let! result = someAsyncOperation()
            return Ok result
        with
        | ex -> return Error ex.Message
    }

Computation expressions

Sequence expressions

let squares = seq {
    for i in 1..10 do
        yield i * i
}

let filtered = seq {
    for i in 1..20 do
        if i % 2 = 0 then
            yield i
}

List comprehensions

let evens = [ for i in 1..10 do if i % 2 = 0 then yield i ]
// [2; 4; 6; 8; 10]

let pairs = [
    for i in 1..3 do
        for j in 1..3 do
            yield (i, j)
]

Query expressions

open System.Linq

let people = [
    { Name = "Alice"; Age = 30 }
    { Name = "Bob"; Age = 25 }
    { Name = "Charlie"; Age = 35 }
]

let adults =
    query {
        for person in people do
        where (person.Age >= 30)
        select person.Name
    }

Custom computation builders

type MaybeBuilder() =
    member this.Bind(x, f) =
        match x with
        | Some v -> f v
        | None -> None

    member this.Return(x) = Some x

let maybe = MaybeBuilder()

let result = maybe {
    let! x = Some 10
    let! y = Some 20
    return x + y
}  // Some 30

Modules and namespaces

Modules

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

// Usage
Math.add 5 3

Nested modules

module Geometry =
    module Circle =
        let area radius = System.Math.PI * radius * radius

    module Rectangle =
        let area width height = width * height

Geometry.Circle.area 5.0

Opening modules

open System

let now = DateTime.Now
let path = IO.Path.Combine("a", "b")

Namespaces

namespace MyApp.Utils

module StringHelpers =
    let reverse (s: string) =
        s.ToCharArray() |> Array.rev |> System.String

Common operations

List operations

List.map (fun x -> x * 2) [1; 2; 3]
List.filter (fun x -> x > 2) [1; 2; 3; 4]
List.fold (+) 0 [1; 2; 3; 4]  // 10
List.reduce (+) [1; 2; 3; 4]  // 10
List.collect (fun x -> [x; x*2]) [1; 2]

String operations

"hello".Length               // 5
"hello".ToUpper()           // "HELLO"
"hello".[0]                 // 'h'
"hello".Substring(1, 3)     // "ell"
String.concat ", " ["a"; "b"]

Option operations

Option.map (fun x -> x * 2) (Some 5)  // Some 10
Option.bind tryDivide (Some 10)
Option.defaultValue 0 None             // 0
Option.isSome (Some 5)                 // true
Option.isNone None                     // true

Result operations

Result.map (fun x -> x * 2) (Ok 5)
Result.bind operation (Ok 10)
Result.defaultValue 0 (Error "fail")

// Combine results
let (>>=) result f = Result.bind f result

Type providers

JSON type provider

#r "nuget: FSharp.Data"
open FSharp.Data

type Weather = JsonProvider<"sample.json">
let data = Weather.Load("weather.json")

printfn "Temperature: %f" data.Temperature

CSV type provider

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

for row in data.Rows do
    printfn "%s: %f" row.Company row.Price

SQL type provider

#r "nuget: FSharp.Data.SqlClient"
open FSharp.Data

[<Literal>]
let connectionString = "..."

type GetCustomers = SqlCommandProvider<"
    SELECT * FROM Customers
", connectionString>

let customers = GetCustomers.Create(connectionString).Execute()

Units of measure

Defining units

[<Measure>] type m    // meters
[<Measure>] type s    // seconds
[<Measure>] type kg   // kilograms

let distance = 10.0<m>
let time = 2.0<s>
let speed = distance / time  // float<m/s>

Dimensionless conversion

[<Measure>] type USD
[<Measure>] type EUR

let dollars = 100.0<USD>
let rate = 0.85<EUR/USD>
let euros = dollars * rate  // float<EUR>

// Remove units
let value = float dollars  // 100.0

Generic units

let square (x: float<'u>) = x * x  // float<'u^2>
let cube (x: float<'u>) = x * x * x  // float<'u^3>

square 5.0<m>  // 25.0<m^2>

Error handling

Try-with expressions

try
    let result = 10 / 0
    printfn "%d" result
with
| :? System.DivideByZeroException ->
    printfn "Cannot divide by zero"
| ex ->
    printfn "Error: %s" ex.Message

Try-finally

try
    use file = System.IO.File.OpenRead("file.txt")
    // Use file
    ()
finally
    printfn "Cleanup"

Railway-oriented programming

let bind switchFunction =
    fun result ->
        match result with
        | Ok value -> switchFunction value
        | Error e -> Error e

let (>>=) result switchFunction =
    bind switchFunction result

// Chain operations
validate input
>>= process
>>= save

Scripting and REPL

FSI (F# Interactive)

// Load script
#load "MyScript.fsx"

// Reference assembly
#r "nuget: Newtonsoft.Json"

// Include directory
#I "/path/to/libs"

// Time execution
#time "on"

Script directives

#!/usr/bin/env dotnet fsi

#r "nuget: FSharp.Data"
open FSharp.Data

printfn "Hello from script!"

Testing

Expecto

open Expecto

let tests =
    testList "Math tests" [
        test "Addition works" {
            let result = 2 + 2
            Expect.equal result 4 "Should be 4"
        }

        testCase "Division works" <| fun () ->
            Expect.equal (10 / 2) 5 "Should be 5"
    ]

[<EntryPoint>]
let main args =
    runTestsWithArgs defaultConfig args tests

Property-based testing

#r "nuget: FsCheck"
open FsCheck

let reverseTest (list: int list) =
    List.rev (List.rev list) = list

Check.Quick reverseTest

// Custom properties
[<Property>]
let ``Addition is commutative`` (a: int) (b: int) =
    a + b = b + a

Common patterns

Recursive functions

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

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

Memoization

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.[x] <- v
            v

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

Discriminated union helpers

type Result<'T,'E> =
    | Ok of 'T
    | Error of 'E

module Result =
    let map f result =
        match result with
        | Ok v -> Ok (f v)
        | Error e -> Error e

    let bind f result =
        match result with
        | Ok v -> f v
        | Error e -> Error e

Gotchas

Indentation matters

// ✅ Correct
let x =
    let y = 5
    y + 10

// ❌ Wrong - indentation error
let x =
let y = 5
y + 10

Mutable vs immutable

let x = 10
x <- 20        // ⚠️ Error: x is immutable

let mutable y = 10
y <- 20        // ✅ OK

Equality operators

let x = 5 = 5      // ✅ Boolean comparison: true
let x = (5 = 5)    // Same as above

let assign x = x   // ⚠️ Function, not comparison

List concatenation

[1; 2] @ [3; 4]    // ✅ List concatenation
[1; 2] + [3; 4]    // ⚠️ Error: + not defined for lists

Function application

printfn "Value: %d" 5    // ✅ Space separates arguments
printfn("Value: %d", 5)  // ⚠️ Works but not idiomatic

Semicolons in lists

let list = [1; 2; 3]     // ✅ Semicolons separate items
let list = [1, 2, 3]     // ⚠️ Creates single tuple item

Type inference limitations

let add x y = x + y      // ⚠️ Error: type not known
let add (x: int) y = x + y  // ✅ One annotation helps
let add x y : int = x + y   // ✅ Return type annotation

Also see