函数式编程之-bind函数

Bind函数

Bind函数在函数式编程中是如此重要,以致于函数式编程语言会为bind函数设计语法糖。另外一个角度Bind函数很是难以理解,几乎不多有人能经过简单的描述说明白bind函数的由来及原理。
这篇文章试图经过“人话”来描述bind函数,并经过浅显的实例为零函数式编程语言的开发者揭秘bind函数的做用及用法。html

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类型重构以下:express

public Optional<string> 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<string>();
}

看起来代码结果跟以前如出一辙,重构后的代码并无变得更漂亮。不过如今的GetFirstThing()/GetSecondThing()/GetThirdThing()方法返回值为Optional<string>类型,再也不是普通的string类型:编程

public Optional<string> GetFirstThing(int id)
{
    //...
    return Optional.None<string>();
}

重构后的这段代码颇有意思,咱们能够从函数组合的角度来让整个代码段变的更加优雅。数组

组合

这段代码其实作了一件事,那就是经过调用三个函数GetFirstThing()/GetSecondThing()/GetThirdThing()来完成一个业务逻辑。从函数式编程思想的角度出发,咱们倾向于把若干个小的函数链接起来,根据之前学过的知识,只有一个输入和一个输出的函数才能链接起来:
他们之因此可以链接是由于这两个函数的签名一致,都拥有一个输入和一个输出。
例如:int -> string, string -> bool就能够组合为int -> bool。
而咱们此时拥有的三个函数方法签名以下:编程语言

GetFirstThing: int -> Optional<string>
GetSecondThing: string -> Optional<string>
GetThirdThing: string -> Optional<string>

显然GetFirstThing和GetSecondThing是没法直接链接的,缘由是GetFirstThing返回了Optional<string>类型,而GetSecondThing的输入倒是一个普通的string类型。若是咱们可以在Optional<T>上扩展一个函数,函数接受一个签名为T -> Optional<T>的函数,那么咱们就有可能将上面的三个函数串联起来:函数式编程

public static class Optional
{
    public static Optional<T> Bind<T>(this Optional<T> input, Func<T, Optional<T>> f)
    {
        if (input.HasValue())
        {
            return f(input.Value);
        }

        return Optional.None<T>();
    }
}

有了上面这个神奇的bind函数你就能够将上面的三个函数链接起来了:函数

public string GetSomething(int id)
{
    return GetFirstThing(id).Bind(GetSecondThing).Bind(GetThirdThing);
}

用F#实现:this

let address = getFirstThing id
    |> bind getSecondThing
    |> bind getThirdThing

经过bind函数咱们成功将三个函数链接了起来, 同时将判空放在了bind函数里,从而保持主要逻辑部分更加线性和清晰。设计

如何编写属于本身的bind函数

  1. 首先须要定义一个泛型类型E<a>,例如咱们上面例子中提到的Optional<T>
  2. 编写属于Optional<T>的bind函数,bind函数的签名为E<a> -> (f: a -> E<b>) -> E<b>。 接收一个E<a>,同时接受一个签名为a -> E<b>的函数,返回E<b>。

List<T>中的bind函数

咱们常常用的List<T>就是一个典型的泛型类型,那他上面有没有bind函数?固然有,不过叫作SelectMany, Scala中也叫flatMap。
看一下SelectMany的方法签名,正好符合bind函数的签名要求:code

public static IEnumerable<TResult> SelectMany<TSource,
TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
{
  //...
}

SelectMany能够用在什么样的场景中?
例若有这样一个场景,一篇文章(paper)能够有若干章节(section)组成,每一个章节(section)又有若干行(row)组成,每行(row)有若干单词(word)组成。

问:给定一篇文章(paper),请找出大于10行(row)的章节(section),里面排除注释的行(row)总共的单词(word)数量。

首先根据需求变下下面的若干函数:

private List<Paper.Section> GetSections(Paper paper)
{
    return paper.Sections.Where(s => s.Rows.Count > 10).ToList();
}

private List<Paper.Section.Row> GetRows(Paper.Section section)
{
    return section.Rows.Where(r=>!r.IsComment).ToList();
}

private List<Paper.Section.Row.Word> GetWords(Paper.Section.Row row)
{
    return row.Words;
}

且看这三个函数的签名:

GetSections: Papaer -> List<Section>
GetRows: Section -> List<Row>
GetWords: Row -> List<Word>

正好这就是就符合bind函数链接的需求:

var length = GetSections(paper)
            .SelectMany(GetRows)
            .SelectMany(GetWords)
            .Count();

F#实现:

let words = getSections paper
    |> bind getRows
    |> bind getWords
 words.Length

bind函数的语法糖支持

bind函数在函数式编程中如此常见,以致于须要设计单独的语法糖,Haskell中叫do natation, Scala中叫for comprehension,F#用Computation expressions

list {
    let! section = getSections(paper)
    let! row = getRows(section)
    let! word = getWord(row)
    return word
}
相关文章
相关标签/搜索