在任何模式的编程过程中都无法避免副作用的产生。我们可以用F[A]这种类型模拟FP的运算指令:A是可能产生副作用的运算,F[_]是个代数数据类型ADT(Algebraic Data Type),可以实现函数组合(functional composition),我们可以不用理会A,先用F[_]来组合形成描述功能的抽象程序AST(Abstract Syntax Tree),对A的运算可以分开另一个过程去实现,而且可以有多种的运算实现方式,这样就达到了算式AST(Monadic Programming)、算法(Interpretation)的所谓关注分离(separation of concern)目的。在前面的讨论中我们介绍过:我们可以把任何F[A]升格成Monad,而Monad具备最完善的函数组合性能,特别是它支持for-comprehension这种表达方式。我们可以在for-comprehension框架里进行我们熟悉的行令编程(imperative programming),可以使程序意思表达更加显而易见。

下面我们来做一个简单的示范:模拟一个互动智力算数测试(math quiz):在系统提示下,用户输入第一个数字、再输入第二个数字、再输入操作符号、系统输出算数操作结果。我们可以设计ADT如下:

 sealed trait Quiz[+Next]
case class Question[Next](que: String, n: String => Next) extends Quiz[Next]
case class Answer[Next](ans: String, n: Next) extends Quiz[Next]

Quiz类型可能属于Question或Answer。Question需要读取一个String类型输入,由于实际需要的可能是一个Int或者是Char,在获取输入后还要进行下一步类型转换(map),所以还必须把一个转换函数String=>Next存放入Question结构。Answer则不需要任何输入,所以我们会把()作为Next的值存入Answer结构。

我们可以map over Next类型获取Quiz的Functor实例:

   implicit object QFunctor extends Functor[Quiz] {
def map[A,B](qa: Quiz[A])(f: A => B): Quiz[B] =
qa match {
case q: Question[A] => Question(q.que, q.n andThen f)
case Answer(a,n) => Answer(a,f(n))
}
}

从case q: Question[A]可以看出来:map over Next实际上是连续运算(andThen)。

我们再来几个操作帮助方法:

 //操作帮助方法helper methods
def askNumber(q: String) = Question(q, (inputString => inputString.toInt)) //_.toInt
def askOperator(q: String) = Question(q, (inputString => inputString.head.toUpper.toChar))
def answer(fnum: Int, snum: Int, opr: Char) = {
def result =
opr match {
case 'A' => fnum + snum
case 'M' => fnum * snum
case 'D' => fnum / snum
case 'S' => fnum - snum
}
Answer("my answer is: " + result.toString,())
}

我们现在可以这样编写AST了:

 import Quiz._
val prg = for {
fn <- askNumber("The first number is:")
sn <- askNumber("The second number is:")
op <- askOperator("The operation is:")
_ <- answer(fn,sn,op)
} yield() //> prg : scalaz.Free[Exercises.interact.Quiz,Unit] = Gosub()

但是,askNumber,askOperator及answer这几个操作函数都返回了Quiz类型,而Quiz类型不是Monad,不支持for-comprehension。我们可以用个隐式转换把所有Quiz[A]升格成Free[Quiz,A]:

   implicit def quizToFree[A](qz: Quiz[A]): Free[Quiz,A] = Free.liftF(qz)

这个示范完整的源代码如下:

 sealed trait Quiz[+Next]
object Quiz {
//问题que:String, 等待String 然后转成数字或操作符号
case class Question[Next](que: String, n: String => Next) extends Quiz[Next]
case class Answer[Next](ans: String, n: Next) extends Quiz[Next]
implicit object QFunctor extends Functor[Quiz] {
def map[A,B](qa: Quiz[A])(f: A => B): Quiz[B] =
qa match {
case q: Question[A] => Question(q.que, q.n andThen f)
case Answer(a,n) => Answer(a,f(n))
}
}
//操作帮助方法helper methods
def askNumber(q: String) = Question(q, (inputString => inputString.toInt)) //_.toInt
def askOperator(q: String) = Question(q, (inputString => inputString.head.toUpper.toChar)) //_.head.toUpper.toChar
def answer(fnum: Int, snum: Int, opr: Char) = {
def result =
opr match {
case 'A' => fnum + snum
case 'M' => fnum * snum
case 'D' => fnum / snum
case 'S' => fnum - snum
}
Answer("my answer is: " + result.toString,())
}
implicit def quizToFree[A](qz: Quiz[A]): Free[Quiz,A] = Free.liftF(qz)
}
import Quiz._
val prg = for {
fn <- askNumber("The first number is:")
sn <- askNumber("The second number is:")
op <- askOperator("The operation is:")
_ <- answer(fn,sn,op)
} yield() //> prg : scalaz.Free[Exercises.interact.Quiz,Unit] = Gosub()

再看看下面的例子。试着猜测程序的作用:

 sealed trait Calc[+A]
object Calc {
case class Push(value: Int) extends Calc[Unit]
case class Add() extends Calc[Unit]
case class Mul() extends Calc[Unit]
case class Div() extends Calc[Unit]
case class Sub() extends Calc[Unit]
implicit def calcToFree[A](ca: Calc[A]) = Free.liftFC(ca)
}
import Calc._
val ast = for {
_ <- Push()
_ <- Push()
_ <- Add()
_ <- Push()
_ <- Mul()
} yield () //> ast : scalaz.Free[[x]scalaz.Coyoneda[Exercises.interact.Calc,x],Unit] = Gosub()

