README.md
September 17, 2026 ยท View on GitHub
FIO is an IO monad plus lightweight fibers (green threads) for building concurrent and asynchronous F# applications. Effects are described as pure, lazy values and run by a pluggable runtime โ so your program is a composable description that stays referentially transparent until you hand it to a runtime. The API takes its cues from ZIO.
- Typed effects โ
FIO<'A, 'E>tracks both the success value and the error in the type - Fibers & channels โ green threads via
.Fork()/.Join()and typed message passing - Structured concurrency โ fail-fast parallel combinators that interrupt losers automatically
- Finalizer guarantees โ
Ensuringfinalizers run on success, error, and interruption - Composable โ the
fio { }computation expression plus a rich set of operators
Install
dotnet add package FSharp.FIO
Quick Start
open FIO.DSL
open FIO.App
open FIO.Console
type App() =
inherit FIOApp<unit, exn>()
override _.effect = fio {
do! Console.printLine "What is your name?" id
let! name = Console.readLine id
do! Console.printLine $"Hello, {name}!" id
}
[<EntryPoint>]
let main _ = App().Run()
FIOApp runs effect, then onOutcome with the settled result, then onShutdown, and exits with
mapExitCode. Ctrl+C and SIGTERM interrupt the effect; its finalizers run before the hooks, so
cleanup that needs effect state belongs in Ensuring/acquireReleaseWith, and onShutdown is for
process-level goodbyes. A finalizer that never completes holds shutdown; a second Ctrl+C or SIGTERM
terminates the process. A defect or an invalid argument in the effect is a fatal error (exit code 2),
not an interruption (130).
Concurrency
Fork effects onto fibers, run them in parallel, and compose the results โ losers are interrupted automatically on the first failure.
open FIO.DSL
// Run two effects in parallel with <&> and collect both results as a tuple.
let taskA: FIO<string, exn> = FIO.succeed "Task A completed! โ
"
let taskB: FIO<int * string, exn> = FIO.succeed (200, "Task B OK โ
")
let both = taskA <&> taskB
// Or fork/join explicitly.
let forked: FIO<string, exn> =
FIO.succeed("Hello, concurrency! ๐").Fork() >>= fun fiber -> fiber.Join()
More in examples/ โ the DSL, App, HTTP, Sockets, and WebSockets tours.
Features
- Effects โ lazy, composable
FIO<'A, 'E>with typed errors - Fibers โ green threads for scalable concurrency
- Channels โ typed message passing between fibers
- Structured concurrency โ fail-fast
ZipPar,Race, andforEachParthat interrupt losers automatically - Composition โ
fio { }CE, operators (>>=,<&>,<|>), combinators - Refs โ
Ref<'A>, an atomic reference cell shared between fibers (withFIO.DSLopen it shadows FSharp.Core'sRef<'T>annotation;'T refis unaffected) - Modules โ
Console
Runtimes
Effects are interpreted by a runtime. Pick one explicitly, or use DefaultRuntime.
| Runtime | Notes |
|---|---|
DirectRuntime | Multi-threaded via the .NET thread pool โ one task per fiber, no scheduler of its own. Handy for tests and as a baseline. |
PollingRuntime | Multi-threaded, linear-time handling of blocked fibers (polling). |
SignalingRuntime | Multi-threaded, event-driven handling of blocked fibers. |
WorkStealingRuntime | Multi-threaded, work-stealing scheduler. The default. |
DefaultRuntime = WorkStealingRuntime โ FIOApp uses it unless you override _.runtime.
Benchmarks
Twelve concurrency workloads (Pingpong, Threadring, Chameneos, Philosophers, โฆ) run against every
runtime, reporting execution time and allocations. Pingpong is tracked per commit on the
live benchmark dashboard; the full suite,
its parameters, and the A/B comparison protocol are documented in
benchmarks/FIO.Benchmarks/README.md.
Packages
| Package | Description |
|---|---|
FSharp.FIO | Core โ effects, fibers, channels, runtimes |
FSharp.FIO.Http | HTTP server (Kestrel) |
FSharp.FIO.Sockets | TCP sockets |
FSharp.FIO.WebSockets | WebSockets |
Each extension library has its own README with API details: Http ยท Sockets ยท WebSockets.
Contributing
Issues and pull requests welcome. See CONTRIBUTING.md, the Code of Conduct, and the Security Policy.