完成了对Free Monad这部分内容的学习了解后,心头豁然开朗,存在心里对FP的疑虑也一扫而光。之前也抱着跟大多数人一样的主观概念,认为FP只适合学术性探讨、缺乏实际应用、运行效率低,很难发展成现实的软件开发模式。Free Monad的出现恰恰解决我心中的疑问,更正了我对FP的偏见:Free Monad提供了一套在Monad 算法内(在 for-comprehension内)的行令编程(imperative programming)方法,解决了FP的复杂语法,使Monadic编程更贴近传统编程模式的习惯和思维,程序意图更容易理解。Free Monad的函数结构化(reification)有效解决了递归算法造成的堆栈溢出(stackoverflow)问题,使FP程序能够安全运行,实现在现实中的应用。

在学习scalaz初期,FP的类型和函数施用搞得我很无奈,不适应:FP类型的Functor,Applicative,Monad等等给我的印象是无比抽象的。而且接触到的有关这些类型的具体使用例子又大多数是针对List,Option,Map这些教科书通用类型的,感觉FP就是一种对编程模式的学术探讨,是用来改变思想的,没什么实用价值。当然,FP的递归算法又更加深了我们对现实中选用它的疑虑。但从Free Monad反向回顾scalaz的这些基础类型和函数,我好像渐渐地明白了它们在scalaz这个FP工具库中存在的意义。回到我了解scalaz的目的:就是希望证实FP这种模式可以成为一种生产工具。之前已经了解了FP模式的优势但对于它的实际应用还是存有疑虑。以我粗浅的标准来讲,如果作为一种实际可用的编程语言,起码必须具备以下几点:

1、语法简单,容易掌握

2、表达式简洁、直白

3、能够保证运行安全

试想我们如何能长期的编写fa.flatMap(a => fb.flatMap(b => fc.map(...)))这样的程序呢?FP针对泛函结构F[A]的运算有着一套全新的数据结构和函数施用方式,没人能明白这样的程序表达的到底是什么目的。这时我们遇到了flatMap函数的方法糖for-comprehension,它可用让我们在一个for-loop里进行我们熟悉的行令式编程,就像下面这样:

for {
x <- getRecNo
r <- getRecord(x)
_ <- r.save()
} yield ()

除去for-yield后不就是我们熟悉的编程方式吗?我们已经习惯并掌握了这种编程方式。因为flatMap是Monad的运算函数,所以FP式的编程又被称为Monadic Programming,直白来讲就是用Monad来编程,或者就是在一个Monad壳子(context)里编程。可以说scalaz的所有东西最终都和Monad有关(everything is about Monad)。通过证明,任何Monad都必须是Functor和Applicative,所以在scalaz里提供的Functor,Applicative以及其它的基础typeclass并不如我们想象的那样好像没什么实用价值,实际上scalaz是通过这些基础typeclass为我们构建各种功能的Monad提供了支持的。现在看来这些基础typeclass还是值得了解的。而且看来如果要进行FP编程,就必须先掌握Monad应用,因为我们需要把所有东西都升格成Monad。那么Monad真的像许多人感觉的那样神秘、虚渺、触不可及吗?答案是否定的。接触的多了我们就可以了解Monad的主要作用就是把一个算法,无论是一个值或者一个函数升格成Monad,这样我们就可以在Monad-for-comprehension里使用它们了。看看scalaz里一些类型的Monad格式吧:

case class State (run: S => (A,S))
case class Reader(run: A => B)
case class Writer(run: (W, A))
...

它们都是把普通的函数或者运算包嵌在一个结构里然后在实现这个类型的flatMap函数时体现这些运算的具体意义。这些道理在scalaz的源代码里都可以得到证实。所以我们根本不需要畏惧Monad,应该采取积极态度去充分了解掌握它。我印象中比较麻烦的是Monad转换和功能结合,它们都涉及到类型匹配,需要较大的想象空间。

好了,有了Monad和各种功能转换、集合方式,我们可以在for-comprehension里进行熟悉的编程了。那么会不会出现在一个for-loop里出现几百行指令的情况呢?我认为不会,因为我们可以用函数组合方式把一个大程序分解成各种功能单一的简单函数,然后逐层进行组合,最终的程序最多也就是十几二十行。这种组合特性有赖于Free Monad提供的算式/算法关注分离(program/interpret separation of concern)模式。它可以把影响函数组合的副作用放到算法(interpret)阶段,让我们能够在算式中实现程序间的组合。这个我用以下的代码来示范一下:

