Orx.Fun.Result 1.3.1

dotnet add package Orx.Fun.Result --version 1.3.1                
NuGet\Install-Package Orx.Fun.Result -Version 1.3.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Orx.Fun.Result" Version="1.3.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Orx.Fun.Result --version 1.3.1                
#r "nuget: Orx.Fun.Result, 1.3.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Orx.Fun.Result as a Cake Addin
#addin nuget:?package=Orx.Fun.Result&version=1.3.1

// Install Orx.Fun.Result as a Cake Tool
#tool nuget:?package=Orx.Fun.Result&version=1.3.1                

Orx.Fun.Result

A simple result type for C#, not minding the error type, instead aiming to be explicit, concise and fluent.

Complete auto-generated documentation can be found here: sandcastle-documentation.

Why?

  • Result (either) type is necessary.
    • Explicit result returns
      • we know that the method might fail;
      • however, it will not throw,
      • it will just return the error if it fails.
    • Continuations without overwhelming try-catch blocks

Consider the following login method, for instance. It can return a valid user only after a series of validation tests are completed. Here, the result type plays the role of an optional with additional information about the failed validation.

static Res<User> Login(string username, string passwordHash)
{
    return OkIf(!string.IsNullOrEmpty(username))    // validate username
        .OkIf(!string.IsNullOrEmpty(passwordHash))  // validate password-hash
        .OkIf(userRepo.ContainsKey(username))       // further validate user
        .Map(() => GetUser(username, password));    // finally map into actual result;
                                                    // any Err in validation steps will directly be mapped to Err, avoiding GetUser call.
}

Alternatively, consider operations that has external risks of failure; such as connecting to a database or reading a file. There exist various reasons such operations can lead to exceptions; however, it is not always explicit. Further, try-catch blocks are too large to be reader friendly.

Consider the example below where we need to parse a user from a file and write it to a database. Both of these operations might fail due to external reasons.

static User ParseUser(string fileContent)
{
	// assume this is a safe method once the file is read.
    // but 'File.ReadAllText' can throw
}
static void PutUserToDatabase(User user)
{
	// this can throw
}

Firstly, the concise but dangerous version:

static void ParseAndPutUserDangerous(string filePath)
{
	string fileContent = File.ReadAllText(filePath);	// can throw
    User user = ParseUser(fileContent);
    PutUserToDatabase(user);							// can throw
}

The caller must catch the exceptions in order not to crash the app; but the danger is not explicit in the function signature, it just returns a void.

Alternatively, the method itself can try to deal with the errors:

static void ParseAndPutUserConfused(string filePath)
{
	try
    {
    	string fileContent = File.ReadAllText(filePath);
        User user = ParseUser(fileContent);
        try
        {
        	PutUserToDatabase(user);
	    }
        catch (Exception dbException)
        {
        	// what do we do with the exception ?
        }
	}
    catch (Exception ioException)
    {
    	// what do we do with the exception ?
	}
}

But now, we don't know what to do with the error. Especially, if this method is called from at least two different methods, we cannot or should not decide. Let the caller decide. Also, try-catch blocks cause too much noise.

What we actually want is something like this:

static Res ParseAndPutUser(string filePath)
{
	return Ok()
    	.TryMap(() => File.ReadAllText(filePath))
        .Map(ParseUser)
        .Try(PutUserToDatabase);
}

Note that this is as concise as the dangerous version; however, it cannot throw and it is explicit in the following sense:

  • whenever Try is used, we know that the internal operation can throw; however, if the exception is thrown it will safely be caught and converted to an error within the Try method;
  • TryMap method is similar in this sense; however, it returns a value rather than void;
  • when we see Map, on the other hand, we know that we are just mapping the value and we know that the internal operation does not throw or fail.

You may see that the result type also provides us with the continuations. In the above example, if either of the "ReadAllText" or "PutUserToDatabase" methods fail; the error will be propagated to the end of the method. Another useful method in this regards is apparently the FlatMap.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  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 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Orx.Fun.Result:

Package Downloads
Orx.MathProg

A mathematical modeling library that enables building, documenting and solving concise, simple and reusable models.

Orx.Fun.Compose

A function composition library for C#, heavily experimental right now, would be more practical if C# gets generic operator overloading.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.3.1 236 12/19/2023
1.3.0 123 12/19/2023
1.2.1 286 7/27/2023
1.2.0 163 7/27/2023
1.1.1 150 7/27/2023
1.1.0 167 7/26/2023
1.0.1 272 5/15/2023
1.0.0 161 5/5/2023