Monads in C# (Part 2): Result

https://news.ycombinator.com/rss Hits: 8
Summary

Previously in the series: List is a monad (part 1) Note: rewritten 2025-12-28. In Part 1 (List), we contrasted Map (Select) vs Bind (SelectMany) on List<T>, then built Maybe<T>. If you read Part 1, you already know the shape: Bind/SelectMany chains steps, and Maybe decides whether the next step runs. The Result monad lets you compose computations that can fail. You return Ok(value) or Fail(error), then compose with Bind to propagate the first failure (later steps don’t run; the failure just flows through). It’s useful for making expected failure explicit and composable. Result<TSuccess, TError> has the same two-case shape as Maybe<T>, except the non-success case carries a reason (TError). Maybe models optionality; Result models failure with an explicit reason. Prefer to compose with Bind until you need to branch, translate, or produce an output (often at a boundary/edge), then Match. If you need to accumulate many errors (e.g., form validation) or you have several first-class outcomes, Result may not be the best fit. If you’re coming from FP, this is closest to an Either/Result-style type. TL;DR What it looks like (implementations for User left out for brevity): public record Error(string Code, string Message); // Assume `repo` is in scope. // Creating results: Result<User, Error> okUser = Result<User, Error>.Ok(new User(/* ... */)); Result<User, Error> failed = Result<User, Error>.Fail(new Error("NotFound", "User not found")); // Assume: // Result<int, Error> ParseId(string inputIdFromRequest) // Result<User, Error> DeactivateDecision(User user) // Returning a Result from a function: Result<User, Error> FindUserOrFail(IUserRepo repo, int id) { User? user = repo.Find(id); if (user is null) { return Result<User, Error>.Fail(new Error("NotFound", $"User {id} not found")); } return Result<User, Error>.Ok(user); } Result<User, Error> result = // Result<User, Error> ParseId(inputIdFromRequest) // Result<int, Error>, first in chain, always runs .Bind(id => FindUserOrFail(...

First seen: 2026-01-05 08:22

Last seen: 2026-01-05 15:23