我一直关注this F# ROP article,并决定尝试在C#中重现它,主要是为了看看我是否可以。对这个问题的篇幅深表歉意,但是如果你熟悉ROP,那么就很容易理解。
他开始时有一个F#歧视联盟......
type Result<'TSuccess, 'TFailure> =
| Success of 'TSuccess
| Failure of 'TFailure
...我将其翻译成一个抽象的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; }
}
我添加了一个静态Create方法,允许您从TSuccess对象创建一个RopValue,它将被送入第一个验证函数。
然后我开始编写绑定函数。 F#版本如下......
let bind switchFunction twoTrackInput =
match twoTrackInput with
| Success s -> switchFunction s
| Failure f -> Failure f
...与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;
}
请注意,我将此作为扩展函数编写,因为这允许我以更实用的方式使用它。
根据他验证某个人的用例,我写了一个人类......
public class Person {
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
...并写了我的第一个验证函数......
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;
}
通过几个类似的电子邮件和年龄验证,我可以编写一个整体验证函数,如下所示......
private static RopValue<Person, string> Validate(Person person) {
return RopValue<Person, string>
.Create<Person, string>(person)
.Bind(CheckName)
.Bind(CheckEmail)
.Bind(CheckAge);
}
这很好用,让我可以这样做......
Person jim = new Person {Name = "Jim", Email = "", Age = 16};
RopValue<Person, string> jimChk = Validate(jim);
Debug.WriteLine("Jim returned: " + (jimChk.IsSuccess() ? "Success" : "Failure"));
但是,我对我这样做的方式有一些问题。首先,验证函数要求您传入RopValue,检查它是否成功,如果成功,则拉出Person然后验证它。如果失败,只需退回。
相比之下,他的验证函数占用了(相当于)一个Person,并返回(一个结果,相当于)一个RopValue ......
let validateNameNotBlank person =
if person.Name = "" then Failure "Name must not be blank"
else Success person
这更简单,但我无法弄清楚如何在C#中执行此操作。
另一个问题是我们使用Success&lt;&gt;启动验证链,因此第一个验证函数将始终返回&#34; if&#34;阻止,失败&lt;&gt;如果验证失败,或者成功&lt;&gt;如果我们通过检查。如果函数返回Failure&lt;&gt;,那么验证链中的下一个函数永远不会被调用,因此我们知道这些方法永远不能传递给Failure&lt;&gt;。因此,永远无法达到每个函数的最后一行(除了在手动创建失败&lt;&gt;并在开始时传递它的奇怪情况,但这将是毫无意义的。)
然后,他创建了一个开关操作符(&gt; =&gt;)来连接验证功能。我尝试过这样做,但无法让它发挥作用。为了连续调用该函数,看起来我必须在Func&lt;&gt;上有一个扩展方法,我认为你无法做到。我得到了这个......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);
}
...但无法解决如何使用它。
那么,任何人都可以解释我如何编写Bind函数以便它可以接受一个Person并返回一个RopValue(就像他的那样)?另外我如何编写一个允许我连接简单验证函数的开关函数?
欢迎对我的代码提出任何其他意见。我不确定它是否尽可能简洁明了。
答案 0 :(得分: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);
}
传递给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);
}
}
然后,您可以避免在链的开头创建虚拟值:
private static RopValue<Person, string> Validate(Person person) {
return CheckName(person)
.Bind(CheckEmail)
.Bind(CheckAge);
}
答案 1 :(得分:1)
李是正确的,你的绑定功能定义不正确。
绑定应始终具有类似于:m<'a> -> ('a -> m<'b>) -> m<'b>
我这样定义,但李的功能相同:
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(>=>
)的类型签名如下:('a -> m<'b>) -> ('b -> m<'c>) -> 'a -> m<'c>
您可以使用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));
}
您可以在Func
上定义扩展方法,但诀窍是让编译器看到这些扩展方法可用,这样的方法可行:
Func<Entry, RopValue<Request, string>> checkEmail = CheckEmail;
var combined = checkEmail.Kleisli(CheckAge);
RopValue<Request, string> result = combined(request);
request
是您要验证的数据。
请注意,通过创建Func
类型的变量,它允许我们使用扩展方法。