- Published on
Modern F# 2026 — F# 9 (.NET 9) / Saturn / Giraffe / Bolero / Fable / Feliz / Elmish / Fantomas Deep-Dive Guide
- Authors

- Name
- Youngju Kim
- @fjvbn20031
Prologue — People Still Write F# in 2026
Every year someone asks, "F#? People still use that?"
Yes. And increasingly seriously, inside the .NET world.
- Jet.com (now part of Walmart Labs) wrote their pricing engine in F# back in the mid-2010s. After acquisition, core pricing models stayed in F#.
- Microsoft itself has compiler teams and Azure services (cost calculation, licensing, analytics) that ship F# code. F# was created at Microsoft Research Cambridge by Don Syme starting in 2005.
- CitiBank, Credit Suisse, Standard Chartered, Bank of America — finance keeps a steady seat for F# in pricing and risk models. Domain modeling is strong and side effects are easy to fence in via types.
- AT&T, Demetrix, Indaba Music, GameSys, Tachyus, G-Research — data pipelines, ML, game backends running F#.
- In Japan, ユーザックシステム (Usac System) builds RPA and logistics automation with F# and .NET in Tokyo, Osaka, and Nagoya. Microsoft Japan's F# evangelists cite them often.
The enemy of F# in the 2010s wasn't "hard to write." The real enemy was: "On Mono, non-Windows builds break, Visual Studio for Mac works half the time, and you cannot decide between Paket and NuGet." What does that look like in 2026?
Answer: .NET 9 is genuinely cross-platform, F# 9 finished nullable reference types, and Fantomas became the standard formatter. Almost no one says "it does not build on my laptop" anymore. One dotnet CLI runs identically on Windows, macOS, and Linux.
This post is a single-breath tour of the F# stack in 2026: compiler, web frameworks, client side, desktop UI, build tools, data, numerics, ML, SQL, parsers, and the people.
1. Modern F# in 2026 — The .NET 9 Era
A one-page picture first.
Modern F# 2026 Stack
[runtime] .NET 9 (Nov 2024, LTS) — single cross-platform SDK
[compiler] F# 9 (Nov 2024) / F# 10 preview (GitHub main)
[build] dotnet CLI / FAKE / Paket / MSBuild
[web backend] Saturn (Rails-style, opinionated)
Giraffe (functional ASP.NET Core middleware)
Suave (legacy, self-hosted HTTP)
[web client] Fable (F# to JS) + Feliz (React) + Elmish (Elm pattern)
Bolero (Blazor WebAssembly + F#)
[desktop] Avalonia.FuncUI (declarative cross-platform UI)
[tooling] Fantomas (formatter) / Ionide (VS Code) / FsAutoComplete
Polyglot Notebooks (Jupyter on .NET)
[data] FSharp.Data (CSV/JSON/XML/SQL type providers)
Donald (thin SQL client)
[numerics·ML] MathNet.Numerics (linear algebra)
DiffSharp (autodiff, PyTorch-style)
TorchSharp (libtorch bindings) + ML.NET
[concurrency] Hopac (concurrent ML style)
async / Task
[parsers] FParsec (Parsec port)
[cloud] F# in Azure Functions / AWS Lambda
F# in Aspire (distributed .NET workflows)
[production users] Jet.com (Walmart) / Microsoft / CitiBank /
Credit Suisse / GameSys / Tachyus / Usac
That is the 2026 F# landscape. Let us walk every box.
2. F# 9 (Nov 2024, ships with .NET 9) — Nullable + DU Improvements
F# 9 was released in November 2024 together with .NET 9. .NET 9 is a 12-month STS release, but F# 9 itself is bundled with the SDK and rides naturally into the next .NET 10 LTS due November 2025.
There are three core changes.
Nullable reference types completed
C# introduced nullable reference types via ? in C# 8 (2019). F# always had Option for the same purpose, so for years the position was "F# code rarely sees null." But every interop with the BCL (the .NET base class library written in C#) leaked nulls back in.
F# 9 makes nullable reference types a first-class citizen at the interop boundary.
// F# 9: nullable annotation
let processName (name: string | null) =
match name with
| null -> "(anonymous)"
| n -> n.ToUpper()
// Safe when calling C# methods
let s: string | null = System.Environment.GetEnvironmentVariable("HOME")
match s with
| null -> printfn "HOME not set"
| home -> printfn "HOME = %s" home
string | null maps exactly to C# 9's string?. The compiler checks it and warns when you call methods on a value before unwrapping null.
Discriminated union improvements
DUs are the soul of F#. F# 9 improves two things:
- Lower boxing cost for struct DUs —
[<Struct>]unions stay on the stack more aggressively. - Per-case visibility —
privateandinternalcan now be applied to individual union cases.
[<Struct>]
type Result<'T, 'E> =
| Ok of value: 'T
| Error of error: 'E
// Visibility per case
type DbHandle =
private | Open of System.Data.IDbConnection
| Closed
Standard library improvements
List.randomShuffle,Array.randomChoice, and friends.Resultmodule additions:Result.toList,Result.toOption,Result.toArray.Seq.tryExactlyOnereturns Some only when the sequence has exactly one element.
let xs = [1; 2; 3]
let shuffled = xs |> List.randomShuffle
// e.g. [2; 1; 3]
let oneOnly = [42] |> Seq.tryExactlyOne
// Some 42
let twoElems = [1; 2] |> Seq.tryExactlyOne
// None
3. F# 10 Preview — Cooking on GitHub
F# 10 is in active development on the dotnet/fsharp main branch. As of May 2026, the following candidates sit in RFC.
Shorter let! joins in task
Combining multiple async results inside task { ... } gets long with chained let!. F# 10 evaluates an expression-builder extension to compress that.
// F# 9 style
task {
let! a = getUserAsync userId
let! b = getOrdersAsync userId
return (a, b)
}
// F# 10 preview short form under discussion
task {
let! (a, b) = getUserAsync userId, getOrdersAsync userId
return (a, b)
}
Asynchronous type providers
JsonProvider and CsvProvider from FSharp.Data hit the network at compile time to fetch schemas. On large schemas the IDE can stall. F# 10 plans an async type provider plumbing.
Resumable code improvements
Resumable code, introduced in F# 6 (the basis of task { } and taskSeq { }), is an internal compiler feature. F# 10 wants to expose it more safely so users can build their own effects, e.g. for Hopac or custom schedulers.
Self-type constraints
To meet C# 11's static abstract interface members. Makes SRTPs (Statically Resolved Type Parameters) more natural to express.
F# 10 ships with .NET 10 LTS in November 2025.
4. Saturn — Opinionated Web Framework on Top of Giraffe
Saturn was created by Krzysztof Cieslak (SAFE Stack maintainer, the person behind Ionide). Think of it as a Rails-style layer sitting on top of Giraffe.
Core concept
Saturn provides an application builder DSL. The ASP.NET Core middleware pipeline gets folded into an F# builder syntax.
open Saturn
let app =
application {
use_router topRouter
url "http://0.0.0.0:8085/"
use_gzip
use_static "public"
use_developer_exceptions
}
run app
That is the entire entry point. Each token inside application { } is a function; Saturn defines the application computation expression and every keyword (use_router, url, ...) acts on an ApplicationBuilder.
Controller — MVC style
Saturn ships a controller builder too.
let userController = controller {
index (fun ctx -> "Listing all users" |> Controller.text ctx)
show (fun ctx id -> sprintf "Showing user %i" id |> Controller.text ctx)
create (fun ctx -> "Creating new user" |> Controller.text ctx)
edit (fun ctx id -> sprintf "Editing user %i" id |> Controller.text ctx)
delete (fun ctx id -> sprintf "Deleting user %i" id |> Controller.text ctx)
}
let topRouter = router {
forward "/users" userController
}
All seven REST actions in one builder. Rails users find the shape familiar.
SAFE Stack
Saturn rarely travels alone. Usually it ships as part of the SAFE Stack.
- S — Saturn (server)
- A — Azure (hosting)
- F — Fable (client, F# to JS)
- E — Elmish (Elm pattern)
dotnet new SAFE scaffolds a full F# stack project. Client and server share the same F# domain types, which is the killer feature.
5. Giraffe — Functional ASP.NET Core
Giraffe is the lower half of Saturn. It wraps the ASP.NET Core HttpContext pipeline in a functional shape.
HttpHandler
Giraffe's core abstraction is HttpHandler.
type HttpHandler = HttpFunc -> HttpContext -> HttpFuncResult
// where HttpFunc = HttpContext -> HttpFuncResult
// HttpFuncResult = Task<HttpContext option>
Take "next handler" and "current context," return a Task that yields the updated context (or None). It corresponds exactly to one slot of ASP.NET Core middleware.
Composition
The >=> operator composes handlers.
open Giraffe
let webApp =
choose [
route "/" >=> text "Hello, world"
route "/json" >=> json {| message = "hello" |}
route "/api/users" >=> requireAuth >=> getUsers
routef "/user/%i" (fun id -> sprintf "User %i" id |> text)
setStatusCode 404 >=> text "Not Found"
]
choose runs the first handler that matches. >=> says "let this handler pass, then chain the next." It looks like ordinary function composition, but it is a Kleisli composition over Task<HttpContext option>.
View — GiraffeViewEngine
You can build HTML in F#.
open Giraffe.ViewEngine
let layout (content: XmlNode list) =
html [] [
head [] [
title [] [ str "Giraffe" ]
link [ _rel "stylesheet"; _href "/style.css" ]
]
body [] content
]
let indexView =
layout [
h1 [] [ str "Hello, Giraffe" ]
p [] [ str "Functional ASP.NET Core" ]
]
Like JSX, but expressed as F# functions and lists, fully typed.
6. Bolero — Blazor + F#
Bolero started at IntelliFactory and is now maintained by the fsbolero organization. It lets you build SPAs in F# on top of Blazor WebAssembly.
Why Bolero
- Blazor is Microsoft's official framework for C# WebAssembly SPAs.
- Bolero layers F# and the Elmish (MVU) pattern on top.
- Result: type safety + components + F#-DSL HTML + Elm-style state management.
open Bolero
open Bolero.Html
open Elmish
type Model = { count: int }
type Message =
| Increment
| Decrement
let init () = { count = 0 }, Cmd.none
let update msg model =
match msg with
| Increment -> { model with count = model.count + 1 }, Cmd.none
| Decrement -> { model with count = model.count - 1 }, Cmd.none
let view model dispatch =
div {
h1 { "Bolero Counter" }
p { sprintf "Count: %d" model.count }
button {
on.click (fun _ -> dispatch Increment)
"+"
}
button {
on.click (fun _ -> dispatch Decrement)
"-"
}
}
Bolero's div { } and button { } are computation expressions. You can freely mix children, attributes, and event handlers inside.
Server-side vs WebAssembly
Bolero supports both hosting models.
- Server-side Blazor — UI updates flow through SignalR from the server.
- Blazor WebAssembly — F# code compiles to .NET IL and runs directly in the WASM runtime.
WebAssembly mode has no server load but ships a larger initial download. Bolero 3.x added AOT (Ahead-Of-Time) compilation that dramatically improves startup.
7. Fable — F# to JavaScript
Fable was started by Alfonso Garcia-Caro. It is now maintained by a core team led by Maxime Mangel.
Fable 5
Fable 5, released in 2024, ships multiple backends.
- JavaScript — the default. Works in Node.js and browsers.
- TypeScript — emits
.tsfiles that drop into existing TS code. - Python — Fable to Python: Jupyter notebooks, Python backends.
- Rust — experimental. Carries some F# safety into Rust.
- Dart — Flutter apps in F#.
// F# source
module App
open Browser
let main () =
let body = document.body
body.innerHTML <- "<h1>Hello from F#</h1>"
main ()
dotnet fable produces a JavaScript ESM module.
How it works
Fable uses FSharp.Compiler.Service to parse the F# source into an AST, then translates that AST into JavaScript (or another target). BCL functions map to Fable's own JS implementations of Map, Seq, List, Option, and so on.
With Fable.React / Feliz / Elmish
Fable itself is just a compiler. UI libraries are separate.
dotnet new -i Fable.Template
dotnet new fable-react
npm install
npm run start
The default Fable React template is the usual starting point.
8. Feliz — F# React-like DSL
Feliz is Zaid Ajaj's React DSL. It expresses React components more tersely and cleanly than Fable.React.
Example
open Feliz
let helloWorld = Html.div [
prop.className "container"
prop.children [
Html.h1 "Hello, Feliz"
Html.p "F# React without JSX"
]
]
[<ReactComponent>]
let Counter () =
let count, setCount = React.useState 0
Html.div [
Html.h2 (sprintf "Count: %d" count)
Html.button [
prop.onClick (fun _ -> setCount (count + 1))
prop.text "+"
]
]
Html.div [ ... ]— a div tag.prop.className,prop.onClick,prop.children— every prop is strongly typed.[<ReactComponent>]— Feliz's compile plug-in turns the function into a React component.React.useState,React.useEffect, and every other hook are exposed with F# signatures.
You can use all of React without ever writing JSX.
Feliz.Bulma / Feliz.MaterialUI
The ecosystem is thick.
- Feliz.Bulma — a wrapper around the Bulma CSS framework.
- Feliz.MaterialUI — F# bindings for Material-UI components.
- Feliz.Recharts — Recharts chart library.
- Feliz.Plotly — Plotly.
Almost every popular React library has a Feliz wrapper.
9. Elmish — The Elm Pattern in F#
Elmish ports Elm's MVU (Model-View-Update) pattern to F#. It is the standard for state management in Bolero, Fable + React, and the SAFE Stack.
Three functions
// 1. initial state
let init () : Model * Cmd<Msg> = ...
// 2. message handling
let update (msg: Msg) (model: Model) : Model * Cmd<Msg> = ...
// 3. rendering
let view (model: Model) (dispatch: Msg -> unit) : ReactElement = ...
That is the whole thing. Far simpler than Redux or Vuex. If you know Elm, you can transcribe directly.
Example
open Elmish
open Elmish.React
open Feliz
type Model = { Count: int }
type Msg =
| Increment
| Decrement
| Reset
let init () = { Count = 0 }, Cmd.none
let update msg model =
match msg with
| Increment -> { model with Count = model.Count + 1 }, Cmd.none
| Decrement -> { model with Count = model.Count - 1 }, Cmd.none
| Reset -> { Count = 0 }, Cmd.none
let view model dispatch =
Html.div [
Html.h1 (sprintf "Count: %d" model.Count)
Html.button [
prop.onClick (fun _ -> dispatch Increment)
prop.text "+"
]
Html.button [
prop.onClick (fun _ -> dispatch Decrement)
prop.text "-"
]
Html.button [
prop.onClick (fun _ -> dispatch Reset)
prop.text "Reset"
]
]
Program.mkProgram init update view
|> Program.withReactSynchronous "elmish-app"
|> Program.run
The browser-mount code is the last four lines. Program.mkProgram takes init/update/view and builds the run loop.
Cmd — Effect isolation
Cmd<Msg> represents "side effects that should happen now." HTTP requests, timers, localStorage access — all expressed as commands.
let loadUserCmd userId =
Cmd.OfPromise.either
(fun () -> Fetch.fetchAs<User> (sprintf "/api/users/%d" userId))
()
UserLoaded
LoadFailed
The update function stays pure. Side effects escape via Cmd, which the runtime executes. Tests fall out trivially.
10. Avalonia.FuncUI — Declarative Desktop UI
Avalonia is the cross-platform .NET UI framework that picked up where WPF left off. The same code runs on Windows, macOS, Linux, iOS, Android, and WebAssembly.
Avalonia.FuncUI layers a functional DSL based on F# computation expressions on top of Avalonia.
Example
open Avalonia.FuncUI.DSL
open Avalonia.Controls
open Avalonia.Layout
let view () =
Component (fun ctx ->
let count = ctx.useState 0
DockPanel.create [
DockPanel.children [
TextBlock.create [
TextBlock.text (sprintf "Count: %d" count.Current)
TextBlock.horizontalAlignment HorizontalAlignment.Center
]
Button.create [
Button.content "+"
Button.onClick (fun _ -> count.Set (count.Current + 1))
]
Button.create [
Button.content "-"
Button.onClick (fun _ -> count.Set (count.Current - 1))
]
]
]
)
No XAML. Every UI fragment is F# code. A React-hook-style useState is built in.
Who uses it
- JetBrains Rider uses Avalonia for some auxiliary UIs (the main IntelliJ platform is Java/Kotlin, but separate tooling uses Avalonia).
- Sirius A, an F# community IRC/Matrix client.
- Internal tooling teams in .NET shops that want to ship F# desktop apps. Since Visual Studio for Mac shut down, this is roughly the only sane way to build .NET UI on macOS.
11. Tooling — Fantomas / Paket / FAKE / Polyglot Notebooks
Fantomas
Fantomas is the official F# formatter. Florian Verdonck and the core team maintain it.
dotnet tool install -g fantomas
fantomas src/
- Formats using the compiler's own AST, so the result is semantically identical to the input.
- Configured via
.editorconfigorfantomas-config.fsharpsettings. - Integrated with VS Code (Ionide), Rider, and Visual Studio.
Since 2024 Fantomas has lived in the same fsprojects/fantomas org alongside the F# compiler and is the de facto F# style standard.
Paket
Paket is an alternative package manager to NuGet, created by Steffen Forkmann.
Advantages:
- Pins transitive dependencies explicitly in a single
paket.lockfile. SDK-style PackageReference improved a lot, but its lock story is still subtle. - Lets you add GitHub repos, gists, or HTTP URLs as dependencies.
- Especially popular in F# projects.
source https://api.nuget.org/v3/index.json
nuget Giraffe ~> 6.0
nuget FSharp.Data ~> 6.0
github fsharp/FSharp.Core
NuGet has improved enough to shrink Paket's footprint, but in large mono-repos it is still preferred.
FAKE
FAKE (F# Make) is the F#-based build script tool.
#r "paket: nuget Fake.Core.Target //"
#load ".fake/build.fsx/intellisense.fsx"
open Fake.Core
open Fake.IO
Target.create "Clean" (fun _ -> Shell.cleanDirs [ "bin"; "obj" ])
Target.create "Build" (fun _ ->
DotNet.build id "src/MyApp.fsproj"
)
Target.create "Test" (fun _ ->
DotNet.test id "tests/MyApp.Tests.fsproj"
)
"Clean" ==> "Build" ==> "Test"
Target.runOrDefault "Test"
The ==> operator builds the dependency graph. Think Make, Rake, Gulp — but in F#.
In 2026 the combo of dotnet CLI plus GitHub Actions covers most pipelines without FAKE, so new projects use it less. Existing codebases still treat it as standard.
Polyglot Notebooks
Polyglot Notebooks is Microsoft's notebook environment. It feels like Jupyter but runs on .NET, and you can mix F#, C#, PowerShell, JavaScript, SQL, and KQL cells in one notebook.
Install the VS Code "Polyglot Notebooks" extension and you are done.
// F# cell
open Microsoft.DotNet.Interactive
#r "nuget: FSharp.Data, 6.4.0"
open FSharp.Data
let stocks = CsvProvider<"https://example.com/stocks.csv">.GetSample()
stocks.Rows |> Seq.take 5 |> Seq.iter (fun r -> printfn "%A" r)
The closest path F# has to replacing Python in data analysis, education, and reporting.
12. FSharp.Data — Type Providers
Type providers are F#'s killer feature. They fetch the schema of an external data source at compile time and expose it as a strongly typed API with IDE autocomplete.
CsvProvider
open FSharp.Data
type Stocks = CsvProvider<"https://example.com/stocks.csv">
let stocks = Stocks.GetSample()
for row in stocks.Rows do
printfn "%s: open=%f, close=%f" row.Date row.Open row.Close
row.Date, row.Open, and row.Close are strongly typed properties generated by the compiler reading the CSV header. If a column name changes, you get a compile error immediately.
JsonProvider
type GitHubRepo = JsonProvider<"https://api.github.com/repos/dotnet/fsharp">
let repo = GitHubRepo.GetSample()
printfn "Name: %s" repo.Name
printfn "Stars: %d" repo.StargazersCount
printfn "Default branch: %s" repo.DefaultBranch
The JSON response maps to types automatically. Renamed keys fail at compile time.
SqlProvider / WorldBankProvider / FreebaseProvider
- SqlProvider — connects to PostgreSQL, SQL Server, SQLite, or MySQL and exposes tables, views, and stored procedures as types.
- WorldBankProvider — strongly typed access to World Bank statistics.
- FreebaseProvider — Freebase is gone, but it was the canonical type-provider demo.
Type providers are a feature unique to F#. C#, Java, and Kotlin do not have this kind of metaprogramming surface. For data-heavy work, this alone is reason enough to choose F#.
13. MathNet.Numerics / DiffSharp — Numerics and Autodiff
MathNet.Numerics
MathNet.Numerics is the numerical-computing library for .NET. Think NumPy and SciPy for .NET.
open MathNet.Numerics.LinearAlgebra
let m = matrix [[ 1.0; 2.0 ]
[ 3.0; 4.0 ]]
let v = vector [ 5.0; 6.0 ]
let result = m * v
printfn "%A" result
// DenseVector 2-Double
// 17
// 39
let inv = m.Inverse()
printfn "%A" inv
- Dense and sparse matrices.
- BLAS / LAPACK acceleration (Intel MKL, OpenBLAS).
- Statistics, integration, optimization, FFT, distributions.
DiffSharp
DiffSharp is the autodiff library for .NET. Created by Atılım Güneş Baydin and collaborators. You define tensor operations PyTorch-style and DiffSharp differentiates them automatically.
open DiffSharp
let f (x: Tensor) = x ** 2.0 + 2.0 * x + 1.0
let x = dsharp.tensor [ 1.0; 2.0; 3.0 ]
let y = f x
let dy = dsharp.diff f x
printfn "y = %A" y // [4; 9; 16]
printfn "dy = %A" dy // [4; 6; 8] (i.e. 2x + 2)
DiffSharp backends include TorchSharp (libtorch C++ bindings) or a reference backend. GPU acceleration is supported.
Used for research code, differentiable programming, and gradient-based optimization.
14. TorchSharp + ML.NET — Machine Learning
TorchSharp
TorchSharp is the .NET PyTorch binding. It P/Invokes into libtorch C++. You get an API that maps almost 1:1 with PyTorch but written in F# (or C#).
open TorchSharp
open type torch
let device = if cuda.is_available() then CUDA else CPU
let model =
Sequential(
("fc1", Linear(784L, 256L)),
("relu1", ReLU()),
("fc2", Linear(256L, 10L))
)
|> fun m -> m.``to``(device)
let x = randn([| 64L; 784L |]).``to``(device)
let y = model.forward(x)
printfn "Output shape: %A" y.shape
Type-safe tensor operations, GPU support, save and load models.
ML.NET
ML.NET is Microsoft's .NET ML framework. It is smooth from F# too.
open Microsoft.ML
open Microsoft.ML.Data
[<CLIMutable>]
type HouseData = {
Size: float32
Price: float32
}
[<CLIMutable>]
type Prediction = {
[<ColumnName("Score")>]
Price: float32
}
let mlContext = MLContext()
let trainingData = [
{ Size = 1.1f; Price = 1.2f }
{ Size = 1.9f; Price = 2.3f }
{ Size = 2.8f; Price = 3.0f }
{ Size = 3.4f; Price = 3.7f }
]
let dataView = mlContext.Data.LoadFromEnumerable trainingData
let pipeline =
mlContext.Transforms
.Concatenate("Features", "Size")
.Append(mlContext.Regression.Trainers.Sdca(labelColumnName = "Price", maximumNumberOfIterations = 100))
let model = pipeline.Fit dataView
let predictor = mlContext.Model.CreatePredictionEngine<HouseData, Prediction>(model)
let size = { Size = 2.5f; Price = 0f }
let prediction = predictor.Predict size
printfn "Predicted price for size %f: %f" size.Size prediction.Price
ML.NET covers gradient boosting, random forests, logistic regression, recommendation systems, text classification, image classification, and more. Importing TensorFlow and ONNX models is supported.
The [<CLIMutable>] attribute creates the setters ML.NET requires (F# records are immutable by default).
15. F# in Azure Functions / AWS Lambda
Azure Functions
The F# Isolated Worker model is standard on .NET 9.
namespace MyApp
open System
open Microsoft.Azure.Functions.Worker
open Microsoft.Azure.Functions.Worker.Http
open Microsoft.Extensions.Logging
type HelloFunction(logger: ILogger<HelloFunction>) =
[<Function("Hello")>]
member _.Run
([<HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")>] req: HttpRequestData)
=
logger.LogInformation "F# HTTP trigger function processed a request"
let response = req.CreateResponse(System.Net.HttpStatusCode.OK)
response.WriteString "Hello from F# on Azure Functions"
response
Microsoft itself treats F# as a first-class language on Azure Functions. Scaffold with func init --worker-runtime dotnet-isolated --language fsharp.
AWS Lambda
AWS's .NET 6/8 runtimes run F# Lambdas just fine.
namespace MyLambda
open Amazon.Lambda.Core
[<assembly: LambdaSerializer(typeof<Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer>)>]
do ()
type Handler() =
member _.Hello (input: string) (_: ILambdaContext) : string =
sprintf "Hello %s from F# Lambda" input
Deploy with the Amazon.Lambda.Tools CLI. Some larger shops write cloud automation scripts in F#.
16. Donald / Suave / Hopac / FParsec — The Rest
Donald — Thin SQL client
Donald is a thin helper over ADO.NET. Not an ORM like EF Core — you write SQL directly and map the results to strongly typed records.
open Donald
let conn = new System.Data.SqlClient.SqlConnection(connStr)
let sql = "SELECT id, name, email FROM users WHERE active = 1"
type User = { Id: int; Name: string; Email: string }
let readUser (rd: System.Data.IDataReader) =
{ Id = rd.ReadInt32 "id"
Name = rd.ReadString "name"
Email = rd.ReadString "email" }
let users =
conn
|> Db.newCommand sql
|> Db.query readUser
For when you do not want LINQ or an ORM and prefer SQL as-is.
Suave
Suave was created by Henrik Feldt. It is a self-hosted HTTP server and routing library that predates Saturn and Giraffe (around 2014). The distinguishing feature is its own standalone HTTP server, not built on ASP.NET Core, which made it popular for small side projects.
open Suave
open Suave.Filters
open Suave.Operators
open Suave.Successful
let app =
choose
[ GET >=> path "/" >=> OK "Hello, Suave"
GET >=> pathScan "/user/%d" (fun id -> OK (sprintf "User %d" id)) ]
startWebServer defaultConfig app
In 2026 new projects mostly default to Giraffe or Saturn, but Suave remains a lightweight option when attaching a tiny HTTP layer to a CLI or utility.
Hopac
Hopac is Vesa Karvonen's concurrency library. It ports the channels-and-jobs model of Concurrent ML / Reppy into F#.
open Hopac
open Hopac.Infixes
let producer (ch: Ch<int>) =
Job.forUpToIgnore 0 9 (fun i -> Ch.give ch i)
let consumer (ch: Ch<int>) =
Job.forUpToIgnore 0 9 (fun _ ->
Ch.take ch >>= fun v ->
Job.unit (printfn "Received: %d" v))
job {
let ch = Ch<int>()
do! Job.start (producer ch)
do! consumer ch
}
|> run
Job is a lightweight thread. Channel-based message passing, synchronous communication, and selection (Alt) come straight from Concurrent ML.
Use it when async or Task is not expressive enough or when you need high-performance message passing. Jet.com famously ran Hopac in parts of its pricing engine.
FParsec
FParsec is Stephan Tolksdorf's F# port of Parsec, the Haskell parser-combinator library.
open FParsec
let pInteger : Parser<int, unit> = pint32
let pPlus = pchar '+' >>. spaces
let pSum =
pInteger .>>. (pPlus >>. pInteger)
|>> (fun (a, b) -> a + b)
let result = run pSum "12 + 30"
match result with
| Success (v, _, _) -> printfn "Sum = %d" v
| Failure (err, _, _) -> printfn "Parse error: %s" err
Builds LL(1)+ backtracking parsers easily. The standard tool for DSLs, config-file parsers, and domain-specific languages.
17. Korea / Japan — Korean .NET Enterprise, Usac, MS Japan
Korea
Honestly, Korea's F# community is small. .NET itself in Korea lives mostly in game servers (especially Unity backends), parts of the financial sector, and public-sector SI work. F# shows up in a slice of those companies as a domain-modeling or data-pipeline tool.
- Some Kakao and Line data-analysis scripts — F# pops up as an ad-hoc analysis tool through Polyglot Notebooks (no official announcement; it surfaces in conference side-talks).
- NHN, Netmarble, Pearl Abyss and similar game companies use F# in back-office tools.
- Finance — modeling tools at brokerages and asset managers, but few public case studies.
- MSDN.kr and the .NET Korea User Group — Facebook group and Discord. There is no dedicated F# Korean group; F# topics surface inside the broader .NET community.
Very few F# books have Korean translations. Most readers go straight to the English Expert F# or Stylish F#.
Japan
Japan's F# community is denser than Korea's.
- ユーザックシステム (Usac System) — an RPA and logistics-automation company with offices in Tokyo, Osaka, and Nagoya. They ship parts of their product in F#. Microsoft Japan's F# evangelist Kazuya Kawakami (@kos59125) cites them frequently.
- F# Tokyo — an annual user meetup that has crossed 100 attendees in some years.
- F# Online Meetup Japan — has run regularly online since 2022, post-COVID.
- Microsoft Japan F# evangelists — Microsoft Japan has consistently pushed F# in their .NET community. Kazuhisa Yokota and Kazuya Kawakami have held that seat.
- Toppan Printing (凸版印刷) and parts of NTT Data — internal tools and analytics pipelines have been reported to use F#.
Japanese F# material is far thicker than Korean. The Qiita fsharp tag has hundreds of posts. Don Syme's books exist in Japanese translation too.
18. Who Should Learn F#
Honestly:
Worth learning if you are
- A C# developer working on .NET backends — same BCL, same runtime, same NuGet packages, plus functional style and domain modeling. C# 9's records, pattern matching, and switch expressions were inspired largely by F#. Learning F# tells you where C# is heading.
- A domain modeler in finance, insurance, logistics, or healthcare — F#'s ability to express domains via discriminated unions is in a different league. Scott Wlaschin's slogan "make illegal states unrepresentable" actually holds here.
- Someone who knows OCaml or Haskell but cannot find an industry job — F# is an ML-family cousin sitting on the massive industrial base of .NET. It can be the answer to "where do I get paid for my functional skills?"
- Anyone who wants to do strongly typed data analysis — type providers really are different in kind.
Skip it if you are
- Never going to touch .NET — F# runs on .NET. If you refuse the platform, Haskell, OCaml, or Scala are more sensible.
- A purist functional programmer — F# is multi-paradigm. Mutability is allowed. OOP classes are allowed. If you want Haskell-grade purity, you will be disappointed.
- Optimizing for the shortest learning curve — F# itself is not hard, but "learn F# without learning C# and the BCL and ASP.NET Core" is essentially impossible. You sign up for the whole .NET ecosystem.
F#'s real value is "ML-style functional domain modeling on the massive industrial infrastructure of .NET." If that combination is attractive, F# is worth your time.
19. References
- F# 9 announcement —
devblogs.microsoft.com/dotnet/announcing-fsharp-9 - F# language reference —
learn.microsoft.com/dotnet/fsharp/language-reference - F# Foundation —
fsharp.org - .NET 9 announcement —
devblogs.microsoft.com/dotnet/announcing-dotnet-9 - F# on GitHub —
github.com/dotnet/fsharp - Saturn —
saturnframework.org - Giraffe —
giraffe.wiki - Bolero —
fsbolero.io - Fable —
fable.io - Feliz —
zaid-ajaj.github.io/Feliz - Elmish —
elmish.github.io - Avalonia.FuncUI —
funcui.avaloniaui.net - Avalonia —
avaloniaui.net - Fantomas —
fsprojects.github.io/fantomas - Paket —
fsprojects.github.io/Paket - FAKE —
fake.build - Polyglot Notebooks —
code.visualstudio.com/docs/polyglot - FSharp.Data —
fsprojects.github.io/FSharp.Data - MathNet.Numerics —
numerics.mathdotnet.com - DiffSharp —
diffsharp.github.io - TorchSharp —
github.com/dotnet/TorchSharp - ML.NET —
dotnet.microsoft.com/apps/machinelearning-ai/ml-dotnet - Donald —
github.com/pimbrouwers/Donald - Suave —
suave.io - Hopac —
github.com/Hopac/Hopac - FParsec —
www.quanttec.com/fparsec - F# in Azure Functions —
learn.microsoft.com/azure/azure-functions/functions-reference-fsharp - SAFE Stack —
safe-stack.github.io - F# Software Foundation Slack —
fsharp.org/guides/slack - F# Tokyo —
fsugjp.connpass.com - Don Syme's blog —
dsyme.github.io - Scott Wlaschin — Domain Modeling Made Functional —
fsharpforfunandprofit.com - Krzysztof Cieslak — Ionide / Saturn —
kcieslak.io