我们在前面花了几期时间讨论Free Monad,那是因为FP既是Monadic programming,Free Monad是FP模式编程的主要方式。对我们来说,Free Monad代表着fp从学术探讨到实际应用的转变,因为我们已经示范了如何用Free Monad的算式算法关注分离模式来实现真正的软件编程。但是美中不足的是用Free Monad只能编写流程式的程序;我们只能一步一步编译这种程序而无法实现并行运算以及在编译之前对程序结果进行分析或转换等。这种特性可以从Monad的运算函数flatMap的函数款式里看出:def flatMap(fa: F[A])(f: A=>F[B]):F[B]。如果F[A]代表当前程序、F[B]就是下一个程序,而下一个程序的产生依赖于F[A]运算结果,一个runtime值。在没有完成F[A]的运算之前我们无法得知F[B]的任何信息。所以又说Monadic程序结构是动态的。我们看到Free Monad功能十分强大:可以用Free Monad来实现任何程序,只不过这些程序结构都是动态的。动态结构程序对解决某些类型的问题在效率上可能不如静态结构的程序。我们可以用Applicative来产生静态结构的程序,这个从Applicative的运算函数ap可以看得出来:def ap(f: F[A=>B])(F[A]):F[B]。我们可以这样看ap:F[A=>B]是第一段程序,F[A]是下一段程序,而F[B]是结果程序。 第一第二段程序都不依赖任何运算值,所以我们可以先构建这些程序然后在任何时候对这些程序进行编译。由于所有程序都是固定预知而互不影响的,所以非常适合并行运算。

与Free Monad相似,Free Applicative Functor也是Applicative的结构化。下面是scalaz对FreeAp的定义:scalaz/FreeAp.scala

sealed abstract class FreeAp[F[_],A] {
...
private [scalaz] case class Pure[F[_],A](a: A) extends FreeAp[F,A]
private abstract case class Ap[F[_],A]() extends FreeAp[F,A] {
type I
val v: () => F[I]
val k: () => FreeAp[F, I => A]
}

FreeAp是一种有两种状态的数据类型:case class Pure(a: A)和 case class Ap(){v: ()=>F[I], k: ()=> FreeAp[F, I=>A]},其中Pure既是Return,包嵌一个运算结果值A,Ap结构内的k代表了下一个FreeAp,实现一个以Pure为终结的FreeAp结构链条。

实现了Applicative的结构化后我们就可以沿袭Free Monad的算式算法关注分离模式先编写描述功能的程序然后再对程序进行编译,只不过FreeAp程序不再是在Monadic for-comprehension内的行令编程,而是一连串的ap类函数了。与Free Monad一致,我们同样用ADT来模拟applicative编程语法,然后用ap函数把ADT链接起来成为程序。我们借用scalaz.example/FreeApUsage.scala来解译:

1、定义ADT: 这里是个解析(parse)数据类型的代数语法

  // An algebra of primitive operations in parsing types from Map[String, Any]
sealed trait ParseOp[A]
case class ParseInt(key: String) extends ParseOp[Int]
case class ParseString(key: String) extends ParseOp[String]
case class ParseBool(key: String) extends ParseOp[Boolean]

2、升格:Lift to FreeAp

  // Free applicative over Parse.
type Parse[A] = FreeAp[ParseOp, A] // Smart constructors for Parse[A]
def parseInt(key: String) = FreeAp.lift(ParseInt(key))
def parseString(key: String) = FreeAp.lift(ParseString(key))
def parseBool(key: String) = FreeAp.lift(ParseBool(key))

FreeAp.lift 可以把任何F[A]升格成FreeAp[F,A]:

  /** Lift a value in `F` into the free applicative functor on `F` */
def lift[F[_],A](x: => F[A]): FreeAp[F, A] = FreeAp(x, Pure((a: A) => a))

3、AST: Applicative编程

  // An example that returns a tuple of (String, Int, Boolean) parsed from Map[String, Any]
val successfulProg: Parse[(String, Int, Boolean)] =
(parseString("string") |@| parseInt("int") |@| parseBool("bool"))((_, _, _)) // An example that returns a tuple of (Boolean, String, Int) parsed from Map[String, Any]
val failedProg: Parse[(Boolean, String, Int)] =
(parseBool("string") |@| parseString("list") |@| parseInt("bool"))((_, _, _))

可以看到上面的Applicative编程就是用|@|把FreeAp结构链接起来,然后跟着把FreeAp之间的运算函数提供进去。我们知道F[A]|@|F[B]还是返回FreeAp[F,C]。也就是说这个程序的结果可以和其它FreeAp进行组合。我们可以看看下面的示范:

