GithubHelp home page GithubHelp logo

vkhorikov / csharpfunctionalextensions Goto Github PK

View Code? Open in Web Editor NEW
2.3K 84.0 301.0 1.61 MB

Functional extensions for C#

License: MIT License

C# 99.91% Dockerfile 0.09%
csharp entity functional-programming maybe-monad result value-object

csharpfunctionalextensions's Introduction

Functional Extensions for C#

Build Status NuGet downloads GitHub license

This library helps write code in more functional way. To get to know more about the principles behind it, check out the Applying Functional Principles in C# Pluralsight course.

Installation

Available on NuGet

dotnet add package CSharpFunctionalExtensions

or

PM> Install-Package CSharpFunctionalExtensions

Also available as a strong named assembly (big thanks to bothzoli who made it possible!).

On NuGet

dotnet add package CSharpFunctionalExtensions.StrongName

Core Concepts

Get rid of primitive obsession

Result<CustomerName> name = CustomerName.Create(model.Name);
Result<Email> email = Email.Create(model.PrimaryEmail);

Result result = Result.Combine(name, email);
if (result.IsFailure)
    return Error(result.Error);

var customer = new Customer(name.Value, email.Value);

Make nulls explicit with the Maybe type

Maybe<Customer> customerOrNothing = _customerRepository.GetById(id);
if (customerOrNothing.HasNoValue)
    return Error("Customer with such Id is not found: " + id);

Compose multiple operations in a single chain

return _customerRepository.GetById(id)
    .ToResult("Customer with such Id is not found: " + id)
    .Ensure(customer => customer.CanBePromoted(), "The customer has the highest status possible")
    .Tap(customer => customer.Promote())
    .Tap(customer => _emailGateway.SendPromotionNotification(customer.PrimaryEmail, customer.Status))
    .Finally(result => result.IsSuccess ? Ok() : Error(result.Error));

Wrap multiple operations in a TransactionScope

return _customerRepository.GetById(id)
    .ToResult("Customer with such Id is not found: " + id)
    .Ensure(customer => customer.CanBePromoted(), "The customer has the highest status possible")
    .WithTransactionScope(customer => Result.Success(customer)
        .Tap(customer => customer.Promote())
        .Tap(customer => customer.ClearAppointments()))
    .Tap(customer => _emailGateway.SendPromotionNotification(customer.PrimaryEmail, customer.Status))
    .Finally(result => result.IsSuccess ? Ok() : Error(result.Error));

API Examples

Maybe

Explicit Construction

Use case: Creating a new Maybe containing a value

Maybe<string> apple = Maybe<string>.From("apple");

// or

Maybe<string> apple = Maybe.From("apple"); // type inference

// or

var apple = Maybe.From("apple");

None/No Value

Use case: Replacing null or the Null Object Pattern for representing 'missing' data.

int storeInventory = ...

Maybe<string> fruit = storeInventory > 0
    ? Maybe<string>.From("apple")
    : Maybe<string>.None;

// or where the generic type is a reference type

Maybe<string> fruit = null;

// or where the generic type is a value type

Maybe<int> fruit = default;

Implicit Conversion

Use case: Easily creating a Maybe from a value

// Constructing a Maybe
Maybe<string> apple = "apple"; // implicit conversion

// Or as a method return value
Maybe<string> GetFruit(string fruit)
{
    if (string.IsNullOrWhiteSpace(fruit))
    {
        return Maybe<string>.None;
    }

    return fruit; // implicit conversion
}

Equality

Use case: Comparing Maybes or values without knowledge of the inner value of the Maybes

Maybe<string> apple = "apple";
Maybe<string> orange = "orange";
string alsoOrange = "orange";
Maybe<string> noFruit = Maybe<string>.None;

Console.WriteLine(apple == orange); // false
Console.WriteLine(apple != orange); // true
Console.WriteLine(orange == alsoOrange); // true
Console.WriteLine(alsoOrange == noFruit); // false

ToString

Maybe<string> apple = "apple";
Maybe<string> noFruit = Maybe<string>.None;

Console.WriteLine(apple.ToString()); // "apple"
Console.WriteLine(noFruit.ToString()); // "No value"

GetValueOrThrow

Use case: Procedurally accessing the inner value of the Maybe

Note: Calling this will throw a InvalidOperationException if there is no value

Maybe<string> apple = "apple";
Maybe<string> noFruit = Maybe<string>.None;

Console.WriteLine(apple.GetValueOrThrow()); // "apple";
Console.WriteLine(noFruit.GetValueOrThrow()); // throws InvalidOperationException !!
Console.WriteLine(noFruit.GetValueOrThrow(new CustomException())); // throws CustomException !!

HasValue and HasNoValue

Use case: Procedurally checking if the Maybe has a value, usually before accessing the value directly

void Response(string fruit)
{
    Console.WriteLine($"Yum, a {fruit} ๐Ÿ˜€");
}

Maybe<string> apple = "apple";
Maybe<string> noFruit = Maybe<string>.None;

if (apple.HasValue)
{
    Response(apple.Value); // safe to access since we checked above
}

if (noFruit.HasNoValue)
{
    Response("We're all out of fruit ๐Ÿ˜ข");
}

GetValueOrDefault

Use case: Safely accessing the inner value, without checking if there is one, by providing a fallback if no value exists

void Response(string fruit)
{
    Console.WriteLine($"It's a {fruit}");
}

Maybe<string> apple = "apple";
Maybe<string> unknownFruit = Maybe<string>.None;

