Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Railway Oriented programming in C# - How do I write the switch function?

I've been following this F# ROP article, and decided to try and reproduce it in C#, mainly to see if I could. Apologies for the length of this question, but if you're familiar with ROP, it will be very easy to follow.

He started off with an F# discriminated union...

type Result<'TSuccess, 'TFailure> =
  | Success of 'TSuccess
  | Failure of 'TFailure

...which I translated into an abstract RopValue class, and two concrete implementations (note that I have changed the class names to ones that I understood better)...

public abstract class RopValue<TSuccess, TFailure> {
  public static RopValue<TSuccess, TFailure> Create<TSuccess, TFailure>(TSuccess input) {
    return new Success<TSuccess, TFailure>(input);
  }
}

public class Success<TSuccess, TFailure> : RopValue<TSuccess, TFailure> {
  public Success(TSuccess value) {
    Value = value;
  }
  public TSuccess Value { get; set; }
}

public class Failure<TSuccess, TFailure> : RopValue<TSuccess, TFailure> {
  public Failure(TFailure value) {
    Value = value;
  }
  public TFailure Value { get; set; }
}

I added a static Create method to allow you to create a RopValue from a TSuccess object, which would be fed into the first of the validation functions.

I then went about writing a binding function. The F# version was as follows...

let bind switchFunction twoTrackInput =
  match twoTrackInput with
  | Success s -> switchFunction s
  | Failure f -> Failure f

...which was a doddle to read compared to the C# equivalent! I don't know if there is a simpler way to write this, but here is what I came up with...

public static RopValue<TSuccess, TFailure> Bind<TSuccess, TFailure>(this RopValue<TSuccess, TFailure> input, Func<RopValue<TSuccess, TFailure>, RopValue<TSuccess, TFailure>> switchFunction) {
  if (input is Success<TSuccess, TFailure>) {
    return switchFunction(input);
  }
  return input;
}

Note that I wrote this as an extension function, as that allowed me to use it in a more functional way.

Taking his use case of validating a person, I then wrote a Person class...

public class Person {
  public string Name { get; set; }
  public string Email { get; set; }
  public int Age { get; set; }
}

...and wrote my first validation function...

public static RopValue<Person, string> CheckName(RopValue<Person, string> res) {
  if (res.IsSuccess()) {
    Person person = ((Success<Person, string>)res).Value;
    if (string.IsNullOrWhiteSpace(person.Name)) {
      return new Failure<Person, string>("No name");
    }
    return res;
  }
  return res;
}

With a couple of similar validations for email and age, I could write an overall validation function as follows...

private static RopValue<Person, string> Validate(Person person) {
  return RopValue<Person, string>
    .Create<Person, string>(person)
    .Bind(CheckName)
    .Bind(CheckEmail)
    .Bind(CheckAge);
}

This works fine, and enables me to do something like this...

Person jim = new Person {Name = "Jim", Email = "", Age = 16};
RopValue<Person, string> jimChk = Validate(jim);
Debug.WriteLine("Jim returned: " + (jimChk.IsSuccess() ? "Success" : "Failure"));

However, I have a few issues with the way I've done this. First off is that the validation functions require you to pass in a RopValue, check it for Success or Failure, if Success, pull out the Person and then validate it. If Failure, just return it.

By contrast, his validation functions took (the equivalent of) a Person, and returned (a Result, which is the equivalent of) a RopValue...

let validateNameNotBlank person =
  if person.Name = "" then Failure "Name must not be blank"
  else Success person

This is much simpler, but I was unable to work out how to do this in C#.

Another issue is that we start the validation chain with a Success<>, so the first validation function will always return something from the "if" block, either a Failure<> if the validation failed, or a Success<> if we got past the checks. If a function returns Failure<>, then the next function in the validation chain never gets called, so it turns out that we know that these methods can never be passed a Failure<>. Therefore, the final line of each of these functions can never be reached (other than in the weird case that you manually created a Failure<> and passed it in at the start, but that would be pointless).

He then created a switch operator (>=>) to connect validation functions. I tried doing this, but couldn't get it to work. In order to chain successive calls to the function, it looked like I'd have to have an extension method on a Func<>, which I don't think you can do. I got as far as this...

