F#
Functional-first programming on .NET.
F# is a functional-first language on .NET. It combines concise syntax, strong type inference and immutable data with full access to the .NET ecosystem. It is popular in finance, data science and domain modelling, and it runs anywhere .NET does.
Why learn F#?
F# gives you the safety of functional programming with the libraries and tooling of .NET.
- Concise: type inference means less code than C# for the same logic.
- Correct: discriminated unions and pattern matching make illegal states hard to write.
- Data and ML: works with ML.NET, TorchSharp and interactive notebooks.
Set up and run your first program
Install the .NET SDK, which includes F#. Run scripts with dotnet fsi or create projects with dotnet new.
dotnet --version
dotnet fsi hello.fsx
dotnet new console -lang F# -o MyApplet name = "Ada"
printfn "Hello, AI! Welcome to F#, %s." name.fsx files are scripts; .fs files belong to projects. Indentation defines blocks.Values and types
let binds immutable values. Types are inferred, but you can annotate them. Use mutable only when you really need to change a value.
let layers = 32
let lr = 3e-4
let model = "llama3.1"
let fp16GB (p: float) = p * 2.0 / 1e9
printfn "%s: %d layers, %.1f GB" model layers (fp16GB 8.03e9)
let mutable steps = 0
steps <- steps + 1Functions and pipelines
Functions are values. The pipe operator |> passes a value through a chain of functions, which makes data transformations easy to read.
let square x = x * x
let isEven x = x % 2 = 0
[1 .. 10]
|> List.filter isEven
|> List.map square
|> List.sum
|> printfn "Sum of even squares: %d"
let params layers width = 12L * int64 layers * int64 width * int64 width
let params4096 = params 32 // partial application
printfn "%d" (params4096 4096)Lists, arrays and maps
Lists are immutable linked lists, arrays are fast and mutable, and Map is an immutable dictionary. List comprehensions use [ for ... ].
let vram = Map [ "H100", 80; "H200", 141; "RTX 5090", 32 ]
let big = [ for KeyValue(name, gb) in vram do if gb >= 80 then yield name ]
let total = vram |> Map.values |> Seq.sum
printfn "%A, total %d GB" big total
let weights = Array.init 5 (fun i -> float i * 0.1)
printfn "%A" weightsRecords and discriminated unions
Records hold data; discriminated unions model choices. Pattern matching makes the compiler check that every case is handled.
type Gpu = { Name: string; Vram: int }
type Device =
| Cpu
| Gpu of Gpu
| Tpu of version: int
let describe device =
match device with
| Cpu -> "CPU"
| Gpu g when g.Vram >= 80 -> sprintf "data center GPU %s" g.Name
| Gpu g -> sprintf "GPU %s (%d GB)" g.Name g.Vram
| Tpu v -> sprintf "TPU v%d" v
[ Cpu; Gpu { Name = "H100"; Vram = 80 }; Tpu 5 ] |> List.iter (describe >> printfn "%s")Options, results and async
Option replaces null and Result models success or failure. task { } blocks write asynchronous code that reads like normal code.
let tryParse (s: string) =
match System.Int32.TryParse s with
| true, v -> Some v
| _ -> None
printfn "%A %A" (tryParse "42") (tryParse "abc")
open System.Net.Http
let fetchLength (url: string) = task {
use client = new HttpClient()
let! body = client.GetStringAsync url
return body.Length
}
printfn "%d" (fetchLength "https://example.com").ResultAI with F#
F# works with the .NET AI stack: ML.NET for classic ML, TorchSharp (PyTorch bindings) for deep learning, ONNX Runtime for inference, and Microsoft.Extensions.AI or Semantic Kernel for LLM apps. Polyglot Notebooks let you explore data interactively.
The essential libraries
Gradient descent from scratch
Fit y = 3x + 0.5 with arrays and a fold.
let n = 100
let xs = Array.init n (fun i -> -1.0 + 2.0 * float i / float (n - 1))
let ys = xs |> Array.map (fun x -> 3.0 * x + 0.5)
let step (w, b) _ =
let errs = Array.map2 (fun x y -> w * x + b - y) xs ys
let gw = 2.0 / float n * Array.sum (Array.map2 (*) errs xs)
let gb = 2.0 / float n * Array.sum errs
(w - 0.1 * gw, b - 0.1 * gb)
let w, b = Seq.fold step (0.0, 0.0) [1 .. 500]
printfn "w = %.2f, b = %.2f" w bBuild an AI agent in F#
System.Net.Http.Json and System.Text.Json are built into .NET, so a tool-calling loop needs no extra packages.
open System.Net.Http
open System.Net.Http.Json
open System.Text.Json
open System.Text.Json.Nodes
let http = new HttpClient()
let url = "http://localhost:11434/v1/chat/completions"
let tools = JsonNode.Parse """[{"type":"function","function":{"name":"get_weather","description":"Weather for a city",
"parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}]"""
let messages = JsonArray()
messages.Add(JsonObject(dict [ "role", JsonValue.Create "user" :> JsonNode
"content", JsonValue.Create "Should I take an umbrella in Lahore today?" ]))
let getWeather (city: string) = sprintf "It is 24°C and sunny in %s." city
let rec loop round =
if round > 0 then
let body = JsonObject(dict [ "model", JsonValue.Create "llama3.1" :> JsonNode
"messages", messages.DeepClone(); "tools", tools.DeepClone() ])
let res = http.PostAsJsonAsync(url, body).Result.Content.ReadFromJsonAsync<JsonNode>().Result
let msg = res["choices"][0]["message"]
messages.Add(msg.DeepClone())
match msg["tool_calls"] with
| :? JsonArray as calls when calls.Count > 0 ->
for c in calls do
let args = JsonNode.Parse(c["function"]["arguments"].GetValue<string>())
let reply = JsonObject(dict [ "role", JsonValue.Create "tool" :> JsonNode
"tool_call_id", JsonValue.Create(c["id"].GetValue<string>())
"content", JsonValue.Create(getWeather (args["city"].GetValue<string>())) ])
messages.Add reply
loop (round - 1)
| _ -> printfn "%s" (msg["content"].GetValue<string>())
loop 5For production apps, Microsoft.Extensions.AI handles tool calling for you across many providers.
ollama pull llama3.1, and keep it running. The code talks to its OpenAI-compatible API at http://localhost:11434/v1, so the same program also works with vLLM, llama.cpp server or a cloud API by changing the URL, model name and adding an API key.F# learning roadmap
Tick items off as you go. Your progress is saved in this browser.
Beginner
Intermediate
Advanced
Projects to build
F# cheat sheet
| Task | Syntax |
|---|---|
| Value | let x = 5 |
| Function | let add a b = a + b |
| Lambda | fun x -> x * 2 |
| Pipe | xs |> List.map f |
| Match | match x with | Some v -> v | None -> 0 |
| Record | { Name = "H100"; Vram = 80 } |
Common mistakes
- Indentation is significant; mixing tabs and spaces causes errors.
- File order matters in projects: a file can only use code defined above it.
=compares values in expressions; use<-to assign to a mutable.
Where to go next
- F# docs — official
- F# for Fun and Profit — free guide
- fsharp.org — community
On TensorCore.si: C# volume, OCaml volume.
Keyboard: [ previous language · ] next language · / search the library