 object algebra {
sealed trait ConfigF[A] case class ConfigInt [A](field: String, value: Int => A) extends ConfigF[A]
case class ConfigFlag [A](field: String, value: Boolean => A) extends ConfigF[A]
case class ConfigPort [A](field: String, value: Int => A) extends ConfigF[A]
case class ConfigServer[A](field: String, value: String => A) extends ConfigF[A]
case class ConfigFile [A](field: String, value: String => A) extends ConfigF[A]
case class ConfigSub [A](field: String, value: FreeAp[ConfigF, A]) extends ConfigF[A]
} object dsl {
import algebra._ type Dsl[A] = FreeAp[ConfigF, A] private def lift[A](value: ConfigF[A]): Dsl[A] = FreeAp.lift[ConfigF, A](value) def int (field: String): Dsl[Int] = lift(ConfigInt (field, identity))
def flag (field: String): Dsl[Boolean] = lift(ConfigFlag (field, identity))
def port (field: String): Dsl[Int] = lift(ConfigPort (field, identity))
def server(field: String): Dsl[String] = lift(ConfigServer(field, identity))
def file (field: String): Dsl[String] = lift(ConfigFile (field, identity))
def sub[A](field: String)
(value: Dsl[A]) = lift(ConfigSub (field, value))
}

上面定义了ADT及升格函数int,flag,port...

  case class AuthConfig(port: Int, host: String)
case class ServerConfig(logging: Boolean, auth: AuthConfig) val authConfig = (int("port") |@| server("host"))(AuthConfig)
val serverConfig = (flag("logging") |@| sub("auth")(authConfig))(ServerConfig)

以上的serverConfig就用了authConfig进行了再组合。

4、Interpret: 翻译,把描述的功能对应到具体的实现方法上,还是用NaturalTransformation的方式把F[A]对应到G[A]:

  def parseOpt[A: ClassTag](a: Any): Option[A] =
a match {
case a: A => Some(a)
case _ => None
} // Natural transformation to Option[A]
def toOption(input: Map[String, Any]): ParseOp ~> Option =
new (ParseOp ~> Option) {
def apply[A](fa: ParseOp[A]) = fa match {
case ParseInt(key) =>
input.get(key).flatMap(parseOpt[java.lang.Integer](_).map(x => (x: Int)))
case ParseString(key) => input.get(key).flatMap(parseOpt[String])
case ParseBool(key) =>
input.get(key).flatMap(parseOpt[java.lang.Boolean](_).map(x => (x: Boolean)))
}
} // Natural transformation to ValidationNel[String, A]
type ValidatedParse[A] = ValidationNel[String, A]
def toValidation(input: Map[String, Any]): ParseOp ~> ValidatedParse =
new (ParseOp ~> ValidatedParse) {
def apply[A](fa: ParseOp[A]) = fa match {
case s@ParseInt(_) => toOption(input)(s)
.toSuccessNel(s"${s.key} not found with type Int")
case s@ParseString(_) => toOption(input)(s)
.toSuccessNel(s"${s.key} not found with type String")
case i@ParseBool(_) => toOption(input)(i)
.toSuccessNel(s"${i.key} not found with type Boolean")
}
}

以上展示了两种程序翻译方法,对同样的程序可以用两种运算方式:

ParseOp ~> Option:翻译成Option类型。注意:input.get(key)返回Option,parseOpt同样返回Option

ParseOp ~> ValidatedPase:翻译成Validation类型。注意:无论如何,运算过程是不会中断的,ValidationNel中会记录所有错误信息

5、运算:runner,用折叠式来对一串FreeAp结构的每一个单元进行运算,还是叫做foldMap:

  /**
* The canonical natural transformation that interprets this free
* program by giving it the semantics of the applicative functor `G`.
* Not tail-recursive unless `G` is a free monad.
*/
def foldMap[G[_]:Applicative](f: F ~> G): G[A] =
this match {
case Pure(x) => Applicative[G].pure(x)
case x@Ap() => Applicative[G].ap(f(x.v()))(x.k() foldMap f)
}

运算原理很简单:如果是Pure就返回包嵌的值;如果是Ap对下一个FreeAp k()进行递归运算。

我们用测试数据来运行一下:

 // An example that returns a tuple of (String, Int, Boolean) parsed from Map[String, Any]
val successfulProg: Parse[(String, Int, Boolean)] =
(parseString("string") |@| parseInt("int") |@| parseBool("bool"))((_, _, _)) // An example that returns a tuple of (Boolean, String, Int) parsed from Map[String, Any]
val failedProg: Parse[(Boolean, String, Int)] =
(parseBool("string") |@| parseString("list") |@| parseInt("bool"))((_, _, _)) // Test input for programs
val testInput: Map[String, Any] =
Map("string" -> "foobar", "bool" -> true, "int" -> , "list" -> List(, )) // Run that baby
println(successfulProg.foldMap(toOption(testInput)))
println(successfulProg.foldMap(toValidation(testInput)))
println(failedProg.foldMap(toOption(testInput)))
println(failedProg.foldMap(toValidation(testInput)))

下面是运算结果:

Some((foobar,,true))
Success((foobar,,true))
None
Failure(NonEmpty[bool not found with type Int,list not found with type String,string not found with type Boolean])

我们得到了期望的结果。

Scalaz(42)- Free :FreeAp-Applicative Style Programming Language的更多相关文章

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

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