public static RopValue<TSuccess, TFailure> Switch<TSuccess, TFailure>(this Func<TSuccess, RopValue<TSuccess, TFailure>> switch1, Func<TSuccess, RopValue<TSuccess, TFailure>> switch2, TSuccess input) {
  RopValue<TSuccess, TFailure> res1 = switch1(input);
  if (res1.IsSuccess()) {
    return switch2(((Success<TSuccess, TFailure>)res1).Value);
  }
  return new Failure<TSuccess, TFailure>(((Failure<TSuccess, TFailure>)res1).Value);
}

...but couldn't work out how to use it.

So, can anyone explain how I would write the Bind function so that it can take a Person and return a RopValue (like his does)? Also how do I write a switch function which will allow me to connect simple validation functions?

Any other comments on my code are welcome. I'm not sure it's anywhere near as neat and simple as it could be.

like image 485
Avrohom Yisroel Avatar asked Mar 10 '16 23:03

Avrohom Yisroel


2 Answers

Your Bind function has the wrong type, it should be:

public static RopValue<TOut, TFailure> Bind<TSuccess, TFailure, TOut>(this RopValue<TSuccess, TFailure> input, Func<TSuccess, RopValue<TOut, TFailure>> switchFunction) {
  if (input is Success<TSuccess, TFailure>) {
    return switchFunction(((Success<TSuccess, TFailure>)input).Value);
  }
  return new Failure<TOut, TFailure>(((Failure<TSuccess, TFailure>)input).Value);
}

The Func parameter passed to your implementation of Bind takes a RopValue<TSuccess, TFailure> parameter rather than just TSuccess. This means the function needs to repeat the same matching on the input that the Bind method should do for you.

This may be a bit unwieldy due to the number of type parameters so you could move it to the base class:

public abstract RopValue<TOut, TFailure> Bind<TOut>(Func<TSuccess, RopValue<TOut, TFailure> f);

public class Success<TSuccess, TFailure> : RopValue<TSuccess, TFailure> {
    public override RopValue<TOut, TFailure> Bind<TOut>(Func<TSuccess, RopValue<TOut, TFailure> f) {
        return f(this.Value);
    }
}

public class Failure<TSuccess, TFailure> : RopValue<TSuccess, TFailure> {
    public override RopValue<TOut, TFailure> Bind<TOut>(Func<TSuccess, RopValue<TOut, TFailure> f) {
        return new Failure<TOut, TFailure>(this.Value);
    }
}

You can then avoid creating a dummy value at the start of the chain:

private static RopValue<Person, string> Validate(Person person) {
  return CheckName(person)
    .Bind(CheckEmail)
    .Bind(CheckAge);
}
like image 117
Lee Avatar answered Oct 21 '22 21:10

Lee


Lee is correct that your bind function is defined incorrectly.

Bind should always have a type signature that looks like: m<'a> -> ('a -> m<'b>) -> m<'b>

I defined it like this but Lee's is functionally identical:

public static RopValue<TSuccess2, TFailure> Bind<TSuccess, TSuccess2, TFailure>(this RopValue<TSuccess, TFailure> input, Func<TSuccess, RopValue<TSuccess2, TFailure>> switchFunction)
{
    if (input.IsSuccess)
    {
        return switchFunction(((Success<TSuccess,TFailure>)input).Value);
    }
    return new Failure<TSuccess2, TFailure>(((Failure<TSuccess, TFailure>)input).Value);
}

Kleisli Composition (>=>) has type signature that looks like: ('a -> m<'b>) -> ('b -> m<'c>) -> 'a -> m<'c>

You can define that using bind:

public static Func<TSuccess, RopValue<TSuccess2, TFailure>> Kleisli<TSuccess, TSuccess2, TFailure>(this Func<TSuccess, RopValue<TSuccess, TFailure>> switch1, Func<TSuccess, RopValue<TSuccess2, TFailure>> switch2)
{
    return (inp => switch1(inp).Bind(switch2));
}

You can define extension methods on Func but the trick is getting the compiler to see that those extension methods are available, something like this would work:

Func<Entry, RopValue<Request, string>> checkEmail = CheckEmail;
var combined = checkEmail.Kleisli(CheckAge);
RopValue<Request, string> result = combined(request);

Where request is your data to validate.

Notice that by creating a variable of type Func, it allows us to make use of the extension method.

like image 1
TheInnerLight Avatar answered Oct 21 '22 23:10

TheInnerLight