string appleValue = apple.GetValueOrDefault("banana");
string unknownFruitValue = unknownFruit.GetValueOrDefault("banana");

Response(appleValue); // It's a apple
Response(unknownFruitValue); // It's a banana

Where

Use case: Converting a Maybe with a value to a Maybe.None if a condition isn't met

Note: The predicate passed to Where (ex )

bool IsMyFavorite(string fruit)
{
    return fruit == "papaya";
}

Maybe<string> apple = "apple";

Maybe<string> favoriteFruit = apple.Where(IsMyFavorite);

Console.WriteLine(favoriteFruit.ToString()); // "No value"

Map

Use case: Transforming the value in the Maybe, if there is one, without needing to check if the value is there

Note: the delegate (ex CreateMessage) passed to Maybe.Map() is only executed if the Maybe has an inner value

string CreateMessage(string fruit)
{
    return $"The fruit is a {fruit}";
}

Maybe<string> apple = "apple";
Maybe<string> noFruit = Maybe<string>.None;

Console.WriteLine(apple.Map(CreateMessage).Unwrap("No fruit")); // "The fruit is a apple"
Console.WriteLine(noFruit.Map(CreateMessage).Unwrap("No fruit")); // "No fruit"

Select

Alias: Maybe.Select() is an alias of Maybe.Map()

Bind

Use case: Transforming from one Maybe into another Maybe (like Maybe.Map but it transforms the Maybe instead of the inner value)

Note: the delegate (ex MakeAppleSauce) passed to Maybe.Bind() is only executed if the Maybe has an inner value

Maybe<string> MakeAppleSauce(Maybe<string> fruit)
{
    if (fruit == "apple") // we can only make applesauce from apples ๐ŸŽ
    {
        return "applesauce";
    }

    return Maybe<string>.None;
}

Maybe<string> apple = "apple";
Maybe<string> banana = "banana";
Maybe<string> noFruit = Maybe<string>.None;

Console.WriteLine(apple.Bind(MakeAppleSauce).ToString()); // "applesauce"
Console.WriteLine(banana.Bind(MakeAppleSauce).ToString()); // "No value"
Console.WriteLine(noFruit.Bind(MakeAppleSauce).ToString()); // "No value"

SelectMany

Alias: Maybe.SelectMany() is an alias of Maybe.Bind()

Choose

Use case: Filter a collection of Maybes to only the ones that have a value, and then return the value for each, or map that value to a new one

Note: the delegate passed to Maybe.Choose() is only executed on the Maybes of the collection with an inner value

IEnumerable<Maybe<string>> unknownFruits = new[] { "apple", Maybe<string>.None, "banana" };

IEnumerable<string> knownFruits = unknownFruits.Choose();
IEnumerable<string> fruitResponses = unknownFruits.Choose(fruit => $"Delicious {fruit}");

Console.WriteLine(string.Join(", ", fruits)) // "apple, banana"
Console.WriteLine(string.Join(", ", fruitResponses)) // "Delicious apple, Delicious banana"

Execute

Use case: Safely executing a void (or Task) returning operation on the Maybe inner value without checking if there is one

Note: the Action (ex PrintFruit) passed to Maybe.Execute() is only executed if the Maybe has an inner value

void PrintFruit(string fruit)
{
    Console.WriteLine($"This is a {fruit}");
}

Maybe<string> apple = "apple";
Maybe<string> noFruit = Maybe<string>.None;

apple.Execute(PrintFruit); // "This is a apple"
noFruit.Execute(PrintFruit); // no output to the console

ExecuteNoValue

Use case: Executing a void (or Task) returning operation when the Maybe has no value

void LogNoFruit(string fruit)
{
    Console.WriteLine($"There are no {fruit}");
}

Maybe<string> apple = "apple";
Maybe<string> banana = Maybe<string>.None;

apple.ExecuteNoValue(() => LogNoFruit("apple")); // no output to console
banana.ExecuteNoValue(() => LogNoFruit("banana")); // "There are no banana"

Or

Use case: Supplying a fallback value Maybe or value in the case that the Maybe has no inner value

Note: The fallback Func<T> (ex () => "banana") will only be executed if the Maybe has no inner value

Maybe<string> apple = "apple";
Maybe<string> banana = "banana";
Maybe<string> noFruit = Maybe<string>.None;

Console.WriteLine(apple.Or(banana).ToString()); // "apple"
Console.WriteLine(noFruit.Or(() => banana)).ToString()); // "banana"
Console.WriteLine(noFruit.Or("banana").ToString()); // "banana"
Console.WriteLine(noFruit.Or(() => "banana").ToString()); // "banana"

Match

Use case: Defining two operations to perform on a Maybe. One to be executed if there is an inner value, and the other to executed if there is not

Maybe<string> apple = "apple";
Maybe<string> noFruit = Maybe<string>.None;

// Void returning Match
apple.Match(
    fruit => Console.WriteLine($"It's a {fruit}"),
    () => Console.WriteLine("There's no fruit"));

// Mapping Match
string fruitMessage = noFruit.Match(
    fruit => $"It's a {fruit}",
    () => "There's no fruit"));

Console.WriteLine(fruitMessage); // "There's no fruit"

TryFirst and TryLast

Use case: Replacing .FirstOrDefault() and .LastOrDefault() so that you can return a Maybe instead of a null or value type default value (like 0, false) when working with collections

IEnumerable<string> fruits = new[] { "apple", "coconut", "banana" };

Maybe<string> firstFruit = fruits.TryFirst();
Maybe<string> probablyABanana = fruits.TryFirst(fruit => fruit.StartsWith("ba"));
Maybe<string> aPeachOrAPear = fruits.TryFirst(fruit => fruit.StartsWith("p"));