  2. Scalaz(25)- Monad: Monad Transformer-叠加Monad效果

    中间插播了几篇scalaz数据类型,现在又要回到Monad专题.因为FP的特征就是Monad式编程(Monadic programming),所以必须充分理解认识Monad.熟练掌握Monad运用.曾 ...

  3. Scalaz(53)- scalaz-stream: 程序运算器-application scenario

    从上面多篇的讨论中我们了解到scalaz-stream代表一串连续无穷的数据或者程序.对这个数据流的处理过程就是一个状态机器(state machine)的状态转变过程.这种模式与我们通常遇到的程序流 ...

  4. Windows Phone开发(42):缓动动画

    原文:Windows Phone开发(42):缓动动画 前面在讨论关键帧动画的时候,我有意把几个带缓动动画的关键帧动画忽略掉,如EasingColorKeyFrame.EasingDoubleKeyF ...

  5. Qt 学习之路 2(42):QListWidget、QTreeWidget 和 QTableWidget

    Qt 学习之路 2(42):QListWidget.QTreeWidget 和 QTableWidget 豆子 2013年2月5日 Qt 学习之路 2 38条评论 上一章我们了解了 model/vie ...

  6. MySQL数据分析-(15)表补充:存储引擎

    大家好,我是jacky,很高兴继续跟大家分享<MySQL数据分析实战>,今天跟大家分享的主题是表补充之存储引擎: 我们之前学了跟表结构相关的一些操作,那我们看一下创建表的SQL模型: 在我 ...

  7. STL笔记(6)标准库:标准库中的排序算法

    STL笔记(6)标准库:标准库中的排序算法 标准库:标准库中的排序算法The Standard Librarian: Sorting in the Standard Library Matthew A ...

  8. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(42)-工作流设计01

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(42)-工作流设计01 工作流在实际应用中还是比较广泛,网络中存在很多工作流的图形化插件,可以做到拉拽的工 ...

  9. SQL Server高可用——日志传送(4-2)——部署

    原文:SQL Server高可用--日志传送(4-2)--部署 前文再续,书接上一回.本章演示一下日志传送的具体过程 准备工作: 由于时间关系,已经装好了3台虚拟机,且同在一个域里面: SQL01:主 ...

随机推荐

  1. paip.python错误解决23

    paip.python错误解决 作者Attilax 艾龙, EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog.csdn.net/attilax ...

  2. HTML基础笔记-01

    ---恢复内容开始--- 学习网站:W3School 1.基础知识 目录: <1.我的文档—> 选择目录名—> 主页—> 样式(点击标题样式,选择你想要的每个标题,重复此步骤) ...

  3. 练习3:修改withdraw 方法 练习目标-使用有返回值的方法:在本练习里,将修改withdraw方法以返回一个布尔值来指示交易是否成功。

    boolean withdraw(double get){ if(get<=balance) { System.out.println("取钱"+get+"元,当余 ...

  4. Android开发之广播

    广播是Android开发中的一个重要的功能,在Android里面有各式各样的广播,比如:电池的状态变化.信号的强弱状态.电话的接听和短信的接收等等,现在给大家简单介绍一下系统发送.监听这些广播的机制. ...

  5. Android属性动画之ObjectAnimator

    相信对于Android初学者,对于Android中的动画效果一定很感兴趣,今天为大家总结一下刚刚学到的属性动画案例. 首先和一般的Android应用一样,我们先建一个工程,为了方便,我们的布局文件中就 ...

  6. Android_TextView之跑马灯效果

    对于android控件中的TextView,相信大家一定不陌生,在显示文本内容时十分方便.不过我在使用时遇到一个小问题,就是当文字交多时,如何为用户进行展示.今天就为大家介绍一种解决方案--跑马灯效果 ...

  7. 面试准备 - C# 版本的树状数组

    树状数组 计算 任意连续N个值的和的时间复杂度为Log(n) 修改也是Log(n) 而普通数组修改是O(1) 计算和是O(n) 具体定义可以看这里:http://zh.wikipedia.org/zh ...

  8. Android基于mAppWidget实现手绘地图(六)–如何展示地图对象

    为了展示选中的点,你需要完成以下步骤: 1.创建或者获得一个已经存在的图层 2.创建代表选中点的地图对象 3.把地图对象添加到图层 创建新图层 使用以下代码片段创建图层 int COFFEE_SHOP ...

  9. CSS3 过渡特性创建信封效果的联系表单

    最近给大家分享 CSS3 效果比较多,都是充分运用了 CSS3 来实现的.通过 CSS3 的新特性,我们创作出好的交互和效果的可能性大大增加.这篇文章中,我想与大家分享使用 CSS3 过渡特性实现的信 ...

  10. Web 开发人员和设计师必读文章推荐【系列三十】

    <Web 前端开发精华文章推荐>2014年第9期(总第30期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...