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)));
If you want to flatten a functor, then use monadic bind.
This is the simplest alternative I could come up with:
The ToAsync conversion is necessary because it converts
Task<Option<MyVehicle>>toOptionAsync<MyVehicle>, off which the subsequentMapandIterhang.