Console.WriteLine(firstFruit.ToString()); // "apple"
Console.WriteLine(probablyABanana.ToString()); // "banana"
Console.WriteLine(aPeachOrAPear.ToString()); // "No value"

Maybe<string> lastFruit = fruits.TryLast();
Maybe<string> anAppleOrApricot = fruits.TryLast(fruit => fruit.StartsWith("a"));

Console.WriteLine(lastFruit.ToString()); // "banana"
Console.WriteLine(anAppleOrApricot.ToString()); // "apple"

TryFind

Use case: Safely getting a value out of a Dictionary

Dictionary<string, int> fruitInventory = new()
{
    { "apple", 10 },
    { "banana", 2 }
};

Maybe<int> appleCount = fruitInventory.TryFind("apple");
Maybe<int> kiwiCount = fruitInventory.TryFind("kiwi");

Console.WriteLine(appleCount.ToString()); // "10"
Console.WriteLine(kiwiCount.ToString()); // "No value"

ToResult

Use case: Representing the lack of an inner value in a Maybe as a failed operation

Note: See Result section below

Maybe<string> fruit = "banana";
Maybe<string> noFruit = Maybe<string>.None;

string errorMessage = "There was no fruit to give";

Result<string> weGotAFruit = fruit.ToResult(errorMessage);
Result<string> failedToGetAFruit = noFruit.ToResult(errorMessage);

Console.WriteLine(weGotAFruit.Value); // "banana"
Console.WriteLine(failedToGetAFruit.Error); // "There was no fruit to give"

ToUnitResult

Use case: Representing the lack of an inner value in a Maybe as a failed operation, if an Error is provided

Use case: Representing the presence of an inner value in a Maybe as a failed operation

Note: See UnitResult section below

Maybe<Error> error = new Error();
Maybe<string> noFruit = Maybe<string>.None;

UnitResult<Error> weGotAnError = error.ToUnitResult();
UnitResult<Error> failedToGetAFruit = noFruit.ToUnitResult(new Error());

Console.WriteLine(weGotAnError.IsFailure); // true
Console.WriteLine(failedToGetAFruit.IsFailure); // true

Result

Explicit Construction: Success and Failure

Use case: Creating a new Result in a Success or Failure state

record FruitInventory(string Name, int Count);

Result<FruitInventory> appleInventory = Result.Success(new FruitInventory("apple", 4));
Result<FruitInventory> failedOperation = Result.Failure<FruitInventory>("Could not find inventory");
Result successInventoryUpdate = Result.Success();

Conditional Construction: SuccessIf and FailureIf

Use case: Creating successful or failed Results based on expressions or delegates instead of if/else statements or ternary expressions

bool onTropicalIsland = true;

Result foundCoconut = Result.SuccessIf(onTropicalIsland, "These trees seem bare ๐Ÿฅฅ");
Result foundGrapes = Result.FailureIf(() => onTropicalIsland, "No grapes ๐Ÿ‡ here");

// or

bool isNewShipmentDay = true;

Result<FruitInventory> appleInventory = Result.SuccessIf(isNewShipmentDay, new FruitInventory("apple", 4), "No ๐ŸŽ today");
Result<FruitInventory> bananaInventory = Result.SuccessIf(() => isNewShipmentDay, new FruitInventory("banana", 2), "All out of ๐ŸŒ");

// or

bool afterBreakfast = true;

Result<FruitInventory> orangeInventory = Result.FailureIf(afterBreakfast, new FruitInventory("orange", 10), "No ๐ŸŠ today");
Result<FruitInventory> grapefruitInventory = Result.FailureIf(() => afterBreakfast, new FruitInventory("grapefruit", 5), "No grapefruit ๐Ÿ˜ข");

Implicit Conversion

Use case: Easily creating a successful result from a value

Result<FruitInventory> appleInventory = new FruitInventory("apple", 4);
Result failedInventoryUpdate = "Could not update inventory";

ToString

Use case: Printing out the state of a Result and its inner value or error

Result<FruitInventory> appleInventory = new FruitInventory("apple", 4);
Result<FruitInventory> bananaInventory = Result.Failure<FruitInventory>("Could not find any bananas");
Result failedInventoryUpdate = "Could not update inventory";
Result successfulInventoryUpdate = Result.Success();

Console.WriteLine(appleInventory.ToString()); // "Success(FruitInventory { Name = apple, Count = 4 })"
Console.WriteLine(bananaInventory.ToString()); // "Failure(Could not find any bananas)"
Console.WriteLine(failedInventoryUpdate.ToString()); // "Failure(Could not update inventory)"
Console.WriteLine(successfulInventoryUpdate.ToString()); // "Success"

Map

Use case: Transforming the inner value of a successful Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex CreateMessage) passed to Result.Map() is only executed if the Result was successful

string CreateMessage(FruitInventory inventory)
{
    return $"There are {inventory.Count} {inventory.Name}(s)";
}

Result<FruitInventory> appleInventory = new FruitInventory("apple", 4);
Result<FruitInventory> bananaInventory = Result.Failure<FruitInventory>("Could not find any bananas");

Console.WriteLine(appleInventory.Map(CreateMessage).ToString()); // "Success(There are 4 apple(s))"
Console.WriteLine(bananaInventory.Map(CreateMessage).ToString()); // "Failure(Could not find any bananas)"

MapError

Use case: Transforming the inner error of a failed Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex ErrorEnhancer) passed to Result.MapError() is only executed if the Result failed

