在编程中,理解代码的执行流程至关重要。通常,习惯于从上到下顺序阅读代码,跟随所有的if-else、try-catch等分支结构。然而,这种阅读方式往往需要理解大量的技术细节,例如null值检查。那么,能否编写一种代码,它能够从左到右顺序阅读,并且原则上不涉及技术细节,就像一系列操作那样直观易懂呢?F#中的管道操作就是这样一种概念。
在F#中,管道操作看起来是这样的:
let squared =
Numbers
|> List.filter isOdd
|> List.map square;;
对象Numbers通过一系列函数进行处理。在C#中,也希望实现类似的功能。
在C#中,可以使用委托来实现类似的功能,但委托不是泛型的。现在,可以使用泛型,同时也希望处理错误和null值。Tomas Petricek和John Skeet的《Real-World Functional Programming》提供了解决方案。关键在于两个方面:Option类型和仅使用一个固定的接口:
Func<Option<T>, Option<T>>
这意味着传入一个函数,它接收一个Option并返回一个Option。这个接口可以无限链式调用或管道化。
Option类型在F#中非常常见。Option可以是None或Some。Some封装了一个实际的业务对象。Continuation是一个将函数的结果传递给另一个函数的概念。如果将这两个概念结合起来,虽然涉及很多理论,但这里不深入讨论。如果将一个对象封装在Option中,然后使用Continuation,就可以像上面展示的AndThen代码那样继续链式调用。Option对象隐藏了它内部的实际类型。
在Option中可以是任何东西。使用Option概念,可以在严格类型语言中以泛型的方式行为。对于Continuation,可以使用Lambda表达式,但类型系统不会允许以泛型的方式实现它们。本文是关于以泛型方式实现Continuation的。
Option类型是一个抽象类,有两个具体实现:None和Some。None结束链式调用,Some封装了[SomeObject]。一个函数必须首先获取封装的对象,对其进行一些处理,然后返回一个None或Some,其中包含封装的对象。Option Some和None类是泛型的!
为了这个Option类型,添加了一些函数,比如上面的AndThenIfElse。如果看参数,会看到需要传入以下两个参数:
Func<Option<T>, Option<T>> tryFirst,
Func<Option<T>, Option<T>> tryNext
添加了:
另一个有趣的要点是,上述continuation的管道不会执行,直到调用Exec()。这样做的原因是,因为异常是在Exec()调用中处理的,并且continuations在第一个None返回时停止。实际上,管道是一个MultiCastDelgates的数组列表,可以序列化。option/Continuation概念也可以在其他场景中使用,例如通过保存直到触发时间来推迟执行。
添加了一个如何在现实世界中使用它的具体例子。代码本身并没有做太多,但给一个很好的想法如何使用它。查看下面的代码(ClientController),可以看到真正的好处:
someClientData.AndThen(isValid)
.AndThen(ClientBusiness.Save)
.AndThenIfElse(ClientBusiness.CheckOnDustin, ClientBusiness.CheckOnNotDustin)
.AndThenCheck(ClientBusiness.ANameLengthCheck, ClientBusiness.GetLastOneInLocality)
.AndThenCaseCheck(ClientBusiness.SomeCaseListOnProfession())
.Exec();