val prgGetData = for {
x <- getRecNo
r <- getRecord(x)
} yield r val prgUpdateRecord = for {
x <- getData
r <- prgGetData
- <- r.updateAndSave()
} yield () prgUpdateRecord.run(dbActions)

再有一个问题就是FP的运算方式了:我们可以看到运算一连串的flatMap是一种递归算法,除非使用尾递归算法,compiler是无法对算法进行优化的,那么运算flatMap就很容易会发生堆栈溢出错误(stackoverflow error),无法保障程序运行安全。Free Monad是通过函数结构化,既是把flatMap函数作为一种数据存放在heap内存上,然后通过折叠算法逐个运算,这和传统的函数引用方式:即通过堆栈设置运算环境有根本不同,Free Monad是用heap换stack,避免了递归算法容易出现的堆栈溢出问题。这方面又解决了FP程序运行安全问题。

通过调研、演练后基本掌握了Monadic Programming(MP)的方式方法。现在把它总结如下:

MP编程可分三个环节:

1、编写程序功能描述,是一串代数语法(AST)。不是即时程序(Programm)

2、把功能描述对应到具体的效果实现方式

3、最后,运算选定的实现方式

分成具体的步骤如下:

1、ADT:模拟语句,用F[A]类数据类型来模拟指令

object FreeADTs {
trait Dialog[A]
case class Ask(prompt: String) extends Dialog[String]
case class Tell(msg: String) extends Dialog[Unit]
implicit def dialogToFree[A](da: Dialog[A]) = Free.liftF(da)
}

2、lift:把F[A]升格成Free Monad

  implicit def dialogToFree[A](da: Dialog[A]) = Free.liftF(da)

3、AST:代数程序,描述程序功能

object FreeASTs {
import FreeADTs._
val prg: Free[Dialog,Unit] = for {
x <- Ask("What's your first name?")
_ <- Tell(s"Hi, $x")
y <- Ask("What's your last name?")
_ <- Tell(s"Hello $x $y!!!")
} yield()
}

4、Interpret:把F[A]对应到G[A]上。G[A]是实现具体效果的Monad。以下提供了两种不同效果的实现方式

object FreeInterp {
import FreeADTs._
object DialogConsole extends (Dialog ~> Id) {
def apply[A](da: Dialog[A]): Id[A] = da match {
case Ask(p) => println(p); readLine
case Tell(s) => println(s) }
}
type WF[A] = Map[String,String] => A
type Tester[A] = WriterT[WF,List[String],A]
implicit val testerMonad = WriterT.writerTMonad[WF,List[String]]
def testerToWriter[A](f: Map[String,String] => (List[String],A)) = WriterT[WF,List[String],A](f)
object DialogTester extends (Dialog ~> Tester) {
def apply[A](da: Dialog[A]): Tester[A] = da match {
case Ask(p) => testerToWriter {m => (List(),m(p))}
case Tell(s) => testerToWriter {m => (List(s),())}
}
}
}

5、Run:最后,对实现方式进行运算

object SimpleFree extends App {
import FreeASTs._
import FreeInterp._ //prg.foldMapRec(DialogConsole)
prg.foldMapRec(DialogTester).run(
Map("What's your first name?" -> "Johnny", "What's your last name?" -> "Foo")
)._1.map(println) }

如果出现多种模拟语法的情况,我们可以用inject方式把各种语法注入Coproduct,形成一个多语法的语句集合。具体的语法集合以及多语法的效果实现对应运算可以参考前面这篇博客中的讨论

