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:主 ...
随机推荐
- lua如何调用C++函数
第一步是定义函数.所有在Lua中被调用的C/C++函数将使用下面一类指针进行调用: typedef int (*lua_CFunction) (lua_State *L); 换句话说,函数必须要以Lu ...
- flow.ci + Github + Slack 一步步搭建 Python 自动化持续集成
理想的程序员必须懒惰,永远追随自动化法则.Automating shapes smarter future. 在一个 Python 项目的开发过程中可能会做的事情:编译.手动或自动化测试.部署环境配置 ...
- salesforce 零基础学习(三十六)通过Process Builder以及Apex代码实现锁定记录( Lock Record)
上一篇内容是通过Process Builder和Approval Processes实现锁定记录的功能,有的时候,往往锁定一条记录需要很多的限制条件,如果通过Approval Processes的条件 ...
- 学习ASP.NET MVC(四)——我的第一个ASP.NET MVC 实体对象
今天我将根据数据库中的表结构添加一些类.这些类将成为这个ASP.NET MVC应用程序中“模型”的一部分. 我们使用Entity Framework(实体框架)来定义和使用这些模型类,并且 ...
- Linux常用命令01
Linux对于我们来说,就是跑程序的运行平台,简单的来说,就是服务器,自己也没怎么系统的学习Linux的命令,随着项目的需要, 比如要查找日志,哪里出问题了,哪里报错了,因此自己也慢慢地懂一些常用的L ...
- Java-set集合
package exception; import java.util.Map; import java.util.TreeMap; import java.util.HashMap; public ...
- 快速入门系列--WebAPI--03框架你值得拥有
接下来进入的是俺在ASP.NET学习中最重要的WebAPI部分,在现在流行的互联网场景下,WebAPI可以和HTML5.单页应用程序SPA等技术和理念很好的结合在一起.所谓ASP.NET WebAPI ...
- 解决h5的video标签,android、ipad客户端播放正常,iphone客户端无法播放
在做html5时插入一个视频播放标签video后,测试时android.ipad客户端播放正常,唯独iphone自带浏览器无法播放. 解决办法: 判断用户所使用客户端访问h5页面时是iphone时,点 ...
- struts2简单数据验证
当表单数据提交到后台后通常要对数据进行校验,以登录为例,后台拿到用户名密码后会判断是否正确,正确的话会跳转到网站用户登录成功的页面,如果不正确的话会提示用户输入不正确. 首先在struts.xml配置 ...
- javase基础复习攻略《四》
本篇内容重点介绍JAVA中的异常处理机制,什么是JAVA异常?JAVA异常是JAVA提供的用于处理程序中错误的一种机制.所谓错误就是指在程序的运行过程中出现的一些异常事件(如:0溢出,数组下表越界,所 ...