🪻 A type-safe, highly concurrent and asynchronous library for F# based on pure functional programming
- Introduction
- Built With
- Getting Started
- Usage
- Benchmarks
- Performance
- License
- Contact
- Acknowledgments
FIO is a type-safe, highly concurrent and asynchronous library for the F# programming language. Based on pure functional programming principles, it serves as an embedded domain-specific language (DSL) empowering developers to craft type-safe, concurrent and maintainable programs with ease using functional effects.
Harnessing concepts from functional programming, FIO simplifies the creation of scalable and efficient concurrent applications. It introduces the IO monad to manage expressions with side effects and employs “green threads” (also known as fibers) for scalable and efficient concurrency. FIO aims to provide an environment similar to that of ZIO, drawing inspiration from both ZIO and Cats Effect.
FIO was initially developed as part of a master's thesis in Computer Science and Engineering at the Technical University of Denmark (DTU). You can read the thesis, which provides more details about FIO, here. Some parts may be outdated as development continues.
DISCLAIMER: FIO is in early development stages and a lot of improvements and enhancements can be made. If you think the project sounds interesting, do not hesitate to create a PR or contact me for further information or assistance.
FIO is built using the following technologies:
It is easy to get started with FIO.
-
Download and install .NET
-
Download and install a compatible IDE such as Visual Studio or Rider, or a text editor like Visual Studio Code
-
Download or clone this repository
-
Open it in your IDE or text editor of choice
-
Navigate to the FIO.Examples project and check out the example programs or create a new F# file to start using FIO
There are currently two ways of using FIO. It is possible to create effects directly and execute them using one of FIO's runtime systems. This gives the developer more control over the runtime and how the effect is executed. In addition, the core FIO library provides a FIOApp type which encapsulates elements such as the runtime which may not be important for the developer.
Create a new F# file and import the library using open FIO.Core
in either the cloned repository or a project with the FIO NuGet package installed. To use FIO's advanced runtime, add open FIO.Runtime.Advanced
as well. For example:
module DirectUsage
open System
open FIO.Core
open FIO.Runtime.Advanced
[<EntryPoint>]
let main _ =
let askForName = fio {
do! !+ printfn("Hello! What is your name?")
let! name = !+ Console.ReadLine()
do! !+ printfn($"Hello, %s{name}, welcome to FIO! 🪻💜")
}
let fiber = AdvancedRuntime().Run askForName
let result = fiber.AwaitResult()
printfn $"%A{result}"
exit 0
You can then execute the program with
$ dotnet run
and you'll see
Hello! What is your name?
Daniel
Hello, Daniel, welcome to FIO! 🪻💜
Ok ()
In general it is recommended to create a type extending the FIOApp type. A FIOApp is essentially a wrapper around the effect which hides elements such as the runtime system and makes it possible to write cleaner FIO programs. For example:
module FIOAppUsage
open System
open FIO.Core
type WelcomeApp() =
inherit FIOApp<unit, obj>()
override this.effect = fio {
do! !+ printfn("Hello! What is your name?")
let! name = !+ Console.ReadLine()
do! !+ printfn($"Hello, %s{name}, welcome to FIO! 🪻💜")
}
WelcomeApp().Run()
Once again, you can execute the FIOApp using
$ dotnet run
and you'll see the same result as before
Hello! What is your name?
Daniel
Hello, Daniel, welcome to FIO! 🪻💜
Ok ()
Side note: It is also possible to avoid using the FIO computation expression and instead directly use the FIO DSL. The above example would then look like this:
let askForName =
!+ printfn("Hello! What is your name?") >>= fun _ ->
!+ Console.ReadLine() >>= fun name ->
!+ printfn($"Hello, %s{name}, welcome to FIO! 🪻💜")
where >>=
is FIO's bind function.
This repository contains five benchmarks that each test an aspect of concurrent computing. All benchmarks reside from the Savina - An Actor Benchmark Suite paper.
- Pingpong (Message sending and retrieval)
- ThreadRing (Message sending and retrieval, context switching between fibers)
- Big (Contention on channel, many-to-many message passing)
- Bang (Many-to-one messaging)
- Fork (Spawning time of fibers)
The benchmarks can be given the following command line options:
USAGE: FIO.Benchmarks [--help] [--naive-runtime] [--intermediate-runtime <evalworkers> <blockingworkers> <evalsteps>]
[--advanced-runtime <evalworkers> <blockingworkers> <evalsteps>]
[--deadlocking-runtime <evalworkers> <blockingworkers> <evalsteps>] --runs <runs>
[--process-increment <actor inc> <inc times>] [--pingpong <rounds>]
[--threadring <actors> <rounds>] [--big <actors> <rounds>] [--bang <actors> <rounds>]
[--fork <actors>]
OPTIONS:
--naive-runtime specify naive runtime.
--intermediate-runtime <evalworkers> <blockingworkers> <evalsteps>
specify evaluation workers, blocking workers and eval steps for intermediate runtime.
--advanced-runtime <evalworkers> <blockingworkers> <evalsteps>
specify evaluation workers, blocking workers and eval steps for advanced runtime.
--deadlocking-runtime <evalworkers> <blockingworkers> <evalsteps>
specify evaluation workers, blocking workers and eval steps for deadlocking runtime.
--runs <runs> specify the number of runs for each benchmark.
--process-increment <actor inc> <inc times>
specify the value of actor increment and how many times.
--pingpong <rounds> specify rounds for pingpong benchmark.
--threadring <actors> <rounds>
specify actors and rounds for threadring benchmark.
--big <actors> <rounds>
specify actors and rounds for big benchmark.
--bang <actors> <rounds>
specify actors and rounds for bang benchmark.
--fork <actors> specify actors for fork benchmark.
--help display this list of options.
For example, running 30 runs of each benchmark using the advanced runtime with 7 evaluation workers, 1 blocking worker and 15 evaluation steps would look as so:
--advanced-runtime 7 1 15 --runs 30 --pingpong 120000 --threadring 2000 1 --big 500 1 --bang 3000 1 --fork 3000
Additionally, FIO supports two conditional compilation options:
- DETECT_DEADLOCK: Enables a naive deadlock detecting thread that attempts to detect if a deadlock has occurred when running FIO programs
- MONITOR: Enables a monitoring thread that prints out data structure content during when running FIO programs
DISCLAIMER: These features are very experimental and may not work as intended.
Below the scalability of each runtime system can be seen for each benchmark. I is denoting the intermediate runtime and A the advanced. To give some insight into the runtimes, the naive runtime uses operating system threads, the intermediate uses fibers with handling of blocked fibers in linear time, and the advanced uses fibers with constant time handling.
Distributed under the GNU General Public License v3.0. See LICENSE.md for more information.
Daniel Larsen (iyyel) - iyyel.io - me@iyyel.io