string ErrorEnhancer(string errorMessage)
{
    return $"Failed operation: {errorMessage}";
}

Console.WriteLine(appleInventory.MapError(ErrorEnhancer).ToString()); // "Success(FruitInventory { Name = apple, Count = 4 })"
Console.WriteLine(bananaInventory.MapError(ErrorEnhancer).ToString()); // "Failed operation: Could not find any bananas"

Testing

CSharpFunctionalExtensions.FluentAssertions

A small set of extensions to make test assertions more fluent when using CSharpFunctionalExtensions! Check out the repo for this library more information!

Includes custom assertions for

  • Maybe
  • Result
  • Result
  • Result<T, E>
  • UnitResult

Example

var result = Result.Success(420);

result.Should().Succeed(); // passes
result.Should().SucceedWith(420); // passes
result.Should().SucceedWith(69); // throws
result.Should().Fail(); // throws

Analyzers

A Roslyn analyzer package that provides warnings and recommendations to prevent misuse of Result objects in CSharpFunctionalExtensions. Ensures more robust implementation when working with Result types.

Available on NuGet

dotnet add package CSharpFunctionalExtensions.Analyzers

Read or Watch more about these ideas

Related Projects

Contributors

A big thanks to the project contributors!

csharpfunctionalextensions's People

Contributors

a-z-hub avatar benprime avatar bopazyn avatar caleb9 avatar fliebrecht avatar golavr avatar hankovich avatar julienasp avatar jvssd avatar kasamviesselman avatar linkdotnet avatar marcelroozekrans avatar mikelthief avatar mingazhev avatar mnissl avatar prophetlamb avatar razenpok avatar rutkowskit avatar samuelviesselman avatar seangwright avatar sergatgh avatar solvingj avatar space-alien avatar svhankovich avatar svroonland avatar tinytownsoftware avatar vkhorikov avatar xavierjohn avatar yaevh avatar yudatc2i avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

csharpfunctionalextensions's Issues

default ConfigureAwait(false) is causing to lose thread culture

I have a heavy usage of your functional library, it is doing a great job as part of API project. The problem I noticed is that after calling OnSuccess or ToResult on async methods current thread culture is lost because you are using ConfigureAwait(false) as default which doesn't return to thread caller, but to first available thread. I know OnSuccess and ToResult have parameter bool continueOnCapturedContext = false that I can set to true, but I wouldn't want to do this on every place in code. So my question actually is, why do you have ConfigureAwait(false) in your library when .NET is continuing on captured context ( ConfigureAwait(true) ) by default? And is it possible that you set continueOnCapturedContext to have default value = true?

Extension resolution

This works:

private IObservable<Unit> MyMethod() => MyApi
            .DoSomething()
            .Select(result => ResultExtensions
                .OnSuccess(result, () => { _myVariable = result.Value; })
                .OnSuccess(() => NavigationService.GoBack())
                .OnFailure(async error => await NavigationService.DisplayAlert(error))
                .OnBoth(_ => Unit.Default));

this doesn't:

private IObservable<Unit> MyMethod() => MyApi
            .DoSomething()
            .Select(result => result
                .OnSuccess(() => { _myVariable = result.Value; })
                .OnSuccess(() => NavigationService.GoBack())
                .OnFailure(async error => await NavigationService.DisplayAlert(error))
                .OnBoth(_ => Unit.Default));

On the one that fails, R# complains that the first OnSuccess is missing a return statement;
Note that even on the first version I have to add ugly braces otherwise it thinks my Action is a Func.

Is there a "flatMap" method in the library?

I searched the repo and found no reference to flatMap, but perhaps Map is overloaded with similar functionality or something. Can you please clarify?

I am running into scenarios like this and perhaps flatMap would be the typical functional solution.

FirstTask()
.Map(inputObject => CreateSomeResultOfClient(inputObject))
.Map(client => client.DoSomething()) <----Doesn't work because client is Result
.OnSuccess(string => Console.WriteLine(string));
.OnFailure(error => Console.WriteLine(error));

Maybe.None doesn't work with Tuples

Contrary to what I expected, Maybe<(ClassA, ClassB)>.None.Hasvalue is true.

It's more an information than an issue and surely I could try a PR to support tuples but tought it'd be good to mention it somehwhere.

Nuget Package Code Installation

There is some project I cannot use dll file. (Such as Dynamics CRM Plugin)

In this case, source installation is really helpful (like as DryIoC and DryIoC.dll nuget pacakge)

Could you provide source installation Nuget Package?

typed fail excepion

Is it possible to create an additional overload for typed failed exceptions? As far as I can see, only an error-message is supported. But the type of exception is for example easy for unit-testing :)

cheers!

Need help choosing Result or Maybe

I am using the extensions for a method that gets some data from another system.

The 3 possible results of calling this method are:

  1. The data is found and returned.
  2. No data is found for the given parameter values but the connection and query worked.
  3. The database connection and/or query failed for some unexpected reason so we don't know if the data exists or not.

If I use Maybe I can handle case 1 and 2 but case 3 is not expressed in the signature.

If I use Result I can handle 1 and 3 but I'm not sure how to handle case 2.

I guess it is possible to use Result<Maybe<DataObject>>. Is that the best option?

What are the guidelines for choosing Maybe versus Result?

Thanks,

Matthew

Error : Cannot implicitly convert type

The following code:

IEnumerable<string> list = new List<string>();
Maybe<IEnumerable<string>> maybeList = list;  // line in error

gives me the error

