面向铁路的编程c# -如何编写开关函数?

时间:2021-11-20 00:07:58

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.

我一直在关注这篇f# ROP文章,并决定尝试在c#中复制它,主要是为了看看我是否可以。对于这个问题的长度表示歉意,但是如果您熟悉ROP,它将非常容易理解。

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)...

…我将其转换为一个抽象的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; }
}

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.

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

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

然后我开始写一个绑定函数。f#版本如下…

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...

…与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;
}

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...

利用他验证一个人的用例,我写了一个person类……

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.

然而,我对我的方法有一些问题。首先,验证函数要求您传入RopValue,检查它是否成功或失败,如果成功,取出Person,然后验证它。如果失败,就返回它。

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

相比之下,他的验证函数取(相当于)一个人,然后返回(结果,相当于)一个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#.

这要简单得多,但是我不知道如何在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).

另一个问题是,我们以<>成功启动验证链,因此第一个验证函数总是从“if”块返回一些东西,如果验证失败,则返回一个失败<>;如果通过检查,则返回一个成功<>。如果一个函数返回Failure<>,那么验证链中的下一个函数就不会被调用,因此我们知道这些方法永远不会被传递为失败<>。因此,无法到达这些函数的最后一行(除了手工创建一个失败<>并在开始时将其传递出去的奇怪情况之外)。

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...

然后他创建了一个开关操作符(>=>)来连接验证函数。我试过这么做,但没能成功。为了对函数进行连续调用,我需要在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);
}

...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?

那么,谁能解释一下我是如何编写Bind函数的,这样它就可以获取一个Person并返回一个RopValue(就像他那样)?另外,如何编写一个开关函数来连接简单的验证函数?

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

欢迎对我的代码提出任何其他意见。我不确定它是否像它可能的那样简洁。

2 个解决方案

#1


5  

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.

传递给绑定实现的Func参数接受RopValue 参数,而不仅仅是TSuccess。这意味着函数需要对输入重复绑定方法应该为您做的相同匹配。 、tfailure>

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);
}

#2


1  

Lee is correct that your bind function is defined incorrectly.

Lee是正确的,您的绑定函数定义错误。

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

绑定的类型签名应该是: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>

Kleisli Composition(>=>)具有如下类型签名:('a -> m<'b>) -> ('b -> m<'c>) -> 'a -> m<'c>

You can define that using bind:

您可以使用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上定义扩展方法,但是关键是让编译器看到这些扩展方法是可用的,类似这样的方法可以工作:

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.

注意,通过创建Func类型的变量,它允许我们使用扩展方法。

#1


5  

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.

传递给绑定实现的Func参数接受RopValue 参数,而不仅仅是TSuccess。这意味着函数需要对输入重复绑定方法应该为您做的相同匹配。 、tfailure>

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);
}

#2


1  

Lee is correct that your bind function is defined incorrectly.

Lee是正确的,您的绑定函数定义错误。

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

绑定的类型签名应该是: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>

Kleisli Composition(>=>)具有如下类型签名:('a -> m<'b>) -> ('b -> m<'c>) -> 'a -> m<'c>

You can define that using bind:

您可以使用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上定义扩展方法,但是关键是让编译器看到这些扩展方法是可用的,类似这样的方法可以工作:

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.

注意,通过创建Func类型的变量,它允许我们使用扩展方法。