Scalaz(42)- Free :FreeAp-Applicative Style Programming Language
我们在前面花了几期时间讨论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的更多相关文章
- Scalaz(22)- 泛函编程思维: Coerce Monadic Thinking
马上进入新的一年2016了,来点轻松点的内容吧.前面写过一篇关于用Reader实现依赖注入管理的博文(Scalaz(16)- Monad:依赖注入-Dependency Injection By Re ...
- Scalaz(25)- Monad: Monad Transformer-叠加Monad效果
中间插播了几篇scalaz数据类型,现在又要回到Monad专题.因为FP的特征就是Monad式编程(Monadic programming),所以必须充分理解认识Monad.熟练掌握Monad运用.曾 ...
- Scalaz(53)- scalaz-stream: 程序运算器-application scenario
从上面多篇的讨论中我们了解到scalaz-stream代表一串连续无穷的数据或者程序.对这个数据流的处理过程就是一个状态机器(state machine)的状态转变过程.这种模式与我们通常遇到的程序流 ...
- Windows Phone开发(42):缓动动画
原文:Windows Phone开发(42):缓动动画 前面在讨论关键帧动画的时候,我有意把几个带缓动动画的关键帧动画忽略掉,如EasingColorKeyFrame.EasingDoubleKeyF ...
- Qt 学习之路 2(42):QListWidget、QTreeWidget 和 QTableWidget
Qt 学习之路 2(42):QListWidget.QTreeWidget 和 QTableWidget 豆子 2013年2月5日 Qt 学习之路 2 38条评论 上一章我们了解了 model/vie ...
- MySQL数据分析-(15)表补充:存储引擎
大家好,我是jacky,很高兴继续跟大家分享<MySQL数据分析实战>,今天跟大家分享的主题是表补充之存储引擎: 我们之前学了跟表结构相关的一些操作,那我们看一下创建表的SQL模型: 在我 ...
- STL笔记(6)标准库:标准库中的排序算法
STL笔记(6)标准库:标准库中的排序算法 标准库:标准库中的排序算法The Standard Librarian: Sorting in the Standard Library Matthew A ...
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(42)-工作流设计01
原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(42)-工作流设计01 工作流在实际应用中还是比较广泛,网络中存在很多工作流的图形化插件,可以做到拉拽的工 ...
- SQL Server高可用——日志传送(4-2)——部署
原文:SQL Server高可用--日志传送(4-2)--部署 前文再续,书接上一回.本章演示一下日志传送的具体过程 准备工作: 由于时间关系,已经装好了3台虚拟机,且同在一个域里面: SQL01:主 ...
随机推荐
- PHP实现RESTful风格的API实例(二)
接前一篇PHP实现RESTful风格的API实例(一) Response.php :包含一个Request类,即输出类.根据接收到的Content-Type,将Request类返回的数组拼接成对应的格 ...
- WPF入门教程系列五——Window 介绍
一.窗体类基本概念 对于WPF应用程序,在Visual Studio和Expression Blend中,自定义的窗体均继承System.Windows.Window类.用户通过窗口与 Windows ...
- javascript_core_06之正则、Math、Date
1.RegExp:Regular Expression,创建封装正则表达式: ①正则直接量:var reg=/reg/ig:②var reg=new RegExp(“reg”,“ig”): 2.Reg ...
- 开源、免费功能全面的Chart图
简介: 每个前端都有一个Chart梦,至于真正去做的寥寥无几,无怪乎几个原因: 浏览器兼容问题 数据处理的一些算法,如自动计算坐标轴.自动排列文本等 流畅的动画 丰富的交互功能 去年一年的时间里,我一 ...
- XML学习笔记3——XSD简述
现在的语言,如果不有那么一点OO的影子,都不好意思称之为语言了.在XML的语义约束方面,DTD虽然简单,但是功能不够强大,完全是直白的描述,于是又有了替代DTD的XSD(XML Schema Defi ...
- 锁&锁与指令原子操作的关系 & cas_Queue
锁 锁以及信号量对大部分人来说都是非常熟悉的,特别是常用的mutex.锁有很多种,互斥锁,自旋锁,读写锁,顺序锁,等等,这里就只介绍常见到的, 互斥锁 这个是最常用的,win32:CreateMute ...
- 行集函数:OpenRowSet 和 OpenQuery
在SQL Server中,行集函数是不确定性的,这意味着,每次调用,返回值不总是相同的.返回值是不确定的,这意味着,对于相同的输入值,不保证每次返回的值都是相同的.对行集函数的每次调用,行集函数都是单 ...
- SQL Server中的版本号
在SQL Server中,通常版本号的命名是大版本.小版本.累积更新这种形式,比如说9.X.XXX就是SQL Server 2005.下面我将把SQL Server中版本号对应的版本列出来,以 ...
- maven+spring+springMVC+mybatis+dubbox
milestone 2016612 dubbox+spring+mybatis provider调通
- C++程序员们,快来写最简洁的单例模式吧
想必每一位程序员都对设计模式中的单例模式非常的熟悉吧,以往我们用C++实现一个单例模式需要写以下代码: class CSingleton { private: CSingleton() //构造函数是 ...