从上面的AST表达方式可以估计到这是一个对Int进行加减乘除的计算器,应该是先通过push把操作对象存入一个Stack。然后对Stack内部的数字进行计算操作。具体是如何实现的,在这个阶段无需知道,这应该是Interpreter的工作。这个例子不就真正体现了算式算法的关注分离了的精髓嘛。

Scalaz(33)- Free :算式-Monadic Programming的更多相关文章

  1. 泛函编程(24)-泛函数据类型-Monad, monadic programming

    在上一节我们介绍了Monad.我们知道Monad是一个高度概括的抽象模型.好像创造Monad的目的是为了抽取各种数据类型的共性组件函数汇集成一套组件库从而避免重复编码.这些能对什么是Monad提供一个 ...

  2. FP又称为Monadic Programming

    什么是Monad? trait Monad[+T] { def flatMap[U]( f : (T) => Monad[U] ) : Monad[U] def unit(value : B)  ...

  3. Scalaz(42)- Free :FreeAp-Applicative Style Programming Language

    我们在前面花了几期时间讨论Free Monad,那是因为FP既是Monadic programming,Free Monad是FP模式编程的主要方式.对我们来说,Free Monad代表着fp从学术探 ...

  4. Scalaz(38)- Free :Coproduct-Monadic语句组合

    很多函数式编程爱好者都把FP称为Monadic Programming,意思是用Monad进行编程.我想FP作为一种比较成熟的编程模式,应该有一套比较规范的操作模式吧.因为Free能把任何F[A]升格 ...

  5. Scalaz(22)- 泛函编程思维: Coerce Monadic Thinking

    马上进入新的一年2016了,来点轻松点的内容吧.前面写过一篇关于用Reader实现依赖注入管理的博文(Scalaz(16)- Monad:依赖注入-Dependency Injection By Re ...

  6. Scalaz(43)- 总结 :FP就是实用的编程模式

    完成了对Free Monad这部分内容的学习了解后,心头豁然开朗,存在心里对FP的疑虑也一扫而光.之前也抱着跟大多数人一样的主观概念,认为FP只适合学术性探讨.缺乏实际应用.运行效率低,很难发展成现实 ...

  7. Scalaz(37)- Free :实践-DB Transaction free style

    我一直在不断的提示大家:FP就是Monadic Programming,是一种特殊的编程风格.在我们熟悉的数据库编程领域能不能实现FP风格呢?我们先设计一些示范例子来分析一下惯用的数据库编程过程: i ...

  8. Scalaz(36)- Free :实践-Free In Action - 实用体验

    在上面几期讨论中我们连续介绍了Free Monad.因为FP是纯函数编程,也既是纯函数的组合集成,要求把纯代码和副作用代码可以分离开来.Free Monad的程序描述(AST)和程序实现(Interp ...

  9. Scalaz(35)- Free :运算-Trampoline,say NO to StackOverflowError

    在前面几次讨论中我们介绍了Free是个产生Monad的最基本结构.它的原理是把一段程序(AST)一连串的运算指令(ADT)转化成数据结构存放在内存里,这个过程是个独立的功能描述过程.然后另一个独立运算 ...

随机推荐

  1. ECMAScript5中数组的方法

    1.forEach()方法 遍历数组,为每个数组元素调用指定函数,三个参数分别为:数组元素 item.元素索引 index.数组本身 arr,无返回值 例: 2.map()方法 调用数组的每个元素传递 ...

  2. Atitit 词法分析器的设计最佳实践说明attilax总结

    Atitit 词法分析器的设计最佳实践说明attilax总结 1.1. 手写的优点:代码可读,对源代码中的各种错误给出友好的提示信息,用户体验高,1 1.2. 使用状态表比较简单,dfa比较麻烦1 1 ...

  3. 基于Oracle的SQL优化(社区万众期待 数据库优化扛鼎巨著)

    基于Oracle的SQL优化(社区万众期待数据库优化扛鼎巨著) 崔华 编   ISBN 978-7-121-21758-6 2014年1月出版 定价:128.00元 856页 16开 编辑推荐 本土O ...

  4. 学习ASP.NET MVC(二)——我的第一个ASP.NET MVC 控制器

    MVC全称是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件设计典范,用一种业务逻辑和数据显示分离的方法组织代码,将 ...

  5. javascript_core_05之正则表达式

    1. 检索关键词: ①检索固定关键词的位置: var i=str.indexOf(“关键词”,fromi):在str中查找fromi位置后,下一个关键词的下标位置:省略fromi,从0开始: var ...

  6. MongoDB修改器的使用2

    1."$inc"的使用 主要用来增加数值,比如网站的访问量,点击量,流量等 db.games.insert({game:"pinball",user:" ...

  7. java中Cookie中文字符乱码问题

    如果Cookie中的Value 中有中文字符出现,在加入Cookie的时候,会出现下面的错误: java.lang.IllegalArgumentException: Control characte ...

  8. poj1273Drainage Ditches

    #include<iostream> /* 题意:就是寻找从源点到汇点的最大流! 要注意的是每两个点的流量可能有多个,也就是说有重边,所以要把两个点的所有的流量都加起来 就是这两个点之间的 ...

  9. Azure ARM (9) 创建ARM模式下的虚拟机网络

    <Windows Azure Platform 系列文章目录> 笔者在之前几章内容中,创建了ARM Resource Group,然后在这个ARM Resource Group下创建Azu ...

  10. Java魔法堂:URI、URL(含URL Protocol Handler)和URN

    一.前言 过去一直搞不清什么是URI什么是URL,现在是时候好好弄清楚它们了!本文作为学习笔记,以便日后查询,若有纰漏请大家指正! 二.从URI说起    1. 概念 URI(Uniform Reso ...