Cannot implicitly convert type 'System.Collections.Generic.IEnumerable' to 'CSharpFunctionalExtensions.Maybe<System.Collections.Generic.IEnumerable>'

Instead, the following code, works perfectly:

List<string> list = new List<string>();
Maybe<IEnumerable<string>> maybeList = list;

Any hint?

Make error message separator , configureable

We use this library in our project but we have one suggestion. It would be cool if it is able to configure the error message separator which is now the constant ", " on one central point.

For example:

Result.SetupErrorMessageSeparator("$$");

I know, it is able to pass the error message separator on method level like

Result.Combine("$$", r1, r2);

But this is not a solution for us because then we have to pass the separator on each usage of combine.

Do you see a opportunity to implement this?

How using OnSuccess/OnFailure inside an linq aggregate function

var totalCost = Orders.Sum(s =>
                {
                    return OperationOnOrder(s)
                        .OnBoth(r => r.IsSuccess ? r.Value.Cost: 0)
                        ;
                });

So OperationOnOrder returns

Result<SomethingElse>

Now, the problem is that I can not "jump out" when OperationOnOrder returns failure, I can only return a decimal value (which sum can handle), so what I do this return 0, but I want to return failure rather than 0.

Any suggestion?

Suggestion: tag versions

I'm trying to see what has changed between the 1.7.1 and 1.8.0 nuget package versions. Tagging the versions would help with that considerably.

Maybe<int>.None.HasValue is true

While tracking a bug if found that Maybe.None.HasValue was true whereas my expected behavior is that any None would have no values.

Initializing new Maybe object

First, this is an awesome library!! I have been using it for several days and I love the control flow.

One change I'd like to suggest is to the Maybe module. It works great if you want to expose a Maybe as a property. But I'd like to be able to create a new Maybe object and pass it on.

For example, an error occurs and I just want to ignore it and return a Maybe. Currently, it seems like you have to create a new variable and return it. This is a little ugly in a lambda.

.OnBoth(result =>
 {
       Maybe<CachedVehicle> ret = result.IsSuccess ? result.Value : null;
       return Result.Ok(ret);
})

I'd prefer to do something like this:

.OnBoth(result => Result.Ok(Maybe<CachedVehicle>(result.IsSuccess ? result.Value : null))

Does that make sense? Did I miss something and maybe this is already possible?

Best Method to "Peek" Into a Result for Logging

With Java streams we have a passive stream operation called "peek" which passes the elements un-touched to the next operation (lambda should return void):

list
.stream()
.peek(element -> log.info("element = " + element))
.map(element -> element is unscathed)

I haven't seen an equivalent in the library. Does one exist? If not, hard to add? It's like ensure but returns void rather than bool?

1.8.0+ Breaks at least one Ensure Overload

The following worked on 1.7.1 but I just updated to 1.8.1 and now it fails to match the appropriate overload on ensure.

Here was my original code:

            return GetAuthenticationContext(authority)
                .OnSuccess(context => AcquireTokenAsync(context, resource))
                .Ensure(authResult => authResult != null, "Failed to Obtain JWT")
                .Map(authResult => authResult.AccessToken)
                .Result.Value;

AcquireTokenAsync() returns Task<Result<T>>

Heres the overload it used to match in AsyncResultExtensionsLeftOperands.cs

        public static async Task<Result<T>> Ensure<T>(this Task<Result<T>> resultTask, Func<T, bool> predicate, string errorMessage)
        {
            Result<T> result = await resultTask;

            if (result.IsFailure)
                return Result.Fail<T>(result.Error);

            if (! predicate(result.Value))
                return Result.Fail<T>(errorMessage);

            return Result.Ok(result.Value);
        }

My guess is that it's related to the new param bool continueOnCapturedContext = false, but in theory the default parameter should have maintained backward compatibility... so I don't know what the deal is. If I find time, I'll try to write a new test and troubleshoot.

Func<T> to Result<T>

Hi! I have a personal project with a class similar to Result<T> and I have the following method, which is used to wrap code on Result structure.

I am planning to submit a PR for this. What do you think?

Code

public static Result<T> ToResult<T>(this Func<T> func)
{
    try
    {
       return Result.Ok(func());
    }
    catch (Exception ex)
    {
       return Result.Fail<T>(ex.Message);
    }
}

Usage

public Result<TDTO> GetById(TKey key) 
{
    return Result.ToResult(() => ToDTO(Repository.GetById(key)));
}

How to share value between successive calls in the pipeline

I have following method:

public async Task<Result<MyOrderModel>> GetMyOrderByOrderId(string subject, long orderId)
        {
            long customerId = 0;

            Action<Customer> keepCustomerId = (c) => customerId = c.Id;

            return await GetCustomerBySubjectAsync(subject)
               .ToResult(ErrorMessage.CustomerNotFound)
               .OnSuccess(customer => keepCustomerId(customer))
               .OnSuccess(customer => GetOrderByIdAsync(orderId).ToResult(ErrorMessage.OrderNotFound))
               .Ensure(order => order.Customer.Id == customerId, ErrorMessage.AmbigiousPrincipalCall)
               .OnSuccess(order => _mapper.Map<MyOrderModel>(order));
        }
  public async Task<Maybe<Customer>> GetCustomerBySubjectAsync(string sub)
        {
            return await _context.Customers.Where(c => c.Sub == sub).FirstOrDefaultAsync();
        }

In the Ensure statement I need access to a result of GetCustomerBySubjectAsync.
For that reason I'm storing customerId in the very first OnSuccess method.

I'm sure there is a more elegant way?
Thanks in advance.

Result with Task Cancellation

Hi Vladimir,

I really enjoyed your PluralSight course and I really appreciate the thoughts which went into this library.
However, I am not entirely sure how to use the Result struct together with a cancelable task (e.g if I have a method, that returns a Task<Result>).
How would you handle the task cancellation? Returning an error result seems to be somehow wrong. I also though about returning a Result<Maybe>

ResultExtensions, why Ensure creates new instance of Result?

Hi, is there a reason that Ensure method creates a new instances of result?
We can just return current result object if current result.IsFailure.

` public static Result Ensure(this Result result, Func<T, bool> predicate, string errorMessage)
{
if (result.IsFailure)
return Result.Fail(result.Error);

        if (!predicate(result.Value))
            return Result.Fail<T>(errorMessage);

        return Result.Ok(result.Value);
    }`

Maybe with Structs

Is there any way to support structs with Maybe?

I removed the 'where T : class' from the code but then I found this issue:

struct Account{
public Guid Id;
}

Maybe<Account> SomeFunction(){
var list = new List<Account>();
return list.FirstOrDefault()
}

var result = SomeFunction();
//This is true!! it should be false
Assert.IsTrue(result.HasValue)

I want to make Account a struct because I need to use use Entity Framework and its use of references (not immutability) is making my life crappy. So I tried to use structs for value passing, but now I can't seem to use Maybe.

Any suggestions you have would be great. Hope I provided enough info.

How can I distinguish between types of failures with Result?

I am using the ResultExtensions in a web API project. No matter what type of error happens in the railway oriented series of calls I am returning 400 (Bad Request). This is convenient and sufficient for now. Here is an excerpt of the code:

[Route("api/qrs/asbuilt/serialnumberstructure")]
[HttpPost]
public IHttpActionResult Post([FromBody] StructureRequestDto structureRequestDto)
{
    return
        ValidateRequest(structureRequestDto)
            .OnSuccess(request => AddNextSerialNumberIfMissing(request))
            .OnSuccess(request => AddPreviousSerialNumberIfMissing(request))
            .OnSuccess(request => AddStructureIfMissing(request))
            .OnSuccess(request => GetStructure(request))
            .Ensure(response => response.HasValue, FormatStructureNotFoundError(structureRequestDto))
            .Map(response => response.Value)
            .OnBoth(
                    response =>
                    response.IsSuccess ? 
                        this.Ok(response.Value) as IHttpActionResult :
                        this.BadRequest(response.Error) as IHttpActionResult);
}

I would like to improve the accuracy of my responses and return 500 for system errors and 400 when the error is caused by the input data. Because the error is string I'm not sure of a good way to do this. I thought about putting the HTTP status codes into the error strings and then parse at the end. That might work.

Are there any suggestions that would help me to be more specific with my error responses?

Thanks

Result with error type

Hey guys,
First of all a very usefull library :) .

  1. When using error types (Result<int, SomeType> ) in an Onsuccess method that is calling an async method, the resulting Result is wrapped in another Result

