Funk 2.0.0
dotnet add package Funk --version 2.0.0
NuGet\Install-Package Funk -Version 2.0.0
<PackageReference Include="Funk" Version="2.0.0" />
<PackageVersion Include="Funk" Version="2.0.0" />
<PackageReference Include="Funk" />
paket add Funk --version 2.0.0
#r "nuget: Funk, 2.0.0"
#:package Funk@2.0.0
#addin nuget:?package=Funk&version=2.0.0
#tool nuget:?package=Funk&version=2.0.0
<p align="center"> <img src="https://github.com/hcerim/Funk/blob/master/docs/Files/Funk.png?raw=true" width="300" /> </p>
Funk — Functional C#
A lightweight functional programming library that brings expressive, composable, and safe abstractions to C#. Less ceremony, more clarity.
Features
- Maybe<T> — explicit nullability without null reference exceptions
- Exc<T, E> — railway-oriented error handling with success, failure, and empty states
- OneOf<T1, …, T5> — type-safe discriminated unions with exhaustive matching
- Record<T1, …, T5> — immutable products with safe deconstruction and mapping
- Pattern<R> — lazy, expression-based pattern matching (sync and async)
- Data<T> & Builder<T> — fluent immutable object updates
- Prelude — terse factory functions (
may,rec,list, …) - Extensions — functional combinators on objects, tasks, enumerables, and actions
Installation
Funk is available as a NuGet package.
dotnet add package Funk
Supports: .NET 8+, .NET Standard 2.0 / 2.1
Usage
Add the namespace and optionally import the Prelude for terse factory functions:
using Funk;
using static Funk.Prelude;
Maybe<T>
Represent the possible absence of a value — no more nulls.
// Create from a value or null
Maybe<string> name = may("Funk"); // NotEmpty
Maybe<string> none = may<string>(null); // IsEmpty
// Pattern match to safely extract
string greeting = name.Match(
_ => "No name provided",
n => $"Hello, {n}!"
);
// Map — transform plain values while staying in Maybe
Maybe<int> length = name.Map(n => n.Length); // Maybe<int> = 4
// FlatMap — chain operations that themselves return Maybe
Maybe<UserProfile> GetProfile(string name) => profiles.Get(name);
Maybe<Theme> GetTheme(Guid themeId) => themes.Get(themeId);
var theme = name
.FlatMap(n => GetProfile(n))
.FlatMap(p => GetTheme(p.ThemeId)); // Maybe<Theme>
// Get with a fallback
string value = name.GetOr(_ => "default");
Exc<T, E>
Railway-oriented error handling — operations that can succeed, fail, or be empty.
// Wrap an operation that might throw
Exc<int, FormatException> parsed = Exc.Create<int, FormatException>(
_ => int.Parse("42")
);
// Map — transform plain values while keeping exception safety
Exc<string, FormatException> result = parsed.Map(n => $"The answer is {n}");
// FlatMap — chain operations that themselves return Exc
Exc<Customer, DbException> GetCustomer(Guid id) => Exc.Create<Customer, DbException>(_ => db.Find(id));
Exc<Account, DbException> GetAccount(Guid accountId) => Exc.Create<Account, DbException>(_ => db.FindAccount(accountId));
var account = GetCustomer(id)
.FlatMap(c => GetAccount(c.AccountId)); // Exc<Account, DbException>
// Pattern match all three states
string message = parsed.Match(
ifEmpty: _ => "Nothing to parse",
ifSuccess: n => $"Parsed: {n}",
ifFailure: e => $"Error: {e.Root}"
);
// Deconstruct into success and failure as Maybe values
var (success, failure) = parsed;
// success: Maybe<int>, failure: Maybe<EnumerableException<FormatException>>
// Recover from failure with OnFailure — chain fallbacks
var config = Exc.Create<Config, IOException>(_ => LoadConfigFromFile())
.OnFailure(e => LoadConfigFromNetwork())
.OnFailure(e => GetDefaultConfig());
// OnFlatFailure when the recovery itself returns an Exc
var data = Exc.Create<string, DbException>(_ => db.GetFromPrimary())
.OnFlatFailure(e => Exc.Create<string, DbException>(_ => db.GetFromReplica()));
// OnEmpty — recover when result is empty (distinct from failure)
var result = Exc.Create<Config, IOException>(_ => LoadConfigFromFile())
.OnFailure(e => GetNullableConfig())
.OnEmpty(_ => GetDefaultConfig());
// Async variants
var asyncResult = await Exc.CreateAsync<string, HttpRequestException>(
_ => httpClient.GetStringAsync("https://api.example.com/data")
).OnFailureAsync(e => httpClient.GetStringAsync("https://api.example.com/fallback"));
Pattern matching
Lazy, expression-based matching with collection initializer syntax:
int statusCode = 404;
// Value-based matching
string status = new Pattern<string>
{
(200, _ => "OK"),
(404, _ => "Not Found"),
(500, _ => "Internal Server Error")
}.Match(statusCode).GetOr(_ => "Unknown");
// Predicate-based matching
string range = new Pattern<string>
{
(x => x < 200, (int _) => "Informational"),
(x => x < 300, (int _) => "Success"),
(x => x < 400, (int _) => "Redirection"),
(x => x < 500, (int _) => "Client Error")
}.Match(statusCode).GetOr(_ => "Server Error");
// Type-based matching
object shape = new Circle(5);
string description = new TypePattern<string>
{
(Circle c) => $"Circle with radius {c.Radius}",
(Square s) => $"Square with side {s.Side}"
}.Match(shape).GetOr(_ => "Unknown shape");
// Async pattern matching
string body = await new AsyncPattern<string>
{
(200, _ => httpClient.GetStringAsync("/ok")),
(404, _ => httpClient.GetStringAsync("/not-found"))
}.Match(statusCode).GetOrAsync(_ => Task.FromResult("Fallback"));
// Async type-based matching
string info = await new AsyncTypePattern<string>
{
(Circle c) => ComputeAreaAsync(c),
(Square s) => ComputeAreaAsync(s)
}.Match(shape).GetOrAsync(_ => Task.FromResult("Unknown shape"));
Data<T> & Builder<T>
Fluent immutable updates — create modified copies without mutation. Ideal for domain models as well as ORM entities:
public class User : Data<User>
{
public string Name { get; private set; }
public int Age { get; private set; }
public User(string name, int age)
{
Name = name;
Age = age;
}
}
var alice = new User("Alice", 30);
// Create a modified copy — original is unchanged
var older = alice.With(u => u.Age, 31).Build();
// Chain multiple modifications
var renamed = alice
.With(u => u.Name, "Bob")
.With(u => u.Age, 25)
.Build();
OneOf<T1, …, T5>
Type-safe discriminated unions:
// A value that is either a string or an int
var result = new OneOf<string, int>("hello");
string output = result.Match(
_ => "empty",
s => $"String: {s}",
n => $"Number: {n}"
);
// Access individual states safely via Maybe
Maybe<string> asString = result.First; // NotEmpty
Maybe<int> asInt = result.Second; // IsEmpty
// Deconstruct into Maybe values
var (first, second) = result;
// first: Maybe<string>, second: Maybe<int>
Record<T1, …, T5>
Immutable products with safe deconstruction and mapping:
// Create a record using the Prelude
var person = rec("Alice", 30);
// Deconstruct
var (name, age) = person;
// Map to a new record
var updated = person.Map((n, a) => (n.ToUpper(), a + 1));
// Match to extract a result
string description = person.Match((n, a) => $"{n} is {a} years old");
LINQ query syntax
Maybe and Exc support C# query expressions for composing operations naturally:
// Maybe — compose multiple lookups
Maybe<string> city =
from user in FindUser("alice")
from address in user.Address.AsMaybe()
where address.Country == "US"
select address.City;
// Exc — chain operations that can fail
Exc<decimal, Exception> total =
from order in LoadOrder(orderId)
from discount in ApplyDiscount(order)
select order.Amount - discount;
Piping
Transform values through fluent pipelines:
var result = "hello"
.Do(s => s.ToUpper())
.Do(s => $"{s}!"); // "HELLO!"
Documentation
For full API documentation, visit the Funk documentation site.
License
This project is licensed under the MIT License — see the LICENSE file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- System.Collections.Immutable (>= 10.0.3)
-
.NETStandard 2.1
- System.Collections.Immutable (>= 10.0.3)
-
net10.0
- No dependencies.
-
net8.0
- System.Collections.Immutable (>= 10.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.0 | 86 | 3/4/2026 |
| 2.0.0-beta05 | 79 | 3/3/2026 |
| 2.0.0-beta04 | 733 | 2/27/2024 |
| 2.0.0-beta03 | 248 | 12/30/2023 |
| 2.0.0-beta02 | 1,253 | 2/3/2021 |
| 2.0.0-beta | 444 | 1/9/2021 |
| 2.0.0-alpha | 476 | 1/4/2021 |
| 1.1.3 | 1,313 | 7/18/2020 |
| 1.1.2 | 724 | 7/12/2020 |
| 1.1.1 | 714 | 7/10/2020 |
| 1.1.0 | 697 | 7/2/2020 |
| 1.0.4 | 679 | 6/30/2020 |
| 1.0.3 | 623 | 6/29/2020 |
| 1.0.2 | 647 | 6/28/2020 |
| 1.0.1 | 702 | 6/26/2020 |
| 1.0.0 | 736 | 6/4/2020 |
| 0.0.5 | 725 | 4/13/2020 |
| 0.0.4 | 668 | 4/12/2020 |
| 0.0.3 | 801 | 4/11/2020 |
| 0.0.2 | 693 | 4/7/2020 |