Replies: 8 comments 3 replies
-
I usually just fall back to regular public void Do(long Id)
{
GetEntity1ById(id)
.Bind(entity1 => (e1: entity1, e2: _entity2Service.GetByEntity1(entity1)))
.Bind((entity1, entity2) => _anotherService.DoSomethingWithBothEntities(entity1, entity2));
} |
Beta Was this translation helpful? Give feedback.
-
That's correct, it's only good if the chain isn't too complicated. You may also try something like this: public void Do(long Id)
{
var e2;
GetEntity1ById(id)
.Bind(entity1 => e2 =_entity2Service.GetByEntity1(entity1))
.Bind(entity1 => _anotherService.DoSomethingWithBothEntities(entity1, e2));
} I personally don't quite like this solution because the saving of the result into a local state goes against FP principles. But it might still be better than falling back to regular |
Beta Was this translation helpful? Give feedback.
-
Cool, now comes the fun part :D I did actually work for an employer where we built an in-house library that does what this repo does, and we came across lots of different issues and scenarios. We had to use Bind, as all services, repositories etc where using they "Maybe" return types (it was called something different), a different solution that a lot of engineers were doing is putting Bind inside Bind, an extreme one had 5 levels of Binds. Something like this public void Do(long Id)
{
GetEntity1ById(id)
.Bind(v1 => M1().Bind(v2 => M2())) //Bind in bind
.Bind(v3 => M3().Bind(v4 => M4()).Bind(v5 => M5())) // 2 levels (Bind to result of Bind)
.Bind(v6 => M6().Bind(v7 => M7().Bind(v8 => M8()))) // different 2 levels (Bind to result of method inside the Bind)
.Bind(v9 => M9().Bind(v10 => M10().Bind(v11 => M11()).Bind(v12 => M12()))) // 3 levels of Bind inside
.Bind(v13=> M13());
} This is obviously just a "sketch" of the calls, but with actual services, methods and variables names in place, and the code expanded into multiple lines, it was a nightmare to even look at and read, let alone debugging it. I just didn't get why it was so important to protect FP at the cost of having bad code like that. |
Beta Was this translation helpful? Give feedback.
-
I don't like these overcomplicated and nested calls either. I'd recommend extracting complicated parts into private methods and giving them meaningful names so that the code becomes easier to grasp. Also, regular What was the different solution you came up with? |
Beta Was this translation helpful? Give feedback.
-
I don't fully remember it, but basically was having a dictionary that stores your returns in every call, and you get it by type. public void Do(long id)
{
//GetEntity1ById returns an instance of type Entity
//M1 returns an instance of type Class1
//M2 returns an instance of type Class2
GetEntity1ById(id)
.BindCollection(entity => M1(entity))
.BindCollection(collection => M2(collection.Get<Class1>()))
.BindCollection(collection => M3(collection.Get<Entity>(), collection.Get<Class2>());
} So you will have all returned values in the dictionary, and you can access them from any Bind, this meant no need for Bind inside Bind, and you can reuse a value returned from the first Bind in the last one. There was a question of what if you need to store two instances of the same type T? You can just have List in the dictionary instead in that case. I know it is not perfect, I only created a POC because of the complains on storing values in local variable, and I need to pass values from one method to the next. |
Beta Was this translation helpful? Give feedback.
-
The issue with regular if-then is you need to manually manage the "Maybe" result and check if an error or not, it was advised against having a manual check for the error and let the library handle it and fall back for you. Seeing an if-statement on Maybe.Result.HasError or Maybe.Result.Error != null (I don't remember how it was done) was considered not-clean-code, and we were encouraged to convert it to utilize Bind |
Beta Was this translation helpful? Give feedback.
-
Here's an idea: [Serializable]
public struct Relay<T, TResult> : IResult<TResult>, ISerializable
where T : struct, IEquatable<T>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<T>
{
private readonly Result<TResult> _result;
public Relay(Result<TResult> result, T relay = default)
{
_result = result;
Relayed = relay;
}
public T Relayed { get; }
public TResult Value => _result.Value;
public bool IsFailure => _result.IsFailure;
public bool IsSuccess => _result.IsSuccess;
public string Error => _result.Error;
public static implicit operator Relay<T, TResult>(Result<TResult> result)
{
return new Relay<T, TResult>(result);
}
public static implicit operator T(Relay<T, TResult> relay)
{
return relay.Relayed;
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
((ISerializable)_result).GetObjectData(info, context);
}
}
public static class ExtensionsExample
{
public static Relay<TRelay, TResult> ToRelay<TRelay, TResult>(this Result<TResult> result, TRelay relay)
where TRelay : struct, IEquatable<TRelay>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<TRelay>
{
return new Relay<TRelay, TResult>(result, relay);
}
public static Relay<TRelay, TResult> ToRelay<TRelay, TResult>(this Result<TResult> result, Func<TResult, TRelay> func)
where TRelay : struct, IEquatable<TRelay>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<TRelay>
{
if (result.IsSuccess)
{
return new Relay<TRelay, TResult>(result, func(result.Value));
}
return result;
}
public static Relay<TRelay, TResult> ToRelay<TRelay, TResult>(this Result<TResult> result, Func<TRelay> func)
where TRelay : struct, IEquatable<TRelay>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<TRelay>
{
if (result.IsSuccess)
{
return new Relay<TRelay, TResult>(result, func());
}
return result;
}
public static Result<TOut> Disperse<TRelay, TResult, TOut>(this Relay<TRelay, TResult> relay, Func<TResult, TRelay, TOut> func)
where TRelay : struct, IEquatable<TRelay>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<TRelay>
{
if (relay.IsFailure)
{
return Result.Failure<TOut>(relay.Error);
}
return func(relay.Value, relay);
}
public static Relay<TRelay, TOut> Bind<TRelay, TResult, TOut>(this Relay<TRelay, TResult> relay, Func<TResult, Result<TOut>> func)
where TRelay : struct, IEquatable<TRelay>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<TRelay>
{
if (relay.IsFailure)
{
return Result.Failure<TOut>(relay.Error);
}
Result<TOut> result = func(relay.Value);
return result.IsSuccess
? new Relay<TRelay, TOut>(result, relay)
: result;
}
public static Relay<TRelay, TOut> Bind<TRelay, TResult, TOut>(this Relay<TRelay, TResult> relay, Func<TResult, Result<TOut>> func, Func<TOut, TRelay, TRelay> relayFunc)
where TRelay : struct, IEquatable<TRelay>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable<TRelay>
{
if (relay.IsFailure)
{
return Result.Failure<TOut>(relay.Error);
}
Result<TOut> result = func(relay.Value);
return result.IsSuccess
? new Relay<TRelay, TOut>(result, relayFunc(result.Value, relay))
: result;
}
} Usage Example: GetEntity1().ToRelay((e1) => (entity: e1, parent: default(Entity)))
.Bind((e1) => GetEntity2(e1.ParentId), (e2, relay) => relay with { parent = e2 })
.Bind((e2) => DoEntity(e2))
.Disperse((_, relay) => DoWithEntities(relay.entity, relay.parent)); |
Beta Was this translation helpful? Give feedback.
-
In my project, I created a simple extension to solve a similar problem: public static partial class ResultExtensions
{
public static Result<(T, K)> BindJoin<T, K>(this Result<T> result, Func<T, Result<K>> toJoin)
{
return result.Bind(r1 => toJoin(r1).Map(r2 => (r1, r2)));
}
public static Result<(T, K)> BindJoin<T, K>(this Result<T> result, Func<T, K> toJoin)
{
return result.BindJoin((t) => Result.Success(toJoin(t)));
}
// etc. - as many as you need in your scenario
} Example usage (publishing event to rabbitmq): var result = CreateMessageBody(message)
.BindJoin(body => GetChannel())
.BindJoin(bc=> bc.Item2.DeclareEventsExchnage<T>())
.Tap(bce=>
{
var ((body, channel), exchangeName) = bce;
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 1;
properties.Persistent = false;
channel.BasicPublish(exchangeName, "", properties, body: body);
}); You can also give the resulting tupple elements some other names like (T A, T B) rather then Item1, Item2. For me the default naming is OK :) |
Beta Was this translation helpful? Give feedback.
-
Assume the following situation
The second Bind cannot see entity1 anymore, how can this be resolved?
Beta Was this translation helpful? Give feedback.
All reactions