Example

Task<Result<int,ErrorType>> GetSomething(){ ... }

var theResult= await Result.Ok().OnSuccess( () => GetSomething());

theResult will be of type Result<Result<int,ErrorType>>

If the example is not clear, please let me know and I will create a test project

I suppose that the methods in AsyncResultExtensionsRightOperand need to be extended

It would be nice to be able to use an error object without the need to also have a return type for value. Maybe an interface needs to be introduced

Install failed - could not install package ... into a project that targets '.NETFramework,Version=v4.0'

Trying to install the package into .NET 4.0 project, but it fails. Is .NET 4.0 not supported or is something else going wrong?

Error:

Install failed. Rolling back...
Install-Package : Could not install package 'CSharpFunctionalExtensions 1.7.1'. You are trying to install this package into a project that targets '.NETFramework,Version=v4.0', but the package does not contain any assembly references or content files that are
 compatible with that framework. For more information, contact the package author.
At line:1 char:16
+ Install-Package <<<<  CSharpFunctionalExtensions
    + CategoryInfo          : NotSpecified: (:) [Install-Package], InvalidOperationException
    + FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PowerShell.Commands.InstallPackageCommand
 
PM> 

Result desconstruction

I want to propose the feature of deconstructing Result, so we can do

var (isSuccess, isFailure, error) = Result.Ok();
var (isSuccess, isFailure, somethingIsDisabled, error) = Result.Ok(true);

Motivation for me is the case when you have a primitive value, like bool wrapped in Result and we want to leave the monad e.g.

var serviceIsDisabled = CheckIfDisabled()

if (serviceIsDisabled.Value) // Do something

serviceIsDisabled.Value in condition looks kinda ugly to me, so i've implemented deconstruction via extension methods. It would be nice to have it in the main package.

Please, let me know if you're ok with and i'll prepare PR with tests and stuff

Result Try method

I'm using your Result object with a third-party library that communicates using exceptions. I'd like to lift the exceptions into a failed Result. My current code just returns a different Result from each branch of the try-catch.

I'm thinking something like:

Result.Try(() => library.ThingThatMightFail(),
           (ex) => "Some error message, possibly branching on exception type...");

// Where
Try<Result<T>>(Func<T> attempt, Func<Exception,string> failure)

Handling specific exceptions can be done with ex is SomeException, and truly unexpected exceptions can be rethrown.

This would just be a convenience method, but often the reason I'm using a Result is to hide library exceptions behind more user friendly error messages.

EqualityComparer for Maybe<Dynamic> returns not equals for Maybe<>.None.

