函数式编程之-bind函数

目录 组合 如何编写属于自己的bind函数 List中的bind函数 bind函数的语法糖支持 Bind函数 Bind函数在函数式编程中是如此重要,以至于函数式编程语言会为bind函数设计语法糖。另一个角度Bind函数非常难以理解,几乎很少有人能通过简单的描述说明白bind函数的由来及原理。 这篇文章试图通过“人话”来描述bind函数,并通过浅显的实例为零函数式编程语言的开发者揭秘bind函数的作用及用法。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public string GetSomething(int id) { var x = GetFirstThing(id); if (x != null) { var y = GetSecondThing(x); if(y != null) { var z = GetThirdThing(y); if (z != null) { return z; } } } return null; } 你一定写过类似的代码,估计你也明白这样的代码看起来很丑陋,一层层的判空嵌套打乱了代码的主题结构。 有没法让他变的更优雅?当然你可以通过"early return"的做法,不过这种方式不在我们的讨论范围之内。 这种风格的代码存在一个明显的code smell, GetFirstThing()/GetSecondThing()/GetThirdThing()等方法有可能返回null,我们说return null是一种不真确的做法,相关分析见拒绝空引用异常。使用Optional类型重构如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public Optional GetSomething(int id) { var x = GetFirstThing(id); if (x.HasValue()) { var y = GetSecondThing(x); if(y.HasValue()) { var z = GetThirdThing(y); if (z.HasValue()) { return z; } } } return Optional.None(); } 看起来代码结果跟之前一模一样,重构后的代码并没有变得更漂亮。不过现在的GetFirstThing()/GetSecondThing()/GetThirdThing()方法返回值为Optional类型,不再是普通的string类型: 1 2 3 4 5 public Optional GetFirstThing(int id) { //... return Optional.None(); } 重构后的这段代码很有意思,我们可以从函数组合的角度来让整个代码段变的更加优雅。 组合 这段代码其实做了一件事,那就是通过调用三个函数GetFirstThing()/GetSecondThing()/GetThirdThing()来完成一个业务逻辑。从函数式编程思想的角度出发,我们倾向于把若干个小的函数连接起来,根据以前学过的知识,只有这样的两个函数才能连接: 图 他们之所以能够连接是因为这两个函数的签名一致,都拥有一个输入和一个输出。 例如:int -> string, string -> bool就可以组合为int -> bool。 而我们此时拥有的三个函数方法签名如下: 1 2 3 GetFirstThing: int -> Optional GetSecondThing: string -> Optional GetThirdThing: string -> Optional 显然GetName和GetEmail是无法直接连接的,原因是GetFirstThing返回了Optional类型,而GetSecondThing的输入却是一个普通的string类型。如果我们能够在Optional上扩展一个函数,函数接受一个签名为T -> Optional的函数,那么我们就有可能将上面的三个函数串联起来: 1 2 3 4 5 6 7 8 9 10 11 12 public static class Optional { public static Optional Bind(this Optional input, Func> f) { if (input.HasValue()) { return f(input.Value); } return Optional.None(); } } 有了上面这个神奇的bind函数你就可以将上面的三个函数连接起来了: 1 2 3 4 public string GetSomething(int id) { return GetFirstThing(id).Bind(GetSecondThing).Bind(GetThirdThing); } 用F#实现: 1 2 3 let address = getFirstThing id |> bind getSecondThing |> bind getThirdThing 通过bind函数我们成功将三个函数连接了起来, 同时将判空放在了bind函数里,从而保持主要逻辑部分更加线性和清晰。 如何编写属于自己的bind函数 首先需要定义一个泛型类型E,例如我们上面例子中提到的Optional 编写属于Optional的bind函数,bind函数的签名为E -> (f: a -> E) -> E。 接收一个E,同时接受一个签名为a -> E的函数,返回E。 List中的bind函数 我们经常用的List就是一个典型的泛型类型,那他上面有没有bind函数?当然有,不过叫做SelectMany, Scala中也叫flatMap。 看一下SelectMany的方法签名,正好符合bind函数的签名要求: 1 2 3 4 5 public static IEnumerable SelectMany(this IEnumerable source, Func> selector) { //... } SelectMany可以用在什么样的场景中? 例如有这样一个场景,一篇文章(paper)可以有若干章节(section)组成,每个章节(section)又有若干行(row)组成,每行(row)有若干单词(word)组成。 问:给定一篇文章(paper),请找出大于10行(row)的章节(section),里面排除注释的行(row)总共的单词(word)数量。 首先根据需求变下下面的若干函数: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 private List GetSections(Paper paper) { return paper.Sections.Where(s => s.Rows.Count > 10).ToList(); } private List GetRows(Paper.Section section) { return section.Rows.Where(r=>!r.IsComment).ToList(); } private List GetWords(Paper.Section.Row row) { return row.Words; } 且看这三个函数的签名: 1 2 3 GetSections: Papaer -> List
GetRows: Section -> List GetWords: Row -> List 正好这就是就符合bind函数连接的需求: 1 2 3 4 var length = GetSections(paper) .SelectMany(GetRows) .SelectMany(GetWords) .Count(); F#实现: 1 2 3 4 let words = getSections paper |> bind getRows |> bind getWords words.Length bind函数的语法糖支持 bind函数在函数式编程中如此常见,以至于需要设计单独的语法糖,Hask中叫do natation, Scala中叫for comprehension,F#用Computation expressions: 1 2 3 4 5 6 list { let! section = getSections(paper) let! row = getRows(section) let! word = getWord(row) return word } 如果您觉得本文对你有用,不妨帮忙点个赞,或者在评论里给我一句赞美。我们正在使用.NET Core / Azure / Xamarin等技术开发基于互联网和移动应用平台上的各种新产品和商业服务。我们的目标是通过举办各种分享活动,交流开发心得和经验来推动.NET技术栈在西安乃至西北地区的发展。我们是一个开发和自由的社区,欢迎您加入西安.NET社区,微信账号:dotNetXA 欢迎您持续关注我的博客 文章出处:.NET西安社区 版权所有,欢迎保留原文链接进行转载:)https://www.cnblogs.com/xiandnc/p/9684271.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信