【函数式】Monads模式初探——for解析式

时间:2023-01-26 12:27:56

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进入我的博客主页

【函数式】Monads模式初探——for解析式的更多相关文章

  1. 【函数式】Monads模式初探——Endofunctor

    自函子 自函子(Endofunctor)是一个将范畴映射到自身的函子(A functor that maps a category to itself). 函子是将一个范畴转换到另一个范畴.所以自函子 ...

  2. Go语言设计模式之函数式选项模式

    Go语言设计模式之函数式选项模式 本文主要介绍了Go语言中函数式选项模式及该设计模式在实际编程中的应用. 为什么需要函数式选项模式? 最近看go-micro/options.go源码的时候,发现了一段 ...

  3. Java8之旅&lpar;七&rpar; - 函数式备忘录模式优化递归

    前言 在上一篇开始Java8之旅(六) -- 使用lambda实现Java的尾递归中,我们利用了函数的懒加载机制实现了栈帧的复用,成功的实现了Java版本的尾递归,然而尾递归的使用有一个重要的条件就是 ...

  4. Java8函数之旅 &lpar;七&rpar; - 函数式备忘录模式优化递归

    前言 在上一篇开始Java8之旅(六) -- 使用lambda实现Java的尾递归中,我们利用了函数的懒加载机制实现了栈帧的复用,成功的实现了Java版本的尾递归,然而尾递归的使用有一个重要的条件就是 ...

  5. Scalaz(10)- Monad:就是一种函数式编程模式-a design pattern

    Monad typeclass不是一种类型,而是一种程序设计模式(design pattern),是泛函编程中最重要的编程概念,因而很多行内人把FP又称为Monadic Programming.这其中 ...

  6. &period;Net平台-MVP模式初探&lpar;一&rpar;

    为什么要写这篇文章 笔者当前正在负责研究所中一个项目,这个项目基于.NET平台,初步拟采用C/S部署体系,所以选择了Windows Forms作为其UI.经过几此迭代,我们发现了一个问题:虽然业务逻辑 ...

  7. Android开发之模板模式初探

    模板模式我认为在Android的开发中是最长用到的,基本是随处可见的,认识该模式,有助于我们对Android的源代码及框架有一个更深层次的认识.那什么是模板模式呢,模板模式就是定义一个基本框架,将当中 ...

  8. Android开发之Buidler模式初探结合AlertDialog&period;Builder解说

          什么是Buidler模式呢?就是将一个复杂对象的构建与它的表示分离,使得相同的构建过程能够创建不同的表示.Builder模式是一步一步创建一个复杂的对象,它同意用户能够仅仅通过指定复杂对象 ...

  9. 菜鸟vimer成长记——第2&period;0章、模式初探

    首先,其他的文本编辑器只有一种模式,就是插入模式.而vim一下子颠覆了我们的世界观——有好多模式.这个是思维上的切换,很难也很重要!!! 其次,Vim 提供一个区分模式的用户界面.也就是说在不同的模式 ...

随机推荐

  1. &period;NET缓存框架CacheManager在混合式开发框架中的应用(1)-CacheManager的介绍和使用

    在我们开发的很多分布式项目里面(如基于WCF服务.Web API服务方式),由于数据提供涉及到数据库的相关操作,如果客户端的并发数量超过一定的数量,那么数据库的请求处理则以爆发式增长,如果数据库服务器 ...

  2. Kanzi编程基础1 - 定时器Timer

    Kanzi虽然发生了比较多的版本更迭,api也发生了很多变化,但定时器的头文件一直都在一个地方:#include "user/include/user/ui/message/kzu_mess ...

  3. java直接打开pdf&comma;doc&comma;xls

    jsp页面: <a href=\'#\' onclick=onLine(\''+urls[i]+'\') >在线打开</a> html页面超链接单击打开online函数 var ...

  4. 关于 WP上应用调试时报错&OpenCurlyDoubleQuote;指定的通信资源(端口)”已由另一个应用程序使用 问题

    问题来源 碰到这个问题是调试wp7项目的时候,之前因为安装的是wp8.0的sdk 包括wp7.5所以wp7的也能用,后来不知道怎么回事wp7项目就不能调试了总是显示启动而不是 device或者是虚拟机 ...

  5. Android开源项目(一)

    Android开源项目(一) GitHub在中国的火爆程度无需多~~,越来越多的开源项目迁移到GitHub平台上.更何况,基于不要重复造*的原则~~~~了解当下比较流行的Android与iOS开源项 ...

  6. 13&period;怎样自学Struts2之Struts2本地化&lbrack;视频&rsqb;

    13.怎样自学Struts2之Struts2本地化[视频] 之前写了一篇"打算做一个视频教程探讨怎样自学计算机相关的技术",优酷上传不了,仅仅好传到百度云上: http://pan ...

  7. JS通用方法扩展

    /* * 系统中JS的扩展函数 * * */ // 清除两边的空格 String.prototype.trim = function() { returnthis.replace(/(^\s*)|(\ ...

  8. Android初级教程实现电话录音

    需求:设置来电后自动录音. 首先设置一个按钮,代码很简单这里就不再给出. 建一个类,RecorderServicer extends Service package com.ydl.recorder; ...

  9. &lbrack;Oracle&rsqb;&lbrack;Corruption&rsqb;发生ORA00600&lbrack;kdsgrp1&rsqb;的时候,如何进行调查

    本质上,这很可能是坏块引发的,所以需要调查 关联的Table 中的坏块状况: Excerpt of trace file============================*** 2017-08- ...

  10. &lpar;一&rpar; 关于配置travis-ci持续集成python pytest测试的相关记录

    首先由于公司用上了高大上的travis-ci商用版,一直想试着学学弄弄看.现在要写openapi的相关测试,而且要在travis-ci上集成.我就想体验一下这个过程.所以自己弄了一个public的仓库 ...