Scalaz(43)- 总结 :FP就是实用的编程模式的更多相关文章

  1. Scalaz(54)- scalaz-stream: 函数式多线程编程模式-Free Streaming Programming Model

    长久以来,函数式编程模式都被认为是一种学术研究用或教学实验用的编程模式.直到近几年由于大数据和多核CPU的兴起造成了函数式编程模式在一些实际大型应用中的出现,这才逐渐改变了人们对函数式编程无用论的观点 ...

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

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

  3. Javascript编程模式(JavaScript Programming Patterns)Part 2.(高级篇)

    模块编程模式的启示(Revealing Module Pattern) 客户端对象(Custom Objects) 懒函数定义(Lazy Function Definition) Christian  ...

  4. C#编程模式之扩展命令

    C#编程模式之扩展命令 前言 根据上一篇的命令模式和在工作中遇到的一些实际情况,有了本篇文章,时时都是学习的一个过程,会在这个过程中发现许多好的模式或者是一种开发方式,今天写出来的就是我工作中常用到的 ...

  5. 泛函编程(27)-泛函编程模式-Monad Transformer

    经过了一段时间的学习,我们了解了一系列泛函数据类型.我们知道,在所有编程语言中,数据类型是支持软件编程的基础.同样,泛函数据类型Foldable,Monoid,Functor,Applicative, ...

  6. MXNet设计笔记之:深度学习的编程模式比较

    市面上流行着各式各样的深度学习库,它们风格各异.那么这些函数库的风格在系统优化和用户体验方面又有哪些优势和缺陷呢?本文旨在于比较它们在编程模式方面的差异,讨论这些模式的基本优劣势,以及我们从中可以学到 ...

  7. CUDA 标准编程模式

    前言 本文将介绍 CUDA 编程的基本模式,所有 CUDA 程序都基于此模式编写,即使是调用库,库的底层也是这个模式实现的. 模式描述 1. 定义需要在 device 端执行的核函数.( 函数声明前加 ...

  8. 利用MVC编程模式-开发一个简易记事本app

    学了极客学院一个开发记事本的课程,利用自己对MVC编程模式的简单理解重写了一遍该app. github地址:https://github.com/morningsky/MyNote MVC即,模型(m ...

  9. 为了支持AOP的编程模式,我为.NET Core写了一个轻量级的Interception框架[开源]

    ASP.NET Core具有一个以ServiceCollection和ServiceProvider为核心的依赖注入框架,虽然这只是一个很轻量级的框架,但是在大部分情况下能够满足我们的需要.不过我觉得 ...

随机推荐

  1. Atitit 游戏的通常流程 attilax 总结 基于cocos2d api

    Atitit 游戏的通常流程 attilax 总结 基于cocos2d api 加载音效1 加载页面1 添加精灵1 设置随机位置2 移动2 垃圾gc2 点击evt2 爆炸效果3 定时生成精灵3 加载音 ...

  2. WPF入门教程系列五——Window 介绍

    一.窗体类基本概念 对于WPF应用程序,在Visual Studio和Expression Blend中,自定义的窗体均继承System.Windows.Window类.用户通过窗口与 Windows ...

  3. web工作流

    web工作流之Gulp学习 Gulp.js 是一个自动化构建工具,开发者可以使用它在项目开发过程中自动执行常见任务. Gulp.js 是基于 Node.js 构建的,利用 Node.js 流的威力,你 ...

  4. Sql Server系列:索引维护

    1. DBCC SHOWCONTIG 显示指定表的数据和索引的碎片信息.当对表进行大量的修改或添加数据后,执行此语句可以查看有无碎片,显示指定的表或试图的数据和索引的碎片信息. 其语法格式: DBCC ...

  5. 绘制圆动画--重写view

    /** * @FileName CircleProgressBar.java * @Package com.read.view * @Description TODO * @Author Alpha ...

  6. AngularJs ui-router 路由的简单介绍

    之前有写过一篇关于Angular自带的路由:ngRoute.今天来说说Angular的第三方路由:ui-router.那么有人就会问:为什么Angular有了自带的路由,我们还需要用ui-router ...

  7. 2014PPTV-题解

    今天在看PPTV几道题目,顺便联系起红宝书<JavaScript高级程序设计>一起看了起来. 1. var msg = 'hello';//顶级作用域windwo下有个变量msg func ...

  8. [转载]—— Android JNI知识点

    Java Native Interface (JNI)标准是java平台的一部分,它允许Java代码和其他语言写的代码进行交互.JNI 是本地编程接口,它使得在 Java 虚拟机 (VM) 内部运行的 ...

  9. 让微信扫描直接下载你的APK

    去年在做一个项目的时候,用微信扫描apk下载失效,显示空白页,网上找找发现在后面加一个字符串即可,于是简单搞定.过了一阵子,今年不行了,我的敏捷个人下载二维码也失效了,今天和大家说一下,现在如何解决这 ...

  10. mysql判断一条记录是否存在,如果存在,则更新此语句,如果不存在,则插入

    前言,在我们的业务逻辑中,很有可能会遇到这样的情况. 1.我要更新一条记录的值. 2.但是我不确定这条记录存不存在??? 3.那如果存在?我就更新,如果不存在,我就插入! 那么如果这样,一般情况下,我 ...