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.