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

标签 c# f# functional-programming

我一直在关注 this F# ROP article ,并决定尝试在 C# 中重现它,主要是看看我是否可以。对于这个问题的长度,我们深表歉意,但如果您熟悉 ROP,将很容易理解。

他从 F# discriminated union 开始......

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,该对象将被送入第一个验证函数。

然后我着手编写一个绑定(bind)函数。 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;
}

请注意,我将其写为扩展函数,因为这使我能够以更实用的方式使用它。

根据他验证一个人的用例,我编写了一个 Person 类...

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,并返回(一个 Result,相当于)一个 RopValue...

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

这要简单得多,但我不知道如何在 C# 中执行此操作。

另一个问题是我们以 Success<> 开始验证链,因此第一个验证函数将始终从“if” block 返回一些内容,如果验证失败则返回 Failure<>,如果验证失败则返回 Success<>我们通过了检查。如果一个函数返回 Failure<>,那么验证链中的下一个函数将永远不会被调用,所以事实证明我们知道这些方法永远不能传递 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);
}

...但不知道如何使用它。

那么,谁能解释一下我将如何编写 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);
}

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

关于c# - C# 中面向铁路的编程 - 如何编写开关函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35929508/

相关文章:

c# - 如何在 C# 或 F# 中使用 Travis-CI

F#:InvalidOperationException:输入序列的元素数量不足?

javascript - 我如何将对象传递给对象的属性?

haskell - 如何将函数元组转换为发出元组的函数

c# - 如何使用诺基亚手机从 C# 应用程序发送/接收 SMS 消息

c# - 使用 XML 序列化序列化没有父节点的集合

c# - 如何使用 protobuf-net 代理序列化继承链中的类型?

generics - 通用参数上的 F# 模式匹配

c# - javascript : json object with arrays serialized for use in . NET MVC 操作

javascript - 在 Electron 应用程序中嵌入其他程序