{"id":31803736,"url":"https://github.com/bogoware/monads","last_synced_at":"2025-10-11T01:22:16.057Z","repository":{"id":176585125,"uuid":"629113916","full_name":"bogoware/Monads","owner":"bogoware","description":"C# Maybe and Result monads","archived":false,"fork":false,"pushed_at":"2025-07-11T07:46:35.000Z","size":319,"stargazers_count":6,"open_issues_count":16,"forks_count":1,"subscribers_count":1,"default_branch":"rel/prod","last_synced_at":"2025-10-10T03:43:39.211Z","etag":null,"topics":["csharp","dotnet","functional-programming","maybe","monads","nuget","result"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bogoware.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-04-17T16:41:25.000Z","updated_at":"2025-10-02T09:54:27.000Z","dependencies_parsed_at":"2023-12-12T16:51:35.020Z","dependency_job_id":"1c853055-51ee-4997-a598-f6a0d6dbccdb","html_url":"https://github.com/bogoware/Monads","commit_stats":null,"previous_names":["bogoware/monads"],"tags_count":50,"template":false,"template_full_name":null,"purl":"pkg:github/bogoware/Monads","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bogoware%2FMonads","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bogoware%2FMonads/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bogoware%2FMonads/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bogoware%2FMonads/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bogoware","download_url":"https://codeload.github.com/bogoware/Monads/tar.gz/refs/heads/rel/prod","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bogoware%2FMonads/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279005926,"owners_count":26083984,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","functional-programming","maybe","monads","nuget","result"],"created_at":"2025-10-11T01:22:14.363Z","updated_at":"2025-10-11T01:22:16.047Z","avatar_url":"https://github.com/bogoware.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bogoware Monads\n\n![Nuget](https://img.shields.io/nuget/dt/Bogoware.Monads?logo=nuget\u0026style=plastic) ![Nuget](https://img.shields.io/nuget/v/Bogoware.Monads?style=plastic)\n\n_Yet another functional library for C#_\n\n## Table of Contents\n\n- [Getting Started](#getting-started)\n- [Introduction to Monads](#introduction-to-monads)\n- [Library Overview](#library-overview)\n- [Result\u0026lt;T\u0026gt; Monad](#resultt-monad)\n  - [Design Goals](#design-goals-for-resultt)\n  - [Complete API Reference](#complete-resultt-api-reference)\n  - [Static Helper Methods](#result-static-helper-methods)\n- [Maybe\u0026lt;T\u0026gt; Monad](#maybet-monad)\n  - [Design Goals](#design-goals-for-maybet)\n  - [Complete API Reference](#complete-maybet-api-reference)\n  - [Converting to Result\u0026lt;T\u0026gt;](#converting-maybet-to-resultt)\n- [Working with Collections](#working-with-collections)\n  - [IEnumerable\u0026lt;Maybe\u0026lt;T\u0026gt;\u0026gt; Extensions](#manipulating-ienumerablemaybet)\n  - [IEnumerable\u0026lt;Result\u0026lt;T\u0026gt;\u0026gt; Extensions](#manipulating-ienumerableresultt)\n- [Error Types and Management](#error-types-and-management)\n  - [Built-in Error Types](#built-in-error-types)\n  - [Error Hierarchy Best Practices](#error-hierarchy-best-practices)\n- [Async Programming with Monads](#async-programming-with-monads)\n- [Advanced Patterns and Best Practices](#advanced-patterns-and-best-practices)\n\n## Getting Started\n\nInstall from NuGet and start using the monads in your C# projects:\n\n```shell\ndotnet add package Bogoware.Monads\n```\n\n### Your First Maybe Example\n\nLet's start with a simple example using `Maybe\u003cT\u003e` to handle optional values safely:\n\n```csharp\nusing Bogoware.Monads;\n\n// Traditional approach with null checks\npublic string GetFullName(string firstName, string? lastName)\n{\n    if (lastName != null)\n        return $\"{firstName} {lastName}\";\n    return firstName;\n}\n\n// Using Maybe\u003cT\u003e for safer optional handling\npublic record Person(string FirstName, Maybe\u003cstring\u003e LastName);\n\npublic string GetFullNameSafe(Person person)\n{\n    return person.LastName\n        .Map(last =\u003e $\"{person.FirstName} {last}\")\n        .GetValue(person.FirstName);\n}\n\n// Usage\nvar personWithLastName = new Person(\"John\", Maybe.Some(\"Doe\"));\nvar personWithoutLastName = new Person(\"Jane\", Maybe.None\u003cstring\u003e());\n\nConsole.WriteLine(GetFullNameSafe(personWithLastName));   // \"John Doe\"\nConsole.WriteLine(GetFullNameSafe(personWithoutLastName)); // \"Jane\"\n```\n\n### Your First Result Example\n\nNow let's see how `Result\u003cT\u003e` handles operations that can fail:\n\n```csharp\nusing Bogoware.Monads;\n\n// Traditional approach with exceptions\npublic User CreateUserUnsafe(string email, string password)\n{\n    if (string.IsNullOrEmpty(email) || !email.Contains(\"@\"))\n        throw new ArgumentException(\"Invalid email\");\n    \n    if (password.Length \u003c 8)\n        throw new ArgumentException(\"Password too short\");\n    \n    return new User(email, password);\n}\n\n// Using Result\u003cT\u003e for explicit error handling\npublic Result\u003cUser\u003e CreateUserSafe(string email, string password)\n{\n    return ValidateEmail(email)\n        .Bind(() =\u003e ValidatePassword(password))\n        .Map(() =\u003e new User(email, password));\n}\n\npublic Result\u003cUnit\u003e ValidateEmail(string email)\n{\n    if (string.IsNullOrEmpty(email) || !email.Contains(\"@\"))\n        return Result.Failure\u003cUnit\u003e(\"Invalid email address\");\n    \n    return Result.Unit;\n}\n\npublic Result\u003cUnit\u003e ValidatePassword(string password)\n{\n    if (password.Length \u003c 8)\n        return Result.Failure\u003cUnit\u003e(\"Password must be at least 8 characters\");\n    \n    return Result.Unit;\n}\n\n// Usage\nvar successResult = CreateUserSafe(\"john@example.com\", \"secure123\");\nvar failureResult = CreateUserSafe(\"invalid-email\", \"short\");\n\nsuccessResult.Match(\n    user =\u003e $\"User created: {user.Email}\",\n    error =\u003e $\"Error: {error.Message}\"\n);\n```\n\n### Combining Maybe and Result\n\nHere's a practical example that combines both monads:\n\n```csharp\nusing Bogoware.Monads;\n\npublic record Book(string Title, Maybe\u003cPerson\u003e Author);\n\npublic class BookService\n{\n    private readonly List\u003cBook\u003e _books = new();\n\n    public Maybe\u003cBook\u003e FindBookByTitle(string title)\n    {\n        var book = _books.FirstOrDefault(b =\u003e b.Title.Equals(title, StringComparison.OrdinalIgnoreCase));\n        return book != null ? Maybe.Some(book) : Maybe.None\u003cBook\u003e();\n    }\n\n    public Result\u003cstring\u003e GetBookDescription(string title)\n    {\n        return FindBookByTitle(title)\n            .MapToResult(() =\u003e new LogicError($\"Book '{title}' not found\"))\n            .Map(book =\u003e FormatBookDescription(book));\n    }\n\n    private string FormatBookDescription(Book book)\n    {\n        return book.Author\n            .Map(author =\u003e $\"'{book.Title}' by {author.GetFullName()}\")\n            .GetValue(() =\u003e $\"'{book.Title}' (Author unknown)\");\n    }\n}\n\n// Usage\nvar bookService = new BookService();\nvar result = bookService.GetBookDescription(\"Clean Code\");\n\nresult.Match(\n    description =\u003e Console.WriteLine(description),\n    error =\u003e Console.WriteLine($\"Error: {error.Message}\")\n);\n```\n\n### Advanced Pipeline Example\n\nFor more complex scenarios, you can chain multiple operations:\n\n```csharp\npublic Result\u003cUser\u003e ProcessUserRegistration(string email, string password, string confirmPassword)\n{\n    return ValidateEmail(email)\n        .Bind(() =\u003e ValidatePassword(password))\n        .Bind(() =\u003e ValidatePasswordMatch(password, confirmPassword))\n        .Bind(() =\u003e CheckEmailNotExists(email))\n        .Map(() =\u003e new User(email, password))\n        .Bind(SaveUser)\n        .IfSuccess(user =\u003e SendWelcomeEmail(user))\n        .Match(\n            user =\u003e Result.Success(user),\n            error =\u003e LogError(error)\n        );\n}\n```\n\nThis approach ensures that:\n- Operations only proceed if the previous step succeeded\n- Errors are captured and handled explicitly\n- The code is more readable and maintainable\n- No exceptions are thrown for expected failure cases\n\n\n## Introduction to Monads\n\nMonads are powerful tools for modeling operations in a functional way, making them a cornerstone \nof functional programming. While we won't delve into a detailed explanation of monads and their inner \nworkings, there are numerous resources available online that approach the topic \nfrom different perspectives.\n\nFor the purpose of this introduction, we can consider monads as an abstraction of _safe container_ that encapsulates\nthe result of an operation. They provide methods that enable manipulation of the result in a safe manner,\nensuring that the execution flow follows the \"happy\" path in case of success and the \"unhappy\" path in case of failure. This model is also known as _railway-oriented programming_.\n\nBy employing monads, code can be protected from further processing in case of errors or missing data. \nAdopting a functional approach offers benefits such as increased readability, improved reasoning capabilities,\nand more robust and error-resistant code.\n\n## Library Overview\n\nThis library provides two well-known monads: `Result` and `Maybe` monads (also referred to as `Either`, \n`Optional`, `Option` in other contexts):\n\n\u003e The `Result\u003cT\u003e` monad is used to model operations that can fail.\n\n\u003eThe `Maybe\u003cT\u003e` monad is used to model operations that can optionally return a value.\n\nAdditionally, the library provides the `Error` abstract class, which complements the `Result\u003cT\u003e` monad and\noffers an ergonomic approach to error management at an application-wide scale.\n\n## Result\u0026lt;T\u0026gt; Monad\n\n## Design Goals for `Result\u003cT\u003e`\n\nThe `Result\u003cT\u003e` monad is designed for modeling operations that can either fail or return a value.\nIt is a generic type, with `T` representing the type of the value returned by the successful operation.\n\n`Result\u003cT\u003e` provides a set of methods that facilitate chaining operations in a functional way:\n* `Map`: Allows transformation of the value returned by the operation, representing the \"happy\" flow.\n  * `Map` to void functor will map to `Result\u003cUnit\u003e`\n  * `MapToUnit()` is just a shortcut for `Map(_ =\u003e { })`\n* `MapError`: Allows transformation of the error returned by the operation, representing the \"unhappy\" flow.\n* `Bind`: Enables chaining of operations providing a fluent syntax that allows\n to capture the values on the \"happy\" path and use them in subsequent steps.\n* `Match`: Facilitates handling of the operation's result by providing separate paths for the \"happy\" and \"unhappy\" flows.\n* `RecoverWith`: Provides a way to recover from an error by returning a `Result\u003cT\u003e`\n* `Ensure`: Allows asserting a condition on the value returned by the operation.\n* `IfSuccess`: Executes if the operation succeeds. It is typically used to generate side effects.\n* `IfFailure`: Executes if the operation fails. It is typically used to generate side effects.\n\nThere are also some unsafe methods intended to support developers who are less familiar with the functional approach\nand may need to resort to a procedural style to achieve their goals.\nThese methods should be used sparingly, as they deviate from the functional paradigm and make the code less\nrobust, potentially leading to unexpected exceptions:\n\n* `ThrowIfFailure()`: Throws an exception if the operation fails. It is typically used to terminate the execution of the pipeline\n  discarding the result of the operation.\n* `Value` or `GetValueOrThrow()`: Extracts the value from the `Result\u003cT\u003e` monad.\n* `Error` or `GetErrorOrThrow()`: Extracts the error from the `Result\u003cT\u003e` monad. \n\nBy adhering to the `Result\u003cT\u003e` monad, code can be modeled in a more readable and reasoned manner.\nIt also contributes to writing more robust code with reduced error-proneness.\n\n### Complete `Result\u003cT\u003e` API Reference\n\n#### Core Methods\n\n##### Map\nTransforms the value if the result is successful:\n\n```csharp\nvar result = Result.Success(42);\nvar doubled = result.Map(x =\u003e x * 2); // Result\u003cint\u003e with value 84\n\n// Map to different type\nvar text = result.Map(x =\u003e $\"Value: {x}\"); // Result\u003cstring\u003e\n\n// Map to Unit (void operations)\nvar unit = result.Map(x =\u003e Console.WriteLine(x)); // Result\u003cUnit\u003e\nvar unit2 = result.MapToUnit(); // Shortcut for discarding the value\n```\n\n##### Bind\nChains operations that return `Result\u003cT\u003e`:\n\n```csharp\npublic Result\u003cint\u003e ParseNumber(string text) =\u003e \n    int.TryParse(text, out var num) ? Result.Success(num) : Result.Failure\u003cint\u003e(\"Invalid number\");\n\npublic Result\u003cstring\u003e FormatNumber(int number) =\u003e\n    number \u003e= 0 ? Result.Success($\"#{number:D4}\") : Result.Failure\u003cstring\u003e(\"Negative numbers not allowed\");\n\n// Chain operations\nvar result = ParseNumber(\"42\")\n    .Bind(FormatNumber); // Result\u003cstring\u003e with \"#0042\"\n```\n\n##### Match\nHandles both success and failure cases:\n\n```csharp\nvar result = CreateUser(\"john@example.com\");\nvar message = result.Match(\n    user =\u003e $\"Created user: {user.Email}\",\n    error =\u003e $\"Failed: {error.Message}\"\n);\n```\n\n##### MapError\nTransforms error types:\n\n```csharp\nvar result = Result.Failure\u003cstring\u003e(\"Database connection failed\");\nvar mappedError = result.MapError(err =\u003e new CustomError($\"Service Error: {err.Message}\"));\n```\n\n##### RecoverWith\nProvides fallback values on failure:\n\n```csharp\nvar result = Result.Failure\u003cstring\u003e(\"Network error\");\nvar recovered = result.RecoverWith(\"Default value\"); // Result\u003cstring\u003e with \"Default value\"\n\n// Using function for lazy evaluation\nvar recovered2 = result.RecoverWith(() =\u003e GetFallbackValue());\n```\n\n##### Ensure\nValidates conditions and fails if not met:\n\n```csharp\nvar result = Result.Success(\"john@example.com\")\n    .Ensure(email =\u003e email.Contains(\"@\"), new ValidationError(\"Invalid email format\"));\n```\n\n##### Side Effects: IfSuccess and IfFailure\nExecute actions without changing the result:\n\n```csharp\nvar result = CreateUser(\"john@example.com\")\n    .IfSuccess(user =\u003e Logger.Info($\"User created: {user.Id}\"))\n    .IfFailure(error =\u003e Logger.Error($\"Creation failed: {error.Message}\"));\n```\n\n##### Satisfy\nCheck conditions on the result value (class types only):\n\n```csharp\nvar result = Result.Success(\"john@example.com\");\nvar isValidEmail = result.Satisfy(email =\u003e email.Contains(\"@\")); // Returns true\n\nvar failedResult = Result.Failure\u003cstring\u003e(\"Error\");\nvar check = failedResult.Satisfy(email =\u003e email.Contains(\"@\")); // Returns false\n```\n\n#### Unsafe Methods (Use Sparingly)\n\n```csharp\nvar result = Result.Success(42);\n\n// Extract value or throw exception\nvar value = result.GetValueOrThrow(); // Returns 42\nvar value2 = result.Value; // Same as above\n\n// Extract error or throw exception  \nvar failedResult = Result.Failure\u003cint\u003e(\"Error message\");\nvar error = failedResult.GetErrorOrThrow(); // Returns Error\nvar error2 = failedResult.Error; // Same as above\n\n// Throw if result is failure\nresult.ThrowIfFailure(); // No exception thrown for success\nfailedResult.ThrowIfFailure(); // Throws ResultFailedException\n```\n\n### `Result` Static Helper Methods\n\nThe `Result` class provides a comprehensive set of helper methods that facilitate the creation of `Result\u003cT\u003e` instances and\nmake the code more readable and functional.\n\n#### Factory Methods\n\n```csharp\n// Create successful results\nvar success = Result.Success(42); // Result\u003cint\u003e\nvar unitSuccess = Result.Unit; // Result\u003cUnit\u003e for void operations\n\n// Create failed results\nvar failure1 = Result.Failure\u003cint\u003e(\"Something went wrong\"); // Uses LogicError\nvar failure2 = Result.Failure\u003cint\u003e(new CustomError(\"Custom error\")); // Uses custom error\n\n// Create from values (smart constructor)\nvar fromValue = Result.From(42); // Result\u003cint\u003e - Success\nvar fromError = Result.From\u003cint\u003e(new LogicError(\"Error\")); // Result\u003cint\u003e - Failure\n```\n\n#### Safe Execution\n\n```csharp\n// Execute actions safely (catches exceptions as RuntimeError)\nvar result1 = Result.Execute(() =\u003e RiskyOperation()); // Result\u003cUnit\u003e\nvar result2 = Result.Execute(() =\u003e ComputeValue()); // Result\u003cT\u003e\n\n// Async execution\nvar asyncResult = await Result.Execute(async () =\u003e await RiskyAsyncOperation());\n```\n\n#### Conditional Results\n\n```csharp\n// Create results based on conditions\nvar result1 = Result.Ensure(userAge \u003e= 18, () =\u003e new ValidationError(\"Must be 18+\"));\nvar result2 = Result.Ensure(() =\u003e IsValidOperation(), () =\u003e new LogicError(\"Invalid state\"));\n\n// Async conditions\nvar asyncResult = await Result.Ensure(async () =\u003e await ValidateAsync(), \n                                     () =\u003e new ValidationError(\"Validation failed\"));\n```\n\n#### Functional Composition\n\n```csharp\n// Start chains with Result.Bind for consistent syntax\nvar result = Result.Bind(() =\u003e GetInitialValue())\n    .Bind(ValidateValue)\n    .Bind(ProcessValue)\n    .Map(FormatOutput);\n\n// Instead of:\nvar result2 = GetInitialValue() // Direct call breaks the chain style\n    .Bind(ValidateValue)\n    .Bind(ProcessValue)\n    .Map(FormatOutput);\n```\n\n#### Complete Example\n\nFor example, instead of writing:\n```csharp\n/// Publishes the project\npublic Result\u003cUnit\u003e Publish() {\n    if (PublishingStatus == PublishingStatus.Published)\n        return new InvalidOperationError(\"Already published\");\n    \n    return ValidateCostComponents() // Note the explicit invocation of the method\n        .Bind(ValidateTimingComponents)\n        // ... more binding to validation methods\n        .IfSuccess(() =\u003e PublishingStatus = PublishingStatus.Published);\n}\n```\n\nYou can write:\n```csharp\n/// Publishes the project\npublic Result\u003cUnit\u003e Publish() =\u003e Result\n    .Ensure(PublishingStatus != PublishingStatus.Published, () =\u003e new InvalidOperationError(\"Already published\"))\n    .Bind(ValidateCostComponents)\n    .Bind(ValidateTimingComponents)\n    // ... more binding to validation methods\n    .IfSuccess(() =\u003e PublishingStatus = PublishingStatus.Published);\n```\n\n## Working with Collections\n\n### Manipulating `IEnumerable\u003cMaybe\u003cT\u003e\u003e`\n\nThe library provides a comprehensive set of extension methods for working with sequences of `Maybe\u003cT\u003e` instances:\n\n#### Core Collection Methods\n\n```csharp\nvar books = new List\u003cMaybe\u003cBook\u003e\u003e { \n    Maybe.Some(new Book(\"1984\", \"Orwell\")), \n    Maybe.None\u003cBook\u003e(), \n    Maybe.Some(new Book(\"Brave New World\", \"Huxley\")) \n};\n\n// SelectValues: Extract all Some values, discard None values\nvar validBooks = books.SelectValues(); // IEnumerable\u003cBook\u003e with 2 books\n\n// MapEach: Transform each Maybe, preserving None values\nvar upperTitles = books.MapEach(book =\u003e book.Title.ToUpper()); \n// IEnumerable\u003cMaybe\u003cstring\u003e\u003e with 2 Some values and 1 None\n\n// BindEach: Chain operations on each Maybe, preserving None values  \nvar authors = books.BindEach(book =\u003e book.Author); \n// IEnumerable\u003cMaybe\u003cAuthor\u003e\u003e\n\n// MatchEach: Transform all Maybes to a common type\nvar descriptions = books.MatchEach(\n    book =\u003e $\"Book: {book.Title}\",\n    \"No book\"\n); // IEnumerable\u003cstring\u003e\n```\n\n#### Filtering and Predicates\n\n```csharp\nvar numbers = new[] { \n    Maybe.Some(1), Maybe.None\u003cint\u003e(), Maybe.Some(2), Maybe.Some(3) \n};\n\n// Where: Filter Some values based on predicate, None values are discarded\nvar evenNumbers = numbers.Where(n =\u003e n % 2 == 0); // Maybe\u003cint\u003e[] with Some(2)\n\n// WhereNot: Filter Some values with negated predicate\nvar oddNumbers = numbers.WhereNot(n =\u003e n % 2 == 0); // Maybe\u003cint\u003e[] with Some(1), Some(3)\n\n// Predicate methods\nvar allHaveValues = numbers.AllSome(); // false (contains None)\nvar allEmpty = numbers.AllNone(); // false (contains Some values)\n```\n\n### Manipulating `IEnumerable\u003cResult\u003cT\u003e\u003e`\n\nThe library provides powerful extension methods for working with sequences of `Result\u003cT\u003e` instances:\n\n#### Core Collection Methods\n\n```csharp\nvar operations = new[] {\n    Result.Success(\"file1.txt\"),\n    Result.Failure\u003cstring\u003e(\"Access denied\"),\n    Result.Success(\"file3.txt\")\n};\n\n// SelectValues: Extract all successful values, discard failures\nvar successfulFiles = operations.SelectValues(); // IEnumerable\u003cstring\u003e with 2 files\n\n// MapEach: Transform each Result, preserving failures\nvar processedFiles = operations.MapEach(file =\u003e file.ToUpper());\n// IEnumerable\u003cResult\u003cstring\u003e\u003e with 2 successes and 1 failure\n\n// BindEach: Chain operations on each Result, preserving failures\nvar fileContents = operations.BindEach(ReadFileContent);\n// IEnumerable\u003cResult\u003cstring\u003e\u003e\n\n// MatchEach: Transform all Results to a common type\nvar messages = operations.MatchEach(\n    file =\u003e $\"Processed: {file}\",\n    error =\u003e $\"Error: {error.Message}\"\n); // IEnumerable\u003cstring\u003e\n```\n\n#### Predicate Methods\n\n```csharp\nvar results = new[] {\n    Result.Success(1),\n    Result.Failure\u003cint\u003e(\"Error 1\"), \n    Result.Success(2),\n    Result.Failure\u003cint\u003e(\"Error 2\")\n};\n\n// Check if all results are successful\nvar allSucceeded = results.AllSuccess(); // false\n\n// Check if all results failed\nvar allFailed = results.AllFailure(); // false\n\n// Check if any result succeeded\nvar anySucceeded = results.AnySuccess(); // true\n\n// Check if any result failed  \nvar anyFailed = results.AnyFailure(); // true\n```\n\n#### Aggregation\n\n```csharp\nvar userOperations = new[] {\n    CreateUser(\"john@example.com\"),\n    CreateUser(\"jane@example.com\"),\n    CreateUser(\"invalid-email\") // This will fail\n};\n\n// AggregateResults: Combine all results into a single Result\nvar aggregated = userOperations.AggregateResults();\n// Result\u003cIEnumerable\u003cUser\u003e\u003e - fails with AggregateError containing all errors\n\n// If all operations succeed:\nvar allSuccess = new[] {\n    Result.Success(1),\n    Result.Success(2),\n    Result.Success(3)\n};\nvar combined = allSuccess.AggregateResults(); // Result\u003cIEnumerable\u003cint\u003e\u003e with [1, 2, 3]\n```\n\n## Error Types and Management\n\n### Design Goals for `Error`\n\nThe `Error` class is used for modeling errors and works in conjunction with the `Result\u003cT\u003e` monad.\n\nThere are two types of errors:\n* `LogicError`: These errors are caused by application logic and should be programmatically handled.\nExamples include `InvalidEmailError`, `InvalidPasswordError`, `InvalidUsernameError`, etc.\n* `RuntimeError`: These errors are caused by external sources and are unrelated to domain logic.\nExamples include `DatabaseError`, `NetworkError`, `FileSystemError`, etc.\n\nDistinguishing between `LogicError`s and `RuntimeError`s is important, as they require different handling approaches:\n* `LogicError`s should be programmatically handled and can be safely reported to the user in case of a malformed request.\n* `RuntimeError`s should be handled by the infrastructure and should not be reported to the user.\n\nFor example, in a typical ASP.NET Core application, `LogicErrors` can be handled by returning a `BadRequest`\nresponse to the client, while `RuntimeErrors` can be handled by returning an `InternalServerError` response.\n\n### Built-in Error Types\n\n#### LogicError\nBase class for application logic errors:\n\n```csharp\n// Simple logic error\nvar error = new LogicError(\"Invalid input provided\");\nvar result = Result.Failure\u003cstring\u003e(error);\n```\n\n#### RuntimeError  \nWraps exceptions that occur during execution:\n\n```csharp\ntry \n{\n    // Some risky operation\n    var data = await riskOperation();\n    return Result.Success(data);\n}\ncatch (Exception ex)\n{\n    return Result.Failure\u003cstring\u003e(new RuntimeError(ex));\n}\n\n// Or use Result.Execute to handle this automatically:\nvar result = Result.Execute(() =\u003e riskyOperation());\n```\n\n#### AggregateError\nContains multiple errors, typically from `AggregateResults`:\n\n```csharp\nvar operations = new[] {\n    Result.Failure\u003cint\u003e(\"Error 1\"),\n    Result.Failure\u003cint\u003e(\"Error 2\"), \n    Result.Success(42)\n};\n\nvar aggregated = operations.AggregateResults();\n// Result fails with AggregateError containing \"Error 1\" and \"Error 2\"\n\nif (aggregated.IsFailure \u0026\u0026 aggregated.Error is AggregateError aggError)\n{\n    foreach (var error in aggError.Errors)\n    {\n        Console.WriteLine($\"Individual error: {error.Message}\");\n    }\n}\n```\n\n#### MaybeNoneError\nDefault error when converting `Maybe.None` to `Result`:\n\n```csharp\nvar maybe = Maybe.None\u003cstring\u003e();\nvar result = maybe.MapToResult(); // Result\u003cstring\u003e fails with MaybeNoneError\n\n// Custom error instead:\nvar result2 = maybe.MapToResult(() =\u003e new LogicError(\"Value was not found\"));\n```\n\n### Error Hierarchy Best Practices\nEach application should model its own logic errors by deriving from a root class that represents the base class\nfor all logic errors. The root class should derive from the `LogicError` class.\n\nFor different kinds of logic errors that can occur, the application should derive specific classes,\neach modeling a particular logic error and providing the necessary properties to describe the error.\n\nIn the following example, we model two logic errors: `NotFoundError` and `InvalidOperationError`:\n\n```csharp\n\npublic abstract class ApplicationError: LogicError\n{\n\t\n\tpublic int ErrorCode { get; }\n\n\tprotected ApplicationError(string message, int errorCode)\n\t\t: base(message)\n\t{\n\t\tErrorCode = errorCode;\n\t}\n}\n\npublic class NotFoundError : ApplicationError\n{\n\t\n\tpublic string ResourceName { get; }\n\tpublic string ResourceId { get; }\n\tpublic NotFoundError(string message, int errorCode, string resourceName, string resourceId)\n\t\t: base(message, errorCode)\n\t{\n\t\tResourceName = resourceName;\n\t\tResourceId = resourceId;\n\t}\n}\n\npublic class InvalidOperationError : ApplicationError\n{\n\t\n\tpublic string OperationName { get; }\n\tpublic string Reason { get; }\n\tpublic InvalidOperationError(string message, int errorCode, string operationName, string reason)\n\t\t: base(message, errorCode)\n\t{\n\t\tOperationName = operationName;\n\t\tReason = reason;\n\t}\n}\n```\n\nAs demonstrated in the project [FluentValidationSample](./sample/FluentValidationSample) the `FluentValidation` library \ncan be used to model validation errors.\n\nIn contrast to `LogicError`s, `RuntimeError`s are generated by the `Result.Execute()` methods to encapsulate exceptions \nthrown by the application.\n\n## Async Programming with Monads\n\nBoth `Result\u003cT\u003e` and `Maybe\u003cT\u003e` provide full async support for all major operations:\n\n### Async Result Operations\n\n```csharp\n// Async Map\nvar result = await Result.Success(\"file.txt\")\n    .Map(async fileName =\u003e await File.ReadAllTextAsync(fileName));\n\n// Async Bind\npublic async Task\u003cResult\u003cUser\u003e\u003e GetUserAsync(int id) =\u003e \n    await ValidateId(id)\n        .Bind(async validId =\u003e await database.GetUserAsync(validId));\n\n// Async side effects\nvar result = await CreateUserAsync(email)\n    .IfSuccess(async user =\u003e await SendWelcomeEmailAsync(user))\n    .IfFailure(async error =\u003e await LogErrorAsync(error));\n\n// Async Match\nvar message = await result.Match(\n    async user =\u003e await FormatUserDetailsAsync(user),\n    async error =\u003e await FormatErrorMessageAsync(error)\n);\n\n// Async Ensure\nvar validated = await result\n    .Ensure(async user =\u003e await IsUserActiveAsync(user), \n            new LogicError(\"User is not active\"));\n```\n\n### Async Maybe Operations\n\n```csharp\n// Async Map\nvar maybe = await Maybe.Some(\"data\")\n    .Map(async data =\u003e await ProcessDataAsync(data));\n\n// Async Bind  \nvar result = await Maybe.Some(userId)\n    .Bind(async id =\u003e await FindUserAsync(id));\n\n// Async side effects\nawait maybe\n    .IfSome(async value =\u003e await ProcessValueAsync(value))\n    .IfNone(async () =\u003e await HandleMissingValueAsync());\n\n// Async WithDefault\nvar withDefault = await Maybe.None\u003cstring\u003e()\n    .WithDefault(async () =\u003e await GetDefaultValueAsync());\n```\n\n### Task\u003cResult\u003cT\u003e\u003e and Task\u003cMaybe\u003cT\u003e\u003e Extensions\n\nAll methods work seamlessly with `Task\u003cResult\u003cT\u003e\u003e` and `Task\u003cMaybe\u003cT\u003e\u003e`:\n\n```csharp\n// Chain async operations\npublic async Task\u003cResult\u003cProcessedData\u003e\u003e ProcessUserDataAsync(int userId)\n{\n    return await GetUserAsync(userId)          // Task\u003cResult\u003cUser\u003e\u003e\n        .Bind(async user =\u003e await GetUserDataAsync(user.Id))  // Chain with async\n        .Map(async data =\u003e await ProcessDataAsync(data))      // Async transform\n        .IfSuccess(async result =\u003e await CacheResultAsync(result)); // Async side effect\n}\n\n// Using Result.Execute for async operations\nvar result = await Result.Execute(async () =\u003e await RiskyAsyncOperation());\n```\n\n## Advanced Patterns and Best Practices\n\n### Railway-Oriented Programming\n\nChain operations to create robust data processing pipelines:\n\n```csharp\npublic async Task\u003cResult\u003cProcessedOrder\u003e\u003e ProcessOrderAsync(OrderRequest request)\n{\n    return await ValidateOrderRequest(request)\n        .Bind(ValidateCustomer)\n        .Bind(ValidateInventory)\n        .Bind(CalculatePricing)\n        .Bind(async order =\u003e await SaveOrderAsync(order))\n        .Bind(async order =\u003e await ProcessPaymentAsync(order))\n        .IfSuccess(async order =\u003e await SendConfirmationAsync(order))\n        .Match(\n            order =\u003e Result.Success(order),\n            async error =\u003e await HandleOrderErrorAsync(error)\n        );\n}\n```\n\n### Combining Maybe and Result\n\nConvert between `Maybe\u003cT\u003e` and `Result\u003cT\u003e` as needed:\n\n```csharp\npublic Result\u003cUserProfile\u003e GetUserProfile(int userId)\n{\n    return FindUser(userId)                    // Maybe\u003cUser\u003e\n        .MapToResult(() =\u003e new NotFoundError(\"User not found\"))  // Result\u003cUser\u003e\n        .Bind(user =\u003e LoadUserProfile(user))   // Result\u003cUserProfile\u003e\n        .Map(profile =\u003e EnrichProfile(profile)); // Result\u003cUserProfile\u003e\n}\n```\n\n### Error Recovery Patterns\n\n```csharp\n// Fallback to default values\nvar config = LoadConfigFromFile()\n    .RecoverWith(() =\u003e LoadConfigFromEnvironment())\n    .RecoverWith(GetDefaultConfig());\n\n// Retry with different strategies  \nvar result = await TryPrimaryService()\n    .RecoverWith(async () =\u003e await TrySecondaryService())\n    .RecoverWith(async () =\u003e await TryFallbackService());\n```\n\n### Validation Patterns\n\n```csharp\npublic Result\u003cValidatedUser\u003e ValidateUser(UserInput input)\n{\n    return ValidateEmail(input.Email)\n        .Bind(() =\u003e ValidatePassword(input.Password))\n        .Bind(() =\u003e ValidateAge(input.Age))\n        .Map(() =\u003e new ValidatedUser(input));\n}\n\n// Or using Result.Ensure for inline validation\npublic Result\u003cUser\u003e CreateUser(string email, string password)\n{\n    return Result.Success(new User(email, password))\n        .Ensure(user =\u003e user.Email.Contains(\"@\"), new ValidationError(\"Invalid email\"))\n        .Ensure(user =\u003e user.Password.Length \u003e= 8, new ValidationError(\"Password too short\"));\n}\n```\n\n## Design Goals for `Maybe\u003cT\u003e`\n\nBefore discussing what can be achieved with the `Maybe\u003cT\u003e` monad, let's clarify that it is not intended as a \nreplacement for `Nullable\u003cT\u003e`.\nThis is mainly due to fundamental libraries, such as Entity Framework, relying on `Nullable\u003cT\u003e` to model class\nattributes, while support for structural types remains limited.\n\nA pragmatic approach involves using `Nullable\u003cT\u003e` for modeling class attributes and `Maybe\u003cT\u003e` for modeling\nreturn values and method parameters.\n\nThe advantage of using `Maybe\u003cT\u003e` over `Nullable\u003cT\u003e` is that `Maybe\u003cT\u003e` provides a set of methods that enable\nchaining operations in a functional manner.\nThis becomes particularly useful when dealing with operations that can optionally return a value,\nsuch as querying a database.\n\nThe implicit conversion from `Nullable\u003cT\u003e` to `Maybe\u003cT\u003e` allows for lifting `Nullable\u003cT\u003e` values to `Maybe\u003cT\u003e`\nvalues and utilizing `Maybe\u003cT\u003e` methods for chaining operations.\n\n\u003e **Practical rule**: Use `Nullable\u003cT\u003e` to model class attributes and `Maybe\u003cT\u003e` to model return values and\n\u003e method parameters.\n\n### Recovering from `Maybe.None` with `WithDefault`\n\nThe `WithDefault` method allows recovering from a `Maybe.None` instance by providing a default value.\n\nFor example, consider the following code snippet:\n\n```csharp\nvar maybeValue = Maybe.None\u003cint\u003e();\nvar value = maybeValue.WithDefault(42);\n```\n\n## Maybe\u0026lt;T\u0026gt; Monad\n\n### Design Goals for `Maybe\u003cT\u003e`\n\nBefore discussing what can be achieved with the `Maybe\u003cT\u003e` monad, let's clarify that it is not intended as a \nreplacement for `Nullable\u003cT\u003e`.\nThis is mainly due to fundamental libraries, such as Entity Framework, relying on `Nullable\u003cT\u003e` to model class\nattributes, while support for structural types remains limited.\n\nA pragmatic approach involves using `Nullable\u003cT\u003e` for modeling class attributes and `Maybe\u003cT\u003e` for modeling\nreturn values and method parameters.\n\nThe advantage of using `Maybe\u003cT\u003e` over `Nullable\u003cT\u003e` is that `Maybe\u003cT\u003e` provides a set of methods that enable\nchaining operations in a functional manner.\nThis becomes particularly useful when dealing with operations that can optionally return a value,\nsuch as querying a database.\n\nThe implicit conversion from `Nullable\u003cT\u003e` to `Maybe\u003cT\u003e` allows for lifting `Nullable\u003cT\u003e` values to `Maybe\u003cT\u003e`\nvalues and utilizing `Maybe\u003cT\u003e` methods for chaining operations.\n\n\u003e **Practical rule**: Use `Nullable\u003cT\u003e` to model class attributes and `Maybe\u003cT\u003e` to model return values and\n\u003e method parameters.\n\n### Complete `Maybe\u003cT\u003e` API Reference\n\n#### Core Methods\n\n##### Map\nTransforms the value if present:\n\n```csharp\nvar maybe = Maybe.Some(\"hello\");\nvar upper = maybe.Map(s =\u003e s.ToUpper()); // Maybe\u003cstring\u003e with \"HELLO\"\n\nvar none = Maybe.None\u003cstring\u003e();\nvar result = none.Map(s =\u003e s.ToUpper()); // Still None\n```\n\n##### Bind\nChains operations that return `Maybe\u003cT\u003e`:\n\n```csharp\npublic Maybe\u003cint\u003e ParseNumber(string text) =\u003e \n    int.TryParse(text, out var num) ? Maybe.Some(num) : Maybe.None\u003cint\u003e();\n\nvar result = Maybe.Some(\"42\")\n    .Bind(ParseNumber); // Maybe\u003cint\u003e with 42\n```\n\n##### Match\nHandles both Some and None cases:\n\n```csharp\nvar maybe = Maybe.Some(\"John\");\nvar greeting = maybe.Match(\n    name =\u003e $\"Hello, {name}!\",\n    \"Hello, stranger!\"\n);\n```\n\n##### GetValue\nRetrieves value with fallback:\n\n```csharp\nvar maybe = Maybe.None\u003cstring\u003e();\nvar value = maybe.GetValue(\"default\"); // Returns \"default\"\nvar value2 = maybe.GetValue(() =\u003e GetDefaultValue()); // Lazy evaluation\n```\n\n##### OfType\nSafe type casting (note: both types must be reference types due to class constraints):\n\n```csharp\nMaybe\u003cobject\u003e maybe = Maybe.Some(\"hello\" as object);\nvar stringMaybe = maybe.OfType\u003cstring\u003e(); // Maybe\u003cstring\u003e with \"hello\"\n\n// Note: OfType has type constraints that limit its use with value types\n```\n\n##### Side Effects: IfSome and IfNone\n\n```csharp\nvar maybe = Maybe.Some(\"important data\");\nmaybe\n    .IfSome(data =\u003e Logger.Info($\"Processing: {data}\"))\n    .IfNone(() =\u003e Logger.Warn(\"No data to process\"));\n```\n\n##### Execute\nPerform actions on the entire Maybe:\n\n```csharp\nvar maybe = Maybe.Some(42);\nmaybe.Execute(m =\u003e Console.WriteLine($\"Maybe contains: {m.IsSome}\"));\n```\n\n##### Predicates and Filtering (class types only)\n\n```csharp\nvar maybe = Maybe.Some(\"42\");\n\n// Check conditions (Satisfy works with class types)\nvar isNumeric = maybe.Satisfy(x =\u003e int.TryParse(x, out _)); // Returns true\n\n// Filter with Where (works on nullable value types)\nvar evenNumber = (42 as int?).Where(x =\u003e x % 2 == 0); // Maybe\u003cint\u003e with 42\nvar oddNumber = (42 as int?).Where(x =\u003e x % 2 == 1); // Maybe\u003cint\u003e as None\n\n// WhereNot (inverse filter)\nvar notEven = (42 as int?).WhereNot(x =\u003e x % 2 == 0); // Maybe\u003cint\u003e as None\n```\n\n##### WithDefault\nProvide fallback values:\n\n```csharp\nvar none = Maybe.None\u003cstring\u003e();\nvar withDefault = none.WithDefault(\"fallback\"); // Maybe\u003cstring\u003e with \"fallback\"\nvar withLazyDefault = none.WithDefault(() =\u003e ExpensiveOperation());\n```\n\n#### Factory Methods\n\n```csharp\n// Create Some value\nvar some1 = Maybe.Some(\"value\");\nvar some2 = new Maybe\u003cstring\u003e(\"value\"); // Equivalent\n\n// Create None\nvar none1 = Maybe.None\u003cstring\u003e();\nvar none2 = new Maybe\u003cstring\u003e(); // Equivalent\n\n// Create from nullable\nstring? nullable = null;\nvar maybe1 = Maybe.From(nullable); // Maybe\u003cstring\u003e as None\nvar maybe2 = (Maybe\u003cstring\u003e)nullable; // Implicit conversion\n```\n\n#### Collection Extensions\n\n```csharp\n// Convert IEnumerable to Maybe (first element or None)\nvar numbers = new[] { 1, 2, 3 };\nvar firstNumber = numbers.ToMaybe(); // Maybe\u003cint\u003e with 1\n\nvar empty = new int[0];\nvar noNumber = empty.ToMaybe(); // Maybe\u003cint\u003e as None\n```\n\n### Converting `Maybe\u003cT\u003e` to `Result\u003cT\u003e`\n\nIt is common to implement a pipeline of operations where an empty `Maybe\u003cT\u003e` instance should be interpreted as a failure,\nin this case the `Maybe\u003cT\u003e` instance can be converted to a `Result\u003cT\u003e` instance by using the `MapToResult` method.\n\nThe `MapToResult` methods can accepts an error as a parameter and returns a `Result\u003cT\u003e` instance with the specified error\nin case the `Maybe\u003cT\u003e` instance is empty.\n\nFor example, consider the following code snippet:\n\n```csharp\nvar result = Maybe\n    .From(someFactoryMethod())\n    .MapToResult(() =\u003e new LogicError(\"Value not found\"))\n    .Bind(ValidateValue)\n    .Bind(UpdateValue);\n\n// Without custom error (uses default MaybeNoneError)\nvar result2 = Maybe.Some(\"value\").MapToResult();\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbogoware%2Fmonads","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbogoware%2Fmonads","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbogoware%2Fmonads/lists"}