Hi,
I came across this issue while investigating a problem in other open project (NSubstitute). After some ffiddling I think I managed to trace it to the implementation of EqualityComparer for Maybe<>.

They say code is worth 1000 words, so here is the sample :

    [Test]
    public void Bug_MaybeDynamicNone()
    {
        var compareUsingMaybeDynamic = EqualityComparer<Maybe<dynamic>>.Default.Equals(Maybe<dynamic>.None, Maybe<dynamic>.None);
        
        var compareObjectNotDynamic = EqualityComparer<object>.Default.Equals(Maybe<int>.None, Maybe<int>.None);

        var compareDynamicInt = EqualityComparer<object>.Default.Equals((dynamic)1, (dynamic)1);

        //NSubstitute is using object as generic type which doesnt work for Maybe<dynamic>
        var compareUsingMaybeObject = EqualityComparer<object>.Default.Equals(Maybe<dynamic>.None, Maybe<dynamic>.None);

        Assert.IsTrue(compareUsingMaybeDynamic);
        Assert.IsTrue(compareObjectNotDynamic);
        Assert.IsTrue(compareDynamicInt);
        Assert.IsTrue(compareUsingMaybeObject); // Only comparission of Maybe<dynamic>.None using object comparer fails.
    }

    [Test]
    public void Bug_DefaultInstance()
    {
        var compareMaybeIntUsingObject = EqualityComparer<object>.Default.Equals(Activator.CreateInstance(typeof(Maybe<int>)), Activator.CreateInstance(typeof(Maybe<int>)));

        var compareUsingMaybeDynamic = EqualityComparer<Maybe<dynamic>>.Default.Equals(Activator.CreateInstance(typeof(Maybe<dynamic>)), Activator.CreateInstance(typeof(Maybe<dynamic>)));

        //NSubstitute is using object as generic type which doesnt work for Maybe<dynamic>
        var compareMaybeDynamicUsingObject = EqualityComparer<object>.Default.Equals(Activator.CreateInstance(typeof(Maybe<dynamic>)), Activator.CreateInstance(typeof(Maybe<dynamic>)));

        Assert.IsTrue(compareMaybeIntUsingObject);
        Assert.IsTrue(compareUsingMaybeDynamic); // Surprisingly this one fails for Default instance but not for Maybe<>.None
        Assert.IsTrue(compareMaybeDynamicUsingObject); //Here is the same problem as with Maybe<dynamic>.None in the test above 
    }

I hope this will be enough to reproduce it. In case you need any more information let me know.

Regards
K.

Edit:
I found even simpler repro

var equals = Maybe<dynamic>.None == (dynamic)Maybe<dynamic>.None;
Assert.True(equals);

also found where the problem is Line 71 in Maybe<>

if (obj is T)
{
    obj = new Maybe<T>((T)obj);
}

This check will pass for dynamic and will create new object with value in it which will fail down the line since current object HasNoValue while the newly created one Has.

Edit 2:
Actually
Maybe<dynamic>.None == (dynamic)Maybe<dynamic>.None;
Is a problem with Implicit conversion while my problems were in Equals.
Changing line 71 to
if (obj is T && !(obj is Maybe<T>))
fixes my problem but doesnt fix implicit conversion bug. I tried to find something to make also this one go away, but only thing I found was an infinite implicit conversion :)

Thats all I've got :) Hope this helps.

K.

Proposal: Add implicit operator T(Maybe<T> value)

I think it would be nice to be able to implicitly convert Maybe<T> to T.
By adding this piece of code to Maybe<T>:

public static implicit operator T(Maybe<T> maybe)
{
	return maybe._value;
} 

it will be possible to do something like this:


Maybe<int> maybe = 666;	
int value = maybe;	
int result = value + maybe + value;

It will also allow passing directly Maybe to functions with T parameter.

Result - How to handle async methods in the middle of a chain when the chain doesn't begin with an async method

For example the following test doesn't compile

public async Task<string> Promote_with_async_methods_in_the_middle_of_the_chain_but_not_starting_the_chain(long id)
{
	var gateway = new EmailGateway();

	// GetById rather than GetByIdAsync
	return await GetById(id)
		.ToResult("Customer with such Id is not found: " + id)
		.Ensure(customer => customer.CanBePromoted(), "The customer has the highest status possible")
		.OnSuccess(customer => customer.PromoteAsync())
		.OnSuccess(customer => gateway.SendPromotionNotificationAsync(customer.Email))
		.OnBoth(result => result.IsSuccess ? "Ok" : result.Error);
}

Help with naming extension method that executes some function and returns Result<T> if success

I'm writing an extension method that operates over a collection and executes some operation. It doesn't care about the output of that function, rather it returns back the original collection if the operation succeeds, else it returns a Result.Fail (and the error msg of the action) if it fails.

So instead of doing

DoSomething()
  .OnSuccess(x => x.DoSomethingElse())
  .OnSuccess(x => {
    DoSomethingElse2()
    return x
  })
  .OnSuccess(x => x.DoSomethingElse3())

I'd do something like

DoSomething()
  .OnSuccess(x => x.DoSomethingElse())
  .OnSuccess(x => x.Execute(() => DoSomethingElse2()))
  .OnSuccess(x => x.DoSomethingElse3())

Where Execute is a generic function defined as

public static Result<T> Execute<T>(this T collection, Func<Result> fn)
   where T : IEnumerable
{
   Result res = fn();
   return res.IsSuccess
      ? Result.Ok(collection)
      : Result.Fail<T>(res.Error);
}

