1
votes

I'm translating this F# source for railway oriented programming to C#.

I'm having trouble translating this SelectMany overload:


  static member inline SelectMany (this:Result<'TSuccess, 'TMessage>, func: Func<_,_>, mapper: Func<_,_,_>) =
    let mapper = lift2 (fun a b -> mapper.Invoke(a,b))
    let v = bind func.Invoke this
    mapper this v

I've mapped function signature with the above:


  public static Result<TResult, TMessage> SelectMany<TSuccess, TMessage, TValue, TResult>(
    this Result<TSuccess, TMessage> result,
    Func<TSuccess, Result<TValue, TMessage>> func,
    Func<TSuccess, TValue, TResult> mapperFunc)

F# lift2 function (that I think I've correctly translated) accepts as first parameter a function with signature ('a -> 'b -> 'c), but when bound to mapper let-binding with partial application I've problem understanding the used lambda function.

I normally use these helpers for partial application, but I was not able to translate this F# code to C#.

2

2 Answers

3
votes

Your Lift2 is expecting a curried function, but the mapper passed into SelectMany is not curried. So let's curry it:

Func<TSuccess, Func<TValue, TResult>> curriedMapper = suc => val => mapperFunc(suc, val);
Func<
    Result<TSuccess, TMessage>, 
    Result<TValue, TMessage>, 
    Result<TResult, TMessage>
> liftedMapper = (a, b) => Lift2(curriedMapper, a, b);
var v = Bind(func, result);
return liftedMapper(result, v);
1
votes

In general, lift2 takes a function of two arguments of simple types 'a and 'b and produces a function that works on wrapped types M<'a> and M<'b>. Otherwise, you can think of it as taking three arguments: a function over simple types and two wrapped values, unwrapping those values, applying the function to them and wrapping the result.

Assuming your functions were correctly ported from F#, the body of SelectMany would look like this:

return Lift2(mapper, this, Bind(func, this));

The odd Invokes are there in F# version, because Funcs are not applicable like in C#, you have to explicitly call Invoke. Also since it's uncurried, a mapper.Invoke can't be passed straight into lift2 - that's why it's wrapped in a curried function.