C#中面向铁路的编程 - 如何编写交换机功能?

Avr*_*oel 16 c# f# functional-programming

我一直在关注这篇F#ROP文章,并决定尝试在C#中重现它,主要是为了看看我是否可以.对这个问题的长度表示歉意,但是如果你熟悉ROP,那将很容易理解.

他从一个受F#歧视的联盟开始......

type Result<'TSuccess, 'TFailure> =
  | Success of 'TSuccess
  | Failure of 'TFailure
Run Code Online (Sandbox Code Playgroud)

...我翻译成一个抽象的RopValue类,以及两个具体的实现(注意我已经将类名更改为我更了解的类名)...

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; }
}
Run Code Online (Sandbox Code Playgroud)

我添加了一个静态Create方法,允许您从TSuccess对象创建一个RopValue,它将被送入第一个验证函数.

然后我开始编写绑定函数.F#版本如下......

let bind switchFunction twoTrackInput =
  match twoTrackInput with
  | Success s -> switchFunction s
  | Failure f -> Failure f
Run Code Online (Sandbox Code Playgroud)

...相比C#等同物,这是一个轻而易举的阅读!我不知道是否有更简单的方法来写这个,但这是我想出来的......

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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,我将其作为扩展函数编写,因为这允许我以更实用的方式使用它.

以他验证一个人的用例为例,我写了一个Person类......

public class Person {
  public string Name { get; set; }
  public string Email { get; set; }
  public int Age { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

...并写了我的第一个验证函数......

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;
}
Run Code Online (Sandbox Code Playgroud)

通过几个类似的电子邮件和年龄验证,我可以编写一个整体验证函数,如下所示......

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

这很好用,让我做这样的事......

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

但是,我对这个方法有一些问题.首先,验证函数要求您传入RopValue,检查它是否成功,如果成功,则拉出Person然后验证它.如果失败,只需将其归还.

相比之下,他的验证函数占用了(相当于)一个Person,并返回(结果,相当于)一个RopValue ......

let validateNameNotBlank person =
  if person.Name = "" then Failure "Name must not be blank"
  else Success person
Run Code Online (Sandbox Code Playgroud)

这更简单,但我无法弄清楚如何在C#中执行此操作.

另一个问题是我们使用Success <>启动验证链,因此第一个验证函数将始终从"if"块返回一些内容,如果验证失败则返回Failure <>,如果我们超过则返回Success <>检查.如果函数返回Failure <>,则验证链中的下一个函数永远不会被调用,因此我们知道这些方法永远不能传递给Failure <>.因此,永远无法达到每个函数的最后一行(除了在手动创建失败<>并在开始时传递它的奇怪情况,但这将是毫无意义的).

然后他创建了一个开关操作符(> =>)来连接验证功能.我尝试过这样做,但无法让它发挥作用.为了连续调用函数,看起来我必须在Func <>上有一个扩展方法,我认为你不能这样做.我得到了......

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);
}
Run Code Online (Sandbox Code Playgroud)

...但无法解决如何使用它.

那么,任何人都可以解释我如何编写Bind函数,以便它可以接受一个Person并返回一个RopValue(就像他的那样)?另外我如何编写一个开关功能,这将允许我连接简单的验证功能?

欢迎对我的代码发表任何其他评论.我不确定它是否尽可能整洁和简单.

Lee*_*Lee 6

你的Bind函数类型错误,它应该是:

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);
}
Run Code Online (Sandbox Code Playgroud)

Func传递给你的执行参数Bind需要一个RopValue<TSuccess, TFailure>参数,而不是只TSuccess.这意味着函数需要在Bind方法应该为您执行的输入上重复相同的匹配.

由于类型参数的数量,这可能有点笨拙,因此您可以将其移动到基类:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以避免在链的开头创建虚拟值:

private static RopValue<Person, string> Validate(Person person) {
  return CheckName(person)
    .Bind(CheckEmail)
    .Bind(CheckAge);
}
Run Code Online (Sandbox Code Playgroud)