zoukankan      html  css  js  c++  java
  • 【函数式】Monads模式初探——for解析式

    for表达式是monad语法糖

    先看一组演示样例:

    case class Person(name: String, isMale: Boolean, children: Person*)
    
    val lara = Person("Lara", false)
    val bob = Person("Bob", true)
    val julie = Person("Julie", false, lara, bob)
    val persons = List(lara, bob, julie)
    
    println(
      persons filter (p => !p.isMale) flatMap (p =>
        (p.children map (c => (p.name, c.name))))
    )
    
    println(
      for (p <- persons; if !p.isMale; c <- p.children)
        yield (p.name, c.name)
    )
    // output is
    // List((Julie,Lara), (Julie,Bob))

    Person类包括了人员名称。是否是男性,以及他的孩子的字段。代码的意义是找出列表中全部的妈妈和孩子结对的名称。
    分别使用了map、flatMap、filter的方式进行查询,还使用了for表达式完毕。得到相同的结果。

    实际上,Scala编译器能够把全部使用yield产生结果的for表达式转移为高阶方法map、flatMap及filter的组合调用

    全部的不带yield的for循环都会被转移为仅对filter和foreach的调用。

    for表达式说明

    for表达式形式例如以下:
    for (seq) yield expr
    这里。seq由生成器、定义及过滤器组成序列,以分号隔开。假设在for表达式中用花括号取代小括号包围表达式序列,那么分号是可选的。
    比方以下的演示样例:

    for (p <- persons; n = p.name; if (n startsWith "To"))
      yield n
    
    for {
      p <- persons            //生成器
      n = p.name              //定义
      if (n startsWith "To")  //过滤器
    } yield n

    生成器的形式为patten <- expression,表达式expression典型的返回值是列表,只是它能够泛化。模式pattern一一匹配列表里的全部元素。假设匹配成功,模式中的变量将绑定元素的对应成分。

    但即使匹配失败也不会抛出MatchError。而仅仅是在迭代中丢弃这个元素罢了。
    全部的for表达式都以生成器開始。

    假设for表达式中有若干生成器,那么后面的生成器比前面的变化的更快。

    for表达式的转译

    对于每个Monad来说,都支持for表达式,而每个for表达式都能够用三个高阶函数map、flatMap及filter表达。

    主要的转译方式

    • 带一个生成器的for表达式
      for (x <- expr1) yield expr2转译为expr1.map(x => expr2)
    • 以生成器和过滤器開始的for表达式
      for (x <- expr1 if expr2) yield expr3
      第一个表达式能够转译成for (x <- expr1 filter (x => expr2)) yield expr3
    • 以两个生成器開始的for表达式
      for (x <- expr1; y <- expr2; seq) yield expr3
      假设seq是随意序列的生成器、定义及过滤器。也可能为空。

      两个生成器被转译为flatMap的应用:
      expr1.flatMap(x => for (y <- expr2; seq) yield expr3 )
      这就生成了还有一个传递给flatMap的函数值形式的for表达式。

    再举个样例:

    // 第一步转译
    for (n <- ns;
        o <- os;
        p <- ps)
        yield n*o*p
    // 第二步转译
    ns flatMap {n =>
              for(o <- os;
              p <- ps)
              yield n*o*p}
    // 第三步转译
    ns flatMap { n =>
              os flatMap { o =>
              for(p <- ps)
              yield n*o*p}}
    // 第四步转译
    ns flatMap {n =>
              os flatMap {o =>
              {ps map {p => n*o*p}}}}

    转译for循环

    for表达式也有一个命令式(imperative)的版本号,用于那些你仅仅调用一个函数,不返回不论什么值而仅仅执行了副作用,这个版本号去掉了yield声明。


    for循环的转译版本号仅仅需用到foreach。for (x <- expr1) body,转译为expr1 foreach (x => body)
    更大的样例是。for (x <- expr1; if expr2; y <- expr3) body。它将被转译为:

    expr1 filter (x => expr2) foreach (x =>
      expr3 foreach (y => body))

    foreach依旧能够使用map来实现:

    class M[A] {
      def map[B](f: A => B): M[B] = ...
      def flatMap[B](f: A => M[B]): M[B] = ...
      def foreach[B](f: A => B): Unit = {
        map(f)
        ()
      }
    }

    foreach能够通过调用map并丢掉结果来实现。只是这么做执行效率不高。所以scala同意你用自己的方式定义foreach。

    转译定义

    假设for表达式中内嵌定义,如for (x <- expr1; y = expr2; seq) yield expr3
    那么将转译为for ((x, y) <- for (x <- expr1) yield (x, expr2); seq) yield expr3


    这里每次产生新的x值的时候,expr2都被又一次计算。所以这可能会浪费计算资源。造成反复计算。
    比方以下的样例和更好的写法:

    for (x <- 1 to 100; y = expensiveComputationNotInvolvingX)
    yield x*y
    
    // better code
    val y = expensiveComputationNotInvolvingX
    for (x <- 1 to 1000) yield x*y

    生成器中的模式

    假设生成器的左側是模式pat而不是简单变量,那么转译方法将变得复杂非常多。


    绑定变量元组
    for ((x1, ..., xn) <- expr1) yield expr2
    转译为:
    expr1.map {case (x1, ..., xn) => expr2}

    随意模式
    for (pat <- expr1) yield expr2
    转译为:

    expr1 filter {
      case pat => true
      case _ => false
    } map {
      case pat => expr2
    }

    即。生成的条目首先经过过滤而且仅有那些匹配与pat的才会被映射。因此。这保证了模式匹配生成器不会抛出MatchError。

    小结

    由于for表达式的转译仅依赖于map、flatMap和filter的搭配,所以能够吧for表达式应用于大批数据类型(这些数据类型能够用Monad来描写叙述和概括)上。
    除了列表、数组之外,Scala标准库中还有更多类型支持四种方法(map、flatMap、filter、foreach),从而同意for表达式存在。相同。假设你自己的数据类型定义了须要的方法也能够完美支持for表达式。

    假设仅仅定义map、flatMap、filter、foreach这些方法的子集,从而部分支持for表达式或循环。
    规则例如以下:

    • 假设定义了map,能够同意单一生成器组成的for表达式
    • 假设定义了flatMap和map,能够同意若干个生成器组成的for表达式
    • 假设定义了foreach,同意for循环
    • 假设定义了filter,for表达式中同意以if开头的过滤器表达式

    for表达式的转译发生在类型检查之前。这能够保持最大的灵活性。由于接下来仅仅需for表达式展开的结果通过类型检查就可以。

    在函数式编程中,Monad定制了map、flatMap和filter功能,它能够解释多种类型的计算,包括从集合、状态和I/O操纵的计算、回溯计算以及交易等,不一而足。

    转载请注明作者Jason Ding及其出处
    jasonding.top
    Github博客主页(http://blog.jasonding.top/)
    CSDN博客(http://blog.csdn.net/jasonding1354)
    简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)
    Google搜索jasonding1354进入我的博客主页

  • 相关阅读:
    localdatetime获取本月第一天及最后一天
    java线程池ThreadPoolExecutor类使用详解
    yapiideaupload
    select count(*)和select count(1)的区别
    pgsql upsert语法
    easyui的datagrid里getSelections只能获取一行值???
    vue.js 外部配置文件(参考)
    PostgreSQL ROW_NUMBER() OVER()
    echart
    vue拼接html中onclick的触发方式,vue中的onclick,vue触发onclick,vue拼接html
  • 原文地址:https://www.cnblogs.com/lxjshuju/p/7396275.html
Copyright © 2011-2022 走看看