在我们C#之中,一直有一个与C#风格格格不入的语法,也就是LINQ的查询表达式,如果你没见过的话,这是他的写法
int[] numbers = [ 0, 1, 2, 3, 4, 5, 6 ];
var numQuery = from num in numbers
where (num % 2) == 0
select num;

这怎么长的和SQL语句似的?
是的,查询语法其中之一的目的就是为了与SQL保持更一致的查询体验。不过还是有很多人更喜欢扩展方法式的写法
int[] numbers = [ 0, 1, 2, 3, 4, 5, 6 ];
var numQuery = numbers.Where(num => num % 2 == 0);
非常简洁好懂,更多C#程序员会喜欢扩展方法的LINQ也很合理。
查询表达式在C#代码中可能会显得异常突出,不和谐,不过其强大的查询能力依然非常有用,除此之外
查询表达式其实还另有妙用。
(在开始之前,请安装LanguageExt库)
上一期我们介绍了应用式, 学习了如何利用应用式,处理高级界域下的多参函数,不过其实我们同时也介绍了一种,通过单子流(嵌套的bind)来实现的方式,不过那种方式由于复杂的嵌套,不被我们喜欢。
但是若是说我们有办法避免这个嵌套呢?
事实上from对应的是Linq中的selectmany方法,而select则当然对应的是select方法。我们可以写出这样那样的代码
int[] seq = [1, 2, 3, 4, 5];
var seqEven =
from x in seq // selectmany
from x2 in seq // selectmany
from x3 in seq // selectmany
from x4 in seq // selectmany
where x % 2 == 0 // where
select (x + 1, 2); // select
这段代码的from其实就类似以下实现
var seqEven1 = seq
.SelectMany(x => seq.SelectMany(x2 => seq));
在最之前我们说过,bind与selectmany的函数签名是完全一致的!
那么事实上,我们当然也可以对bind做类似的事情。这下我们回头一看发现,查询表达式的语法,居然是不受语法嵌套限制的!这就弥补了我们之前不满意的一点,我们可以将代码写成
var v4 =
from x in GetSome(6)
from y in GetSome(x - 3)
select Add(x, y);
int Add(int a, int b) => a + b;
Either<string, int> GetSome(int value)
{
Console.WriteLine("+++" + value);
return value > 4 ? value : "Too Small";
}
对比原来的写法
var v1 = GetSome(1);
var v2 = GetSome(6);
// v1 + v2 使用Add函数
var v3 = v1
.Bind(v1v => // SelectMany
v2.Map(v2v => v1v + v2v) // Select
);
曾经麻烦的嵌套bind在查询表达式的协助下变得非常优雅,我们可以轻易的取出其中的内容,并在有失败内容的时候整个表达式就会快速失败(与之前的bind行为一致),我们称之为单子流
在普通的编程模式中,我们遇到的比较多的高级值可能就是类似数组这类,大多数时候我们用Linq方法就能轻松解决其查询问题,但是在函数式编程中我们将会运用到一堆的高级值的时候,查询表达式的优点就被明显的放大了!这就是查询表达式语法的妙用(不过在F#中 我们拥有计算表达式可以更好的实现这一点)
那么应用式与单子流有什么差异呢,如果我们使用查询表达式的单子流似乎把原有应用式一些仅有的优点都覆盖了?
事实上还是有区别的
应用式中所有的参数计算已经完成,可以只是单纯的组合。并且参数也可以分别的并行的计算出来,较为独立,不能互相依赖,但是可以一次性收集所有参数的错误。(这在后面验证器中可能会非常有用)
而单子流,(正如代码所示)可以依赖于上一个计算的结果,并且一定程度上不依赖于柯里化,不过也由于其快速失败的特性,所以我们没法方便的收集到所有可能的问题。
这下我们就了解了函数式编程中两种应对多参函数的方法,掌握他们,让他们在正确的地方上发挥自己的作用吧!
学会了吗?学会了
微信公众号: @scixing的炼丹房

922

被折叠的 条评论
为什么被折叠?



