完成了对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. react8 组件之间的通信

    <body><!-- React 真实 DOM 将会插入到这里 --><div id="example"></div> <!- ...

  2. WPF入门教程系列十——布局之Border与ViewBox(五)

    九. Border Border 是一个装饰的控件,此控件绘制边框及背景,在 Border 中只能有一个子控件,若要显示多个子控件,需要将一个附加的 Panel 控件放置在父 Border 中.然后可 ...

  3. 用javascript动态创建并提交表单form,表格table

    <script> //helper function to create the formfunction getNewSubmitForm(){ var submitForm = doc ...

  4. 动态单链表的传统存储方式和10种常见操作-C语言实现

    顺序线性表的优点:方便存取(随机的),特点是物理位置和逻辑为主都是连续的(相邻).但是也有不足,比如:前面的插入和删除算法,需要移动大量元素,浪费时间,那么链式线性表 (简称链表) 就能解决这个问题. ...

  5. golang在Windows下Sublime Text开发调试环境的配置

    一.前言 近期本人有工作调动,进入了一个全新的领域[golang]服务端开发.在此写下本文,希望给那些没接触过golang开发调试环境及还在犹豫选择那家golang IDE而纠结的朋友们一点点帮助,如 ...

  6. Android基于mAppWidget实现手绘地图(四)--如何附加javadoc

    如何把javadoc添加到代码库中? How to attach javadoc to the library? 项目属性——>Java Build Path——>Libraries.选择 ...

  7. Android进程间通信之socket通信

    用Java中的socket编程. 通过socket实现两个应用之间的通信,可以接收和发送数据,同时将接收到的数据显示在activity界面上. Server端: ServerLastly.java p ...

  8. 【大数据】Summingbird(Storm + Hadoop)的demo运行

    一.前言 为了运行summingbird demo,笔者走了很多的弯路,并且在国内基本上是查阅不到任何的资料,耗时很久才搞定了demo的运行.真的是一把辛酸泪,有兴趣想要研究summingbird的园 ...

  9. Windows Azure Cloud Service (36) 在Azure Cloud Service配置SSL证书

    <Windows Azure Platform 系列文章目录> 在某些时候,我们需要在Azure PaaS Cloud Service配置HTTPS连接.本章将介绍如何在本地创建证书,然后 ...

  10. Windows Azure Cloud Service (37) 浅谈Cloud Service

    <Windows Azure Platform 系列文章目录> 最近在和一些客户聊天,常常被遇到这样的问题: 1.问题一:我在创建一个新的Windows Azure Virtual Mac ...