Is Execute an appropriate name in this case? I only care about whether an action method succeeds or not, but I'm never returning the actual results of that function so it seems misleading...how would you name that? Since I'm using your library, I figured I could use your expertise ๐Ÿ˜„

Result.Combine does not work with Result<T>[]

Pretty clear issue here, just needs an overload.

Result[] test1 = new Result[] { Result.Ok("one") };

Result.Combine(test1);
//Throws "Argument 1: cannot convert from 'CSharpFunctionalExtensions.Result[] to 'string'"

Result.Combine("dummy", test1);
//Throws "Argument 2: cannot convert from 'CSharpFunctionalExtensions.Result[] to 'CSharpFunctionalExtensions.Result'"

Maybe.None could be added

I could be wrong, but when I want to instantiate Maybe without value I have to use
new Maybe<SomeType>() syntax.
For convenience reason could Maybe.None static method be added like in some other Maybe libraries?

Question about Maybe and Enums

I think I just determined that Maybe can't be used to hold enum's because it can currently only hold reference types, is this correct?

If so, I think it's because you've constrained T to "class" specifically.

public struct Maybe<T> : IEquatable<Maybe<T>> where T : class

Can you think of any easy way to achieve an enum type in a Maybe?

For the record, I was trying to implement text parsing adapters for enums and other primitive types into Maybe's. The goal was similar to what was achieved here:

https://github.com/louthy/language-ext

If you scroll down, you can see how they replaced TryParse with parseInt that returns an option.

Interestingly, they did not do am implementation for parseEnum, but in theory they could have.

Maybe<DateTime> doesn't work

Hi!

I'm trying to have optional DateTime. But when I do Maybe<DateTime>.None, it uses the default value which is 0001-01-01, and when I do .HasValue, it's true.

Thanks !

Cannot Access Maybe<T>.None on .NET 4.6.2

Hello,

I am using your library within a .NET 4.6.2 project and cannot access the Maybe.None static property.

The following does not compile (error is: 'Maybe' does not contain a definition for 'None'):
Maybe<MyClass> none = Maybe<MyClass>.None;

But this does:
Maybe<MyClass> none = null;

What's going on here?

Thanks,
Greg Valainis

Add extension method Combine for IEnumerable<Result<T>> / IEnumerable<Result>

In case OnSuccess return IEnumerable<Result> required to use method:

var result = Result.Combine(results);
return result.IsSuccess
         ? Result.Ok(results.Select(e => e.Value))
         : Result.Fail<IEnumerable<T>>(result.Error);

and to do it in OnSuccess method:
.OnSuccess(results => /*code above*/)

It will be nice to have extension methods:
Result<IEnumerable<T>> Combine(this IEnumerable<Result<T>> results)
Result Combine(this IEnumerable<Result> results)

In this case we will have something like:

.OnSuccess(arg => ... ) /* returns IEnumerable<Result<T>> */
.Combine() /* convert IEnumerable<Result<T>> to Result<IEnumerable<T>> */
.OnSuccess(collection => ... )

Json serialization of Result

I can't return Result from a webapi as in case of success, Error throws an exception of type 'System.InvalidOperationException'. Why not just return string.empty?

Adding an implicit operator for T to the Result<T> class to get the value?

I was wondering if this would be a good addition to the Result<T> class:

public static implicit operator T(Result<T> result) { return result.Value; }

The benefits of this implicit operator removes the need for doing result.Value in many places. For example:

public TestClass()
{
            var result = Result.Ok(new Foo());
            //instead of: Foo foo = result.Value
            Foo foo = result;

            //instead of: MethodThatTakesFoo(result.Value)
            MethodThatTakesFoo(result);
}
public void MethodThatTakesFoo(Foo value)  { }

The other benefit is you could rename your var names:

//instead of: var userOrError = ...
var user = userRepository.GetUser(id);
if(user.IsFailure) { }
//Do stuff to the user
//instead of: userRepository.UpdateUser(userOrError.Value);
userRepository.UpdateUser(user);

I've noticed I find myself passing the result into other methods and forgetting the .Value (which obviously causes a compile error). Also, I'm not a big fan of the name "Value", as it is a little too generic and adds a bit of clutter in my opinion. I'm curious about your thoughts on this change. What would be the potential issues/drawback with doing this approach?

Thanks

Name collisions

Hi Vladimir,

Currently I am trying to use your CSharpFunctionalExtensions library, but I am getting a name collision when I try to use it as follows.

// Given a function with a signature of Func<string, bool>.
bool IsNotNullOrWhiteSpace(string text) => !string.IsNullOrWhiteSpace(text);

// When I try to use Ensure like this.
// Then the compiler gives a error.
void MyFunction(string text) {
  Result.Ok(text)
    .Ensure(IsNotNullOrWhiteSpace, "Text is null or white space.")
}

Compiler warning:

The call is ambiguous between the following methods or properties:
'AsyncResultExtensionsRightOperand.Ensure(Result, Func<T, Task>, string)'
and
'ResultExtensions.Ensure(Result, Func<T, bool>, string)'

// This is possible:
void MyWorkAround(string text) {
  Result.Ok(text)
    .Ensure(t => IsNotNullOrWhiteSpace(t), "Text is null or white space.")
}

It is not a problem, though I prefer the cleaner look of just using the name of my expression bodied function.

I would prefer prefixing async functions with Async to explicitly state their nature and moving them to their own namespace.

If you do not have any objections I could create a pull request unless this behavior is by design?

P.S.
I really enjoyed your PluralSight course and I am looking forward to seeing more from you in the future.

Strong Name

Is it possible to strong name the assemblies?

Many Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.