Home > OS >  Using Bind in an asynchronous context
Using Bind in an asynchronous context

Time:01-20

I'm using the `language-ext' package for C# in an attempt to make our code more robust, but struggling on how to chain async operations.

Consider the following abstract example of a simple class describing a vehicle

public class MyVehicle
{
    public MyVehicle(string name)
    {
        Name = name;
    }

    public string Name { get; }

    public MyVehicle DeepCopy()
    {
        return new MyVehicle(Name);
    }
}

and another class to store some state about a vehicle

public class MyVehicleState
{
    public MyVehicle CopiedVehicle { get; private set; }

    public Option<int> EditingId { get; set; }

    public void SetCopiedVehicle(MyVehicle vehicle)
    {
        CopiedVehicle = vehicle;
    }
}

and a vehicle repository to retrieve vehicles

public class MyVehicleRepository
{
    public Option<MyVehicle> GetVehicle(int id)
    {
        return id == 2 ? Some(new MyVehicle("Delorian")) : Option<MyVehicle>.None;
    }
}

If I want to set the copied vehicle property in MyVehicleState to be a deep copy of that defined by SelectedVehicleId in the same class, I could do the following (not sure if correct):

    var state = new MyVehicleState
    {
        EditingId = Some(2)
    };
    var repo = new MyVehicleRepository();

    state.EditingId
        .Bind(vehicleId => repo.GetVehicle(vehicleId))
        .Map(vehicle => vehicle.DeepCopy())
        .IfSome(copiedVehicle => state.SetCopiedVehicle(copiedVehicle));

Now, what if I change the vehicle repository to be asynchronous, such that

public class MyVehicleRepository
{
    public async Task<Option<MyVehicle>> GetVehicle(int id)
    {
        await Task.Delay(TimeSpan.FromSeconds(2));
        return id == 2 ? Some(new MyVehicle("Delorian")) : Option<MyVehicle>.None;
    }
}

How do I update my functional code to do the same thing and set the deep copy in my state class?

I've tried swapping to BindAsync and using MapAsync, but I seem to end up with an input I need to unwrap twice and not sure how to proceed (N.B. new to this functional world).

Update

The following seems to work, but is it the most succinct way?

await state.EditingId
        .MapAsync(async vehicleId => await repo.GetVehicle(vehicleId))
        .IfSome(x => x
            .Map(y => y.DeepCopy())
            .IfSome(z => state.SetCopiedVehicle(z)));

CodePudding user response:

If you want to flatten a functor, then use monadic bind.

This is the simplest alternative I could come up with:

await state.EditingId
    .BindAsync(i => repo.GetVehicle(i).ToAsync())
    .Map(v => v.DeepCopy())
    .Iter(state.SetCopiedVehicle);

The ToAsync conversion is necessary because it converts Task<Option<MyVehicle>> to OptionAsync<MyVehicle>, off which the subsequent Map and Iter hang.

  • Related