Created
April 9, 2026 09:11
-
-
Save kamoshi/a0045c22952b98283137f573dfdd2c7e to your computer and use it in GitHub Desktop.
C# 15 monoid
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using HelloWorld; | |
| // ── Maybe<T> usage ──────────────────────────────────────────────────────────── | |
| Maybe<string> name = "Alice"; | |
| Maybe<string> empty = new None(); | |
| Console.WriteLine($"Map: {name.Map(s => s.Length).Show()}"); | |
| Console.WriteLine($"Bind: {name.Bind(s => s.Length > 3 | |
| ? (Maybe<string>)s.ToUpper() | |
| : new None()).Show()}"); | |
| Console.WriteLine($"OrElse: {empty.GetOrElse("(nobody)")}"); | |
| // ── Monoid examples ─────────────────────────────────────────────────────────── | |
| Console.WriteLine(); | |
| var a = MaybeMonoid<int, SumMonoid>.Combine("hello".Length, 10); | |
| var b = MaybeMonoid<int, SumMonoid>.Combine(new None(), 10); | |
| var c = MaybeMonoid<int, SumMonoid>.Combine(new None(), new None()); | |
| Console.WriteLine($"Some(5) <> Some(10) = {a.Show()}"); | |
| Console.WriteLine($"None <> Some(10) = {b.Show()}"); | |
| Console.WriteLine($"None <> None = {c.Show()}"); | |
| // Fold a sequence | |
| Console.WriteLine(); | |
| Maybe<int>[] values = [1, new None(), 3, new None(), 5]; | |
| Maybe<int> total = MonoidUtils<Maybe<int>, MaybeMonoid<int, SumMonoid>>.Concat(values); | |
| Console.WriteLine($"Fold [1, None, 3, None, 5] = {total.Show()}"); | |
| namespace HelloWorld | |
| { | |
| // ── None ───────────────────────────────────────────────────────────────── | |
| public record struct None; | |
| // ── Maybe<T> union ─────────────────────────────────────────────────────── | |
| public union Maybe<T>(T, None); | |
| public static class MaybeExtensions | |
| { | |
| public static string Show<T>(this Maybe<T> m) => m switch | |
| { | |
| T v => $"Some({v})", | |
| null => "None", | |
| _ => "None" | |
| }; | |
| public static Maybe<TResult> Map<T, TResult>( | |
| this Maybe<T> m, Func<T, TResult> f) => m switch | |
| { | |
| T v => f(v), | |
| _ => new None() | |
| }; | |
| public static Maybe<TResult> Bind<T, TResult>( | |
| this Maybe<T> m, Func<T, Maybe<TResult>> f) => m switch | |
| { | |
| T v => f(v), | |
| _ => new None() | |
| }; | |
| public static T GetOrElse<T>(this Maybe<T> m, T fallback) => m switch | |
| { | |
| T v => v, | |
| _ => fallback | |
| }; | |
| } | |
| // ── Monoid typeclass — interface so static abstract members work ─────────── | |
| // (static abstract is a C# 11 feature, only supported on interfaces) | |
| public interface IMonoid<T> | |
| { | |
| static abstract T Identity { get; } | |
| static abstract T Combine(T a, T b); | |
| } | |
| // ── Abstract class for derived utility — uses the interface as a constraint ─ | |
| public abstract class MonoidUtils<T, TMonoid> where TMonoid : IMonoid<T> | |
| { | |
| public static T Concat(IEnumerable<T> items) => | |
| items.Aggregate(TMonoid.Identity, TMonoid.Combine); | |
| public static T Stimes(int n, T a) | |
| { | |
| var result = TMonoid.Identity; | |
| for (var i = 0; i < n; i++) result = TMonoid.Combine(result, a); | |
| return result; | |
| } | |
| } | |
| // ── Concrete monoid: int addition ───────────────────────────────────────── | |
| public class SumMonoid : IMonoid<int> | |
| { | |
| public static int Identity => 0; | |
| public static int Combine(int a, int b) => a + b; | |
| } | |
| // ── Lifted Maybe monoid ─────────────────────────────────────────────────── | |
| // Laws hold when TMonoid satisfies monoid laws for T: | |
| // Identity = None | |
| // Combine(None, y) = y | |
| // Combine(x, None) = x | |
| // Combine(Some a, Some b) = Some(TMonoid.Combine(a, b)) | |
| public class MaybeMonoid<T, TMonoid> : IMonoid<Maybe<T>> | |
| where TMonoid : IMonoid<T> | |
| { | |
| public static Maybe<T> Identity => new None(); | |
| public static Maybe<T> Combine(Maybe<T> a, Maybe<T> b) => | |
| (a, b) switch | |
| { | |
| (T va, T vb) => TMonoid.Combine(va, vb), | |
| (T va, _) => va, | |
| (_, T vb) => vb, | |
| _ => new None() | |
| }; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment