我们在前面花了几期时间讨论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. Java的概述以及语法

    Java的语法分为标示符和数据类型 Java的概述: 一些手打的: long l = 12345; //隐式转换 int a = (int)121234567L; //强制转换 float f =12 ...

  2. ASP.net的文件扩展名

    尽管ASP.NET中采用的是事件响应模式,使程序开发人员和最终用户感觉与WinForm程序非常接近,但是它毕竟还是Web应用程序.而Web应用程序的特点,就是基于浏览器与服务器的请求与响应的执行方式. ...

  3. atitit..代码生成流程图 流程图绘制解决方案 java  c#.net  php v2

    atitit..代码生成流程图 流程图绘制解决方案 java  c#.net  php v2 1.1. Markdown 推荐,就是代码和flow都不能直接使用.1 1.2. Java code2fl ...

  4. java初学者应掌握的30个基本概念

    核心提示:OOP中唯一关系的是对象的接口是什么,就像计算机的销售商她不管电源内部结构 是怎样的,他只关系能否给你提供电就行了,也就是只要知道can or not而不是how and why. 基本概念 ...

  5. 第七节:Class与Style绑定

    1.绑定class,v-bind:class  简写为::class 对象方式: <!-- 绑定一个class对象,也可以绑定data中的对象--> <span v-bind:cla ...

  6. XML学习笔记6——XPath语言

    在上一篇笔记的结尾,我们接触到了两个用于选择XML文档中特定范围的元素<selector>和<field>,这两个元素的取值都是XPath表达式,那么,什么是XPath呢?简单 ...

  7. jQuery事件流的顺序

    <div id="aaron"> <div id='test'> <ul> <p>点击p被委托,ul被阻止了,因为内部重写了事件对象 ...

  8. Git-图文教程

    https://github.com/haveatry823/QSanguoshaAI/wiki/Git-%E5%9B%BE%E6%96%87%E6%95%99%E7%A8%8B http://www ...

  9. Session监听器

    Session监听器,是用来监听session对象创建和关闭的.有时我们需要在session创建或关闭时执行一些操作.这是就可以使用Session Listenner. .在项目的web.xml文件中 ...

  10. Abp公共连接和事务管理方法

    Conection 和事务管理在使用数据库的应用中是一个最重要的概念.当你打开一个连接,开始一个事务,如何来处理这些连接等等. 您也许知道,.NET使用了连接池.所以,创建一个连接实际上是从连接池里得 ...