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(就像他的那样)?另外我如何编写一个开关功能,这将允许我连接简单的验证功能?
欢迎对我的代码发表任何其他评论.我不确定它是否尽可能整洁和简单.
你的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)