Scalaz(7)- typeclass:Applicative-idomatic function application
Applicative,正如它的名称所示,就是FP模式的函数施用(function application)。我们在前面的讨论中不断提到FP模式的操作一般都在管道里进行的,因为FP的变量表达形式是这样的:F[A],即变量A是包嵌在F结构里的。Scalaz的Applicative typeclass提供了各种类型的函数施用(function application)和升格(lifting)方法。与其它scalaz typeclass使用方式一样,我们只需要实现了针对自定义类型的Applicative实例就可以使用这些方法了。以下是Applicative trait的部分定义:scalaz/Applicative.scala
trait Applicative[F[_]] extends Apply[F] { self =>
////
def point[A](a: => A): F[A]
// alias for point
final def pure[A](a: => A): F[A] = point(a)
。。。
我们首先需要实现抽象函数point,然后由于Applicative继承了Apply,我们看看Apply trait有什么抽象函数需要实现的;scalaz/Apply.scala
trait Apply[F[_]] extends Functor[F] { self =>
////
def ap[A,B](fa: => F[A])(f: => F[A => B]): F[B]
。。。
我们还需要实现抽象函数ap。注意Apply又继承了Functor,所以我们还需要实现map,一旦实现了Applicative实例就能同时获取了Functor实例。
现在我们先设计一个自定义类型作为下面的范例:
trait Configure[+A] {
def get: A
}
object Configure {
def apply[A](data: => A) = new Configure[A] { def get = data }
}
Configure("env string") //> res0: Exercises.ex4.Configure[String] = Exercises.ex4$Configure$$anon$1@6bf2
//| d08e
Configure[+A]是个典型的FP类型。通过实现特殊命名apply的函数作为类型构建器,我们可以这样构建实例:Configure("some string")。现在我们按照scalaz隐式解析(implicit resolution)惯例在伴生对象(companion object)里定义隐式Applicative实例:
import scalaz._
import Scalaz._
object ex4 {
trait Configure[+A] {
def get: A
}
object Configure {
def apply[A](data: => A) = new Configure[A] { def get = data }
implicit val configFunctor = new Functor[Configure] {
def map[A,B](ca: Configure[A])(f: A => B): Configure[B] = Configure(f(ca.get))
}
implicit val configApplicative = new Applicative[Configure] {
def point[A](a: => A) = Configure(a)
def ap[A,B](ca: => Configure[A])(cfab: => Configure[A => B]): Configure[B] = cfab map {fab => fab(ca.get)}
}
}
由于Apply继承了Functor,我们必须先获取Configure的Functor实例。现在我们可以针对Configure类型使用Applicative typeclass的功能函数了。Applicative typeclass的组件函数可以分为几种主要类型:
1、Applicative实例构建函数,point:
"abc".point[Configure] //> res1: Exercises.ex4.Configure[String] = Exercises.ex4$Configure$$anon$3@3c41
//| 9631
.point[Configure] //> res2: Exercises.ex4.Configure[Int] = Exercises.ex4$Configure$$anon$3@6302168
//| 9
.point[Option] //> res3: Option[Int] = Some(5)
看款式应该是通过隐式转换实现的:scalaz/syntax/ApplicativeSyntax.scala
trait ToApplicativeOps extends ToApplicativeOps0 with ToApplyOps {
implicit def ToApplicativeOps[F[_],A](v: F[A])(implicit F0: Applicative[F]) =
new ApplicativeOps[F,A](v)
////
implicit def ApplicativeIdV[A](v: => A) = new ApplicativeIdV[A] {
lazy val self = v
}
trait ApplicativeIdV[A] extends Ops[A] {
def point[F[_] : Applicative]: F[A] = Applicative[F].point(self)
def pure[F[_] : Applicative]: F[A] = Applicative[F].point(self)
def η[F[_] : Applicative]: F[A] = Applicative[F].point(self)
} ////
}
是通过implicit def ApplicativeIDV[A](v: => A)实现的。
2、对F[T}类型进行F[A =>B]式的函数施用(从管道里提供作用函数)。施用函数款式是这样的:
def ap[A,B](fa: => F[A])(f: => F[A => B]): F[B]
对比Functor函数map:map[A,B](fa: F[A])(f: A => B]): F[B], 分别只在提供操作函数A=>B的方式:ap在F结构内部提供,又或者换句话说ap提供的是高阶函数F[A=>B]。从函数款式看来,ap要比map功能更加强大。因为我们可以用ap实现map, 反之不可:
def map[A,B](fa: Configure[A])(f: A => B) = ap(fa)(point(f))
通过ap2,ap3,ap4 ...款式的函数我们可以把 F[A],F[B],F[C],F[D]...多个值连接起来:scalaz/Apply.scala
def ap2[A,B,C](fa: => F[A], fb: => F[B])(f: F[(A,B) => C]): F[C] =
ap(fb)(ap(fa)(map(f)(_.curried)))
def ap3[A,B,C,D](fa: => F[A], fb: => F[B], fc: => F[C])(f: F[(A,B,C) => D]): F[D] =
ap(fc)(ap2(fa,fb)(map(f)(f => ((a:A,b:B) => (c:C) => f(a,b,c)))))
def ap4[A,B,C,D,E](fa: => F[A], fb: => F[B], fc: => F[C], fd: => F[D])(f: F[(A,B,C,D) => E]): F[E] =
ap2(fc, fd)(ap2(fa,fb)(map(f)(f => ((a:A,b:B) => (c:C, d:D) => f(a,b,c,d)))))
def ap5[A,B,C,D,E,R](fa: => F[A], fb: => F[B], fc: => F[C], fd: => F[D], fe: => F[E])(f: F[(A,B,C,D,E) => R]): F[R] =
ap2(fd, fe)(ap3(fa,fb,fc)(map(f)(f => ((a:A,b:B,c:C) => (d:D, e:E) => f(a,b,c,d,e)))))
...
试着在Configure类型上使用ap:
Apply[Configure].ap2(Configure(),Configure())(((_: Int) + (_: Int)).point[Configure])
//> res4: Exercises.ex4.Configure[Int] = Exercises.ex4$Configure$$anon$3@64cd705
//| f或者用注入方法(injected method)<*>:scalaz/Syntax/ApplySyntax.scala
或者用注入方法(injected method)<*>:scalaz/Syntax/ApplySyntax.scala
(Configure() <*> {Configure() <*> {Configure() <*> {(((_:Int)+(_:Int)+(_:Int)).curried).point[Configure]}}}).get
//> res5: Int = 6
以上的Apply[Configure]是通过Apply typeclass的构建函数apply实现的:scalaz/Apply.scala
object Apply {
@inline def apply[F[_]](implicit F: Apply[F]): Apply[F] = F
3、简化一下ap的写法,只用提供f:(A,B) => C这样的基本操作函数:scalaz/Apply.scala
def apply2[A, B, C](fa: => F[A], fb: => F[B])(f: (A, B) => C): F[C] =
ap(fb)(map(fa)(f.curried))
def apply3[A, B, C, D](fa: => F[A], fb: => F[B], fc: => F[C])(f: (A, B, C) => D): F[D] =
apply2(apply2(fa, fb)((_, _)), fc)((ab, c) => f(ab._1, ab._2, c))
def apply4[A, B, C, D, E](fa: => F[A], fb: => F[B], fc: => F[C], fd: => F[D])(f: (A, B, C, D) => E): F[E] =
apply2(apply2(fa, fb)((_, _)), apply2(fc, fd)((_, _)))((t, d) => f(t._1, t._2, d._1, d._2))
def apply5[A, B, C, D, E, R](fa: => F[A], fb: => F[B], fc: => F[C], fd: => F[D], fe: => F[E])(f: (A, B, C, D, E) => R): F[R] =
apply2(apply3(fa, fb, fc)((_, _, _)), apply2(fd, fe)((_, _)))((t, t2) => f(t._1, t._2, t._3, t2._1, t2._2))
...
用在Configure类型上:
(Apply[Configure].apply2(Configure(),Configure())(((_: Int) + (_: Int)))).get
//> res6: Int = 3
(^(Configure(),Configure())((_:Int)+(_:Int))).get
//> res7: Int = 3
(^^(Configure(),Configure(),Configure())((_:Int)+(_:Int)+(_:Int))).get
//> res8: Int = 6
这个^,^^是apply2,apply3的注入方法:scalaz/syntax/ApplySyntax.scala
def ^[A,B,C](fa: => F[A], fb: => F[B])(
f: (A, B) => C): F[C] =
F.apply2(fa, fb)(f) def ^^[A,B,C,D](fa: => F[A], fb: => F[B], fc: => F[C])(
f: (A, B, C) => D): F[D] =
F.apply3(fa, fb, fc)(f) def ^^^[A,B,C,D,E](fa: => F[A], fb: => F[B], fc: => F[C], fd: => F[D])(
f: (A,B,C,D) => E): F[E] =
F.apply4(fa, fb, fc, fd)(f) def ^^^^[A,B,C,D,E,I](fa: => F[A], fb: => F[B], fc: => F[C], fd: => F[D], fe: => F[E])(
f: (A,B,C,D,E) => I): F[I] =
F.apply5(fa, fb, fc, fd, fe)(f)
...
另一种表达方式是通过ApplicativeBuilder typeclass实现的注入方法|@|:
((Configure() |@| Configure() |@| Configure())((_:Int)+(_:Int)+(_:Int))).get
//> res9: Int = 6
效果是一样的。我们用一个实际的简单例子来示范一下Applicative的具体函数施用:
def configName(name: String): Configure[String] = Configure(name)
//> configName: (name: String)Exercises.ex4.Configure[String]
def configID(userid: String): Configure[String] = Configure(userid)
//> configID: (userid: String)Exercises.ex4.Configure[String]
def configPwd(pwd: String): Configure[String] = Configure(pwd)
//> configPwd: (pwd: String)Exercises.ex4.Configure[String]
case class WebLogForm(name:String, id: String, pwd: String) def logOnWeb(name: String, userid: String, pwd: String) =
^^(configName(name),configID(userid), configPwd(pwd))(WebLogForm(_,_,_))
//> logOnWeb: (name: String, userid: String, pwd: String)Exercises.ex4.Configur
//| e[Exercises.ex4.WebLogForm]
def logOnWeb1(name: String, userid: String, pwd: String) =
(configName(name) |@| configID(userid) |@| configPwd(pwd))(WebLogForm(_,_,_))
//> logOnWeb1: (name: String, userid: String, pwd: String)Exercises.ex4.Configu
//| re[Exercises.ex4.WebLogForm]
值得注意的是:用Applicative施用configName,configID,configPwd时,这三个函数之间没有依赖关系。特别适合并行运算或fail-fast,因为无论如何这三个函数都一定会运行。这种Applicative的函数施用体现了它在并行运算中的优势。
4、Applicative style 函数施用。上面提到的|@|操作并不是一种操作函数而是一种层级式持续函数施用模式。具体实现在ApplicativeBuilder typeclass里:scalaz/ApplicativeBuilder.scala
private[scalaz] trait ApplicativeBuilder[M[_], A, B] {
val a: M[A]
val b: M[B]
def apply[C](f: (A, B) => C)(implicit ap: Apply[M]): M[C] = ap.apply2(a, b)(f)
def tupled(implicit ap: Apply[M]): M[(A, B)] = apply(Tuple2.apply)
def ⊛[C](cc: M[C]) = new ApplicativeBuilder3[C] {
val c = cc
}
def |@|[C](cc: M[C]) = ⊛(cc)
sealed trait ApplicativeBuilder3[C] {
val c: M[C]
def apply[D](f: (A, B, C) => D)(implicit ap: Apply[M]): M[D] = ap.apply3(a, b, c)(f)
def tupled(implicit ap: Apply[M]): M[(A, B, C)] = apply(Tuple3.apply)
def ⊛[D](dd: M[D]) = new ApplicativeBuilder4[D] {
val d = dd
}
def |@|[D](dd: M[D]) = ⊛(dd)
sealed trait ApplicativeBuilder4[D] {
val d: M[D]
def apply[E](f: (A, B, C, D) => E)(implicit ap: Apply[M]): M[E] = ap.apply4(a, b, c, d)(f)
def tupled(implicit ap: Apply[M]): M[(A, B, C, D)] = apply(Tuple4.apply)
def ⊛[E](ee: M[E]) = new ApplicativeBuilder5[E] {
val e = ee
}
def |@|[E](ee: M[E]) = ⊛(ee)
...
可以看得出(F[A] |@| F[B] |@| F[C])((A,B,C) => D)这个表达式中的两个|@|符号分别代表ApplicativeBuilder2(F[B])及ApplicativeBuilder3(F[C])。
这是另一种通过函数施用实现连接Applicative类型值的方式。
5、产生tuple:(F[A],F[B])合并成F[(A,B)]:scalaz/Apply.scala
def tuple2[A,B](fa: => F[A], fb: => F[B]): F[(A,B)] =
apply2(fa, fb)((_,_))
def tuple3[A,B,C](fa: => F[A], fb: => F[B], fc: => F[C]): F[(A,B,C)] =
apply3(fa, fb, fc)((_,_,_))
def tuple4[A,B,C,D](fa: => F[A], fb: => F[B], fc: => F[C], fd: => F[D]): F[(A,B,C,D)] =
apply4(fa, fb, fc, fd)((_,_,_,_))
def tuple5[A,B,C,D,E](fa: => F[A], fb: => F[B], fc: => F[C], fd: => F[D], fe: => F[E]): F[(A,B,C,D,E)] =
apply5(fa, fb, fc, fd, fe)((_,_,_,_,_))
...
比如:
Apply[Configure].tuple2(Configure("abc"),Configure())
//> res10: Exercises.ex4.Configure[(String, Int)] = Exercises.ex4$Configure$$an
//| on$3@5ffead27
Apply[Configure].tuple3(Configure("abc"),Configure(),Configure(true))
//> res11: Exercises.ex4.Configure[(String, Int, Boolean)] = Ex
具体用来干什么,我现在还说不上来。
6、把一个普通函数升格(lift)成高阶函数,如:(A,B) => C 升格成 (F[A],F[B]) => F[C]: scalaz/Apply.scala
def lift2[A, B, C](f: (A, B) => C): (F[A], F[B]) => F[C] =
apply2(_, _)(f)
def lift3[A, B, C, D](f: (A, B, C) => D): (F[A], F[B], F[C]) => F[D] =
apply3(_, _, _)(f)
def lift4[A, B, C, D, E](f: (A, B, C, D) => E): (F[A], F[B], F[C], F[D]) => F[E] =
apply4(_, _, _, _)(f)
def lift5[A, B, C, D, E, R](f: (A, B, C, D, E) => R): (F[A], F[B], F[C], F[D], F[E]) => F[R] =
apply5(_, _, _, _, _)(f)
...
这种函数升格方式在用FP方式使用OOP库函数时更加方便。最典型的例子是Option类型在FP中结合OOP函数库的使用。如果我们希望在使用OOP库函数时使用Option类型的输入参数和返回值,那我们就可以通过函数升格(function lifting)来实现这样的功能。
val of2 = Apply[Option].lift2((_: Int) + (_: Int))//> of2 : (Option[Int], Option[Int]) => Option[Int] = <function2>
of2(Some(),Some()) //> res12: Option[Int] = Some(3)
val of3 = Apply[List].lift3((s1: String, s2: String, s3: String) => s1 + " "+s2+" "+s3)
//> of3 : (List[String], List[String], List[String]) => List[String] = <functi
//| on3>
of3(List("How"),List("are"),List("you?")) //> res13: List[String] = List(How are you?)
我们分别用lift2,lift3把普通函数升格成Option和List高阶函数。
再来个更实际一点的例子:在java.sql.DriverManager库里有个getConnection函数。它的函数款式是:getConnection(p1:String,p2:String,p3:String): java.sql.Connection
虽然我没有它的源代码,但我还是想使用我自定义的类型Configure作为参数,我可以这样:
import java.sql.DriverManager val sqlConnect = Apply[Configure] lift3 java.sql.DriverManager.getConnection
//> sqlConnect : (Exercises.ex4.Configure[String], Exercises.ex4.Configure[Str
//| ing], Exercises.ex4.Configure[String]) => Exercises.ex4.Configure[java.sql.
//| Connection] = <function3>
sqlConnect(Configure("Source"),Configure("User"),Configure("Password"))
//> res12: Exercises.ex4.Configure[java.sql.Connection] = Exercises.ex4$Configu
//| re$$anon$3@79924b
的确这样可以使我继续在FP模式中工作。
总结来说:Applicative typeclass提供了一套函数施用方式。它是通过一个包嵌在容器结构的高阶函数实现管道内的施用。Applicative typeclass还提供了方法将普通函数升格到高阶函数使FP和OOP混合模式的函数施用更安全方便。
Scalaz(7)- typeclass:Applicative-idomatic function application的更多相关文章
- Scalaz(6)- typeclass:Functor-just map
Functor是范畴学(Category theory)里的概念.不过无须担心,我们在scala FP编程里并不需要先掌握范畴学知识的.在scalaz里,Functor就是一个普通的typeclass ...
- Scalaz(25)- Monad: Monad Transformer-叠加Monad效果
中间插播了几篇scalaz数据类型,现在又要回到Monad专题.因为FP的特征就是Monad式编程(Monadic programming),所以必须充分理解认识Monad.熟练掌握Monad运用.曾 ...
- Scalaz(22)- 泛函编程思维: Coerce Monadic Thinking
马上进入新的一年2016了,来点轻松点的内容吧.前面写过一篇关于用Reader实现依赖注入管理的博文(Scalaz(16)- Monad:依赖注入-Dependency Injection By Re ...
- Scalaz(53)- scalaz-stream: 程序运算器-application scenario
从上面多篇的讨论中我们了解到scalaz-stream代表一串连续无穷的数据或者程序.对这个数据流的处理过程就是一个状态机器(state machine)的状态转变过程.这种模式与我们通常遇到的程序流 ...
- MySQL数据分析-(15)表补充:存储引擎
大家好,我是jacky,很高兴继续跟大家分享<MySQL数据分析实战>,今天跟大家分享的主题是表补充之存储引擎: 我们之前学了跟表结构相关的一些操作,那我们看一下创建表的SQL模型: 在我 ...
- STL笔记(6)标准库:标准库中的排序算法
STL笔记(6)标准库:标准库中的排序算法 标准库:标准库中的排序算法The Standard Librarian: Sorting in the Standard Library Matthew A ...
- java中文乱码解决之道(三)-----编码详情:伟大的创想---Unicode编码
随着计算机的发展.普及,世界各国为了适应本国的语言和字符都会自己设计一套自己的编码风格,正是由于这种乱,导致存在很多种编码方式,以至于同一个二进制数字可能会被解释成不同的符号.为了解决这种不兼容的问题 ...
- Android Animation学习(五) ApiDemos解析:容器布局动画 LayoutTransition
Android Animation学习(五) ApiDemos解析:容器布局动画 LayoutTransition Property animation系统还提供了对ViewGroup中的View改变 ...
- Android Animation学习(四) ApiDemos解析:多属性动画
Android Animation学习(四) ApiDemos解析:多属性动画 如果想同时改变多个属性,根据前面所学的,比较显而易见的一种思路是构造多个对象Animator , ( Animator可 ...
随机推荐
- sql基础知识:分页+排序
Oracle的分页还真是挺恶心地,不像mysql直接Limit就搞定 select name from student limit 0,20; Oracle需要借助rownum实现: select * ...
- lua实现深度拷贝table表
lua当变量作为函数的参数进行传递时,类似的也是boolean,string,number类型的变量进行值传递.而table,function,userdata类型的变量进行引用传递.故而当table ...
- [开发工具]Java开发常用的在线工具
注明: 本文转自http://www.hollischuang.com/archives/1459.作为一个Java开发人员,经常要和各种各样的工具打交道,除了我们常用的IDE工具以外,其实还有很多工 ...
- ASP.NET MVC 异常Exception拦截器Fillter
异常信息的处理在程序中非常重要, 在asp.net mvc中提供异常属性拦截器进行对异常信息的处理,异常拦截器也没有什么的,只是写一个类,继承另一个类(System.Web.Mvc.FilterAtt ...
- js算法之最常用的排序
引入 大学学习计算机语言的那几年,从c语言,到c++,再到数据结构JAVA..让我印象最深刻的还是最开始老师讲冒泡算法的时候,直到现在大四快毕业了我才渐渐通窍了.刚学前端的时候以为前端就是做出好看很炫 ...
- 如何用Python实现目录遍历
1. 基本实现 [root@localhost ~]# cat dirfile.py import os path='/tmp' for dirpath,dirnames,filenames in o ...
- RAC Concept
1. RAC的高可用性 RAC的高可用性主要包含以下几点: 1> 实现节点间的负载均衡. 2> 实现失败切换的功能. 3> 通过Service组件来控制客户端的访问路径. 4> ...
- 实用js代码大全
实用js代码大全 //过滤数字 <input type=text onkeypress="return event.keyCode>=48&&event.keyC ...
- C语言打印记事本内搜索字符串所在行信息
本程序采用C语言编写,使用方法: 1.双击“甲骨文字符串查询作品.exe”运行程序; 2.运行前请确保此可执行程序目录下有1.txt文件. 3.根据提示输入一个字符串,程序将显示存在所搜索字符串的所有 ...
- html/css基础篇——iframe和frame的区别【转】
转自共享圈的使用iframe的优缺点,为什么少用iframe以及iframe和frame的区别.其中本人不认同的地方有做小修改 注:HTML5不再支持使用frame,iframe只有src 属性 一. ...