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

1 trait Applicative[F[_]] extends Apply[F] { self =>
2   ////
3   def point[A](a: => A): F[A]
4
5   // alias for point
6   final def pure[A](a: => A): F[A] = point(a)
7 。。。

我们首先需要实现抽象函数point,然后由于Applicative继承了Apply,我们看看Apply trait有什么抽象函数需要实现的;scalaz/Apply.scala

1 trait Apply[F[_]] extends Functor[F] { self =>
2   ////
3   def ap[A,B](fa: => F[A])(f: => F[A => B]): F[B]
4 。。。

我们还需要实现抽象函数ap。注意Apply又继承了Functor,所以我们还需要实现map,一旦实现了Applicative实例就能同时获取了Functor实例。

现在我们先设计一个自定义类型作为下面的范例:

1 trait Configure[+A] {
2     def get: A
3 }
4 object Configure {
5     def apply[A](data: => A) = new Configure[A] { def get = data }
6 }
7 Configure("env string")                           //> res0: Exercises.ex4.Configure[String] = [email protected]
8                                                   //| d08e

Configure[+A]是个典型的FP类型。通过实现特殊命名apply的函数作为类型构建器,我们可以这样构建实例:Configure("some string")。现在我们按照scalaz隐式解析(implicit resolution)惯例在伴生对象(companion object)里定义隐式Applicative实例:

 1 import scalaz._
 2 import Scalaz._
 3 object ex4 {
 4 trait Configure[+A] {
 5     def get: A
 6 }
 7 object Configure {
 8     def apply[A](data: => A) = new Configure[A] { def get = data }
 9     implicit val configFunctor = new Functor[Configure] {
10         def map[A,B](ca: Configure[A])(f: A => B): Configure[B] = Configure(f(ca.get))
11     }
12     implicit val configApplicative = new Applicative[Configure] {
13         def point[A](a: => A) = Configure(a)
14         def ap[A,B](ca: => Configure[A])(cfab: => Configure[A => B]): Configure[B] = cfab map {fab => fab(ca.get)}
15     }
16 }

由于Apply继承了Functor,我们必须先获取Configure的Functor实例。现在我们可以针对Configure类型使用Applicative typeclass的功能函数了。Applicative typeclass的组件函数可以分为几种主要类型:

1、Applicative实例构建函数,point:

1 "abc".point[Configure]                            //> res1: Exercises.ex4.Configure[String] = [email protected]
2                                                   //| 9631
3 12.point[Configure]                               //> res2: Exercises.ex4.Configure[Int] = [email protected]
4                                                   //| 9
5 5.point[Option]                                   //> res3: Option[Int] = Some(5)

看款式应该是通过隐式转换实现的:scalaz/syntax/ApplicativeSyntax.scala

 1 trait ToApplicativeOps extends ToApplicativeOps0 with ToApplyOps {
 2   implicit def ToApplicativeOps[F[_],A](v: F[A])(implicit F0: Applicative[F]) =
 3     new ApplicativeOps[F,A](v)
 4
 5   ////
 6   implicit def ApplicativeIdV[A](v: => A) = new ApplicativeIdV[A] {
 7     lazy val self = v
 8   }
 9
10   trait ApplicativeIdV[A] extends Ops[A] {
11     def point[F[_] : Applicative]: F[A] = Applicative[F].point(self)
12     def pure[F[_] : Applicative]: F[A] = Applicative[F].point(self)
13     def η[F[_] : Applicative]: F[A] = Applicative[F].point(self)
14   }  ////
15 }

是通过implicit def ApplicativeIDV[A](v: => A)实现的。

2、对F[T}类型进行F[A =>B]式的函数施用(从管道里提供作用函数)。施用函数款式是这样的:

1   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, 反之不可:

1         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

1  def ap2[A,B,C](fa: => F[A], fb: => F[B])(f: F[(A,B) => C]): F[C] =
2     ap(fb)(ap(fa)(map(f)(_.curried)))
3   def ap3[A,B,C,D](fa: => F[A], fb: => F[B], fc: => F[C])(f: F[(A,B,C) => D]): F[D] =
4     ap(fc)(ap2(fa,fb)(map(f)(f => ((a:A,b:B) => (c:C) => f(a,b,c)))))
5   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] =
6     ap2(fc, fd)(ap2(fa,fb)(map(f)(f => ((a:A,b:B) => (c:C, d:D) => f(a,b,c,d)))))
7   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] =
8     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)))))
9 ...

试着在Configure类型上使用ap:

1 Apply[Configure].ap2(Configure(1),Configure(2))(((_: Int) + (_: Int)).point[Configure])
2                                                   //> res4: Exercises.ex4.Configure[Int] = [email protected]
3                                                   //| f或者用注入方法(injected method)<*>:scalaz/Syntax/ApplySyntax.scala

或者用注入方法(injected method)<*>:scalaz/Syntax/ApplySyntax.scala

1 (Configure(1) <*> {Configure(2) <*> {Configure(3) <*> {(((_:Int)+(_:Int)+(_:Int)).curried).point[Configure]}}}).get
2                                                   //> res5: Int = 6

以上的Apply[Configure]是通过Apply typeclass的构建函数apply实现的:scalaz/Apply.scala

1 object Apply {
2   @inline def apply[F[_]](implicit F: Apply[F]): Apply[F] = F

3、简化一下ap的写法,只用提供f:(A,B) => C这样的基本操作函数:scalaz/Apply.scala

1  def apply2[A, B, C](fa: => F[A], fb: => F[B])(f: (A, B) => C): F[C] =
2     ap(fb)(map(fa)(f.curried))
3   def apply3[A, B, C, D](fa: => F[A], fb: => F[B], fc: => F[C])(f: (A, B, C) => D): F[D] =
4     apply2(apply2(fa, fb)((_, _)), fc)((ab, c) => f(ab._1, ab._2, c))
5   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] =
6     apply2(apply2(fa, fb)((_, _)), apply2(fc, fd)((_, _)))((t, d) => f(t._1, t._2, d._1, d._2))
7   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] =
8     apply2(apply3(fa, fb, fc)((_, _, _)), apply2(fd, fe)((_, _)))((t, t2) => f(t._1, t._2, t._3, t2._1, t2._2))
9 ...

用在Configure类型上:

1 (Apply[Configure].apply2(Configure(1),Configure(2))(((_: Int) + (_: Int)))).get
2                                                   //> res6: Int = 3
3 (^(Configure(1),Configure(2))((_:Int)+(_:Int))).get
4                                                   //> res7: Int = 3
5 (^^(Configure(1),Configure(2),Configure(3))((_:Int)+(_:Int)+(_:Int))).get
6                                                   //> res8: Int = 6

这个^,^^是apply2,apply3的注入方法:scalaz/syntax/ApplySyntax.scala

 1   def ^[A,B,C](fa: => F[A], fb: => F[B])(
 2                f: (A, B) => C): F[C] =
 3     F.apply2(fa, fb)(f)
 4
 5   def ^^[A,B,C,D](fa: => F[A], fb: => F[B], fc: => F[C])(
 6                  f: (A, B, C) => D): F[D] =
 7     F.apply3(fa, fb, fc)(f)
 8
 9   def ^^^[A,B,C,D,E](fa: => F[A], fb: => F[B], fc: => F[C], fd: => F[D])(
10                    f: (A,B,C,D) => E): F[E] =
11     F.apply4(fa, fb, fc, fd)(f)
12
13   def ^^^^[A,B,C,D,E,I](fa: => F[A], fb: => F[B], fc: => F[C], fd: => F[D], fe: => F[E])(
14                      f: (A,B,C,D,E) => I): F[I] =
15     F.apply5(fa, fb, fc, fd, fe)(f)
16 ...

另一种表达方式是通过ApplicativeBuilder typeclass实现的注入方法|@|:

1 ((Configure(1) |@| Configure(2) |@| Configure(3))((_:Int)+(_:Int)+(_:Int))).get
2                                                   //> res9: Int = 6

效果是一样的。我们用一个实际的简单例子来示范一下Applicative的具体函数施用:

 1 def configName(name: String): Configure[String] = Configure(name)
 2                                                   //> configName: (name: String)Exercises.ex4.Configure[String]
 3 def configID(userid: String): Configure[String] = Configure(userid)
 4                                                   //> configID: (userid: String)Exercises.ex4.Configure[String]
 5 def configPwd(pwd: String): Configure[String] = Configure(pwd)
 6                                                   //> configPwd: (pwd: String)Exercises.ex4.Configure[String]
 7 case class WebLogForm(name:String, id: String, pwd: String)
 8
 9 def logOnWeb(name: String, userid: String, pwd: String) =
10   ^^(configName(name),configID(userid), configPwd(pwd))(WebLogForm(_,_,_))
11                                                   //> logOnWeb: (name: String, userid: String, pwd: String)Exercises.ex4.Configur
12                                                   //| e[Exercises.ex4.WebLogForm]
13 def logOnWeb1(name: String, userid: String, pwd: String) =
14   (configName(name) |@| configID(userid) |@| configPwd(pwd))(WebLogForm(_,_,_))
15                                                   //> logOnWeb1: (name: String, userid: String, pwd: String)Exercises.ex4.Configu
16                                                   //| re[Exercises.ex4.WebLogForm]

值得注意的是:用Applicative施用configName,configID,configPwd时,这三个函数之间没有依赖关系。特别适合并行运算或fail-fast,因为无论如何这三个函数都一定会运行。这种Applicative的函数施用体现了它在并行运算中的优势。

4、Applicative style 函数施用。上面提到的|@|操作并不是一种操作函数而是一种层级式持续函数施用模式。具体实现在ApplicativeBuilder typeclass里:scalaz/ApplicativeBuilder.scala

 1 private[scalaz] trait ApplicativeBuilder[M[_], A, B] {
 2   val a: M[A]
 3   val b: M[B]
 4
 5   def apply[C](f: (A, B) => C)(implicit ap: Apply[M]): M[C] = ap.apply2(a, b)(f)
 6
 7   def tupled(implicit ap: Apply[M]): M[(A, B)] = apply(Tuple2.apply)
 8
 9   def ?[C](cc: M[C]) = new ApplicativeBuilder3[C] {
10     val c = cc
11   }
12
13   def |@|[C](cc: M[C]) = ?(cc)
14
15   sealed trait ApplicativeBuilder3[C] {
16     val c: M[C]
17
18     def apply[D](f: (A, B, C) => D)(implicit ap: Apply[M]): M[D] = ap.apply3(a, b, c)(f)
19
20     def tupled(implicit ap: Apply[M]): M[(A, B, C)] = apply(Tuple3.apply)
21
22     def ?[D](dd: M[D]) = new ApplicativeBuilder4[D] {
23       val d = dd
24     }
25
26     def |@|[D](dd: M[D]) = ?(dd)
27
28     sealed trait ApplicativeBuilder4[D] {
29       val d: M[D]
30
31       def apply[E](f: (A, B, C, D) => E)(implicit ap: Apply[M]): M[E] = ap.apply4(a, b, c, d)(f)
32
33       def tupled(implicit ap: Apply[M]): M[(A, B, C, D)] = apply(Tuple4.apply)
34
35       def ?[E](ee: M[E]) = new ApplicativeBuilder5[E] {
36         val e = ee
37       }
38
39       def |@|[E](ee: M[E]) = ?(ee)
40 ...

可以看得出(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

1  def tuple2[A,B](fa: => F[A], fb: => F[B]): F[(A,B)] =
2     apply2(fa, fb)((_,_))
3   def tuple3[A,B,C](fa: => F[A], fb: => F[B], fc: => F[C]): F[(A,B,C)] =
4     apply3(fa, fb, fc)((_,_,_))
5   def tuple4[A,B,C,D](fa: => F[A], fb: => F[B], fc: => F[C], fd: => F[D]): F[(A,B,C,D)] =
6     apply4(fa, fb, fc, fd)((_,_,_,_))
7   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)] =
8     apply5(fa, fb, fc, fd, fe)((_,_,_,_,_))
9 ...

比如:

1 Apply[Configure].tuple2(Configure("abc"),Configure(123))
2                                                   //> res10: Exercises.ex4.Configure[(String, Int)] = Exercises.ex4$Configure$$an
3                                                   //| [email protected]
4 Apply[Configure].tuple3(Configure("abc"),Configure(123),Configure(true))
5                                                   //> res11: Exercises.ex4.Configure[(String, Int, Boolean)] = Ex

具体用来干什么,我现在还说不上来。

6、把一个普通函数升格(lift)成高阶函数,如:(A,B) => C 升格成 (F[A],F[B]) => F[C]: scalaz/Apply.scala

1  def lift2[A, B, C](f: (A, B) => C): (F[A], F[B]) => F[C] =
2     apply2(_, _)(f)
3   def lift3[A, B, C, D](f: (A, B, C) => D): (F[A], F[B], F[C]) => F[D] =
4     apply3(_, _, _)(f)
5   def lift4[A, B, C, D, E](f: (A, B, C, D) => E): (F[A], F[B], F[C], F[D]) => F[E] =
6     apply4(_, _, _, _)(f)
7   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] =
8     apply5(_, _, _, _, _)(f)
9 ...

这种函数升格方式在用FP方式使用OOP库函数时更加方便。最典型的例子是Option类型在FP中结合OOP函数库的使用。如果我们希望在使用OOP库函数时使用Option类型的输入参数和返回值,那我们就可以通过函数升格(function lifting)来实现这样的功能。

1 val of2 = Apply[Option].lift2((_: Int) + (_: Int))//> of2  : (Option[Int], Option[Int]) => Option[Int] = <function2>
2 of2(Some(1),Some(2))                              //> res12: Option[Int] = Some(3)
3 val of3 = Apply[List].lift3((s1: String, s2: String, s3: String) => s1 + " "+s2+" "+s3)
4                                                   //> of3  : (List[String], List[String], List[String]) => List[String] = <functi
5                                                   //| on3>
6 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作为参数,我可以这样:

1 import java.sql.DriverManager
2
3 val sqlConnect = Apply[Configure] lift3 java.sql.DriverManager.getConnection
4                                                   //> sqlConnect  : (Exercises.ex4.Configure[String], Exercises.ex4.Configure[Str
5                                                   //| ing], Exercises.ex4.Configure[String]) => Exercises.ex4.Configure[java.sql.
6                                                   //| Connection] = <function3>
7 sqlConnect(Configure("Source"),Configure("User"),Configure("Password"))
8                                                   //> res12: Exercises.ex4.Configure[java.sql.Connection] = Exercises.ex4$Configu
9                                                   //| [email protected]

的确这样可以使我继续在FP模式中工作。

总结来说:Applicative typeclass提供了一套函数施用方式。它是通过一个包嵌在容器结构的高阶函数实现管道内的施用。Applicative typeclass还提供了方法将普通函数升格到高阶函数使FP和OOP混合模式的函数施用更安全方便。

 
时间: 2024-08-03 15:15:14

Scalaz(7)- typeclass:Applicative-idomatic function application的相关文章

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

中间插播了几篇scalaz数据类型,现在又要回到Monad专题.因为FP的特征就是Monad式编程(Monadic programming),所以必须充分理解认识Monad.熟练掌握Monad运用.曾经看到一段对Monad的描述:“Monadic for-comprehension就是一种嵌入式编程语言,由它的Monad提供它的语法”.但如果每一种Monad的for-comprehension都独立提供一套语法的话,这种编程语言就显得十分单调.功能简单了.那么既然是FP,我们应该可以通过函数组合

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

从上面多篇的讨论中我们了解到scalaz-stream代表一串连续无穷的数据或者程序.对这个数据流的处理过程就是一个状态机器(state machine)的状态转变过程.这种模式与我们通常遇到的程序流程很相似:通过程序状态的变化来推进程序进展.传统OOP式编程可能是通过一些全局变量来记录当前程序状态,而FP则是通过函数组合来实现状态转变的.这个FP模式讲起来有些模糊和抽象,但实际上通过我们前面长时间对FP编程的学习了解到FP编程讲究避免使用任何局部中间变量,更不用说全局变量了.FP程序的数据A是

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

STL笔记(6)标准库:标准库中的排序算法 标准库:标准库中的排序算法The Standard Librarian: Sorting in the Standard Library Matthew Austern http://www.cuj.com/experts/1908/austern.htm?topic=experts 用泛型算法进行排序    C++标准24章有一个小节叫“Sorting and related operations”.它包含了很多对已序区间进行的操作,和三个排序用泛型

藏妹子之处(excel)(C++) 密码:无

收 藏   藏妹子之处(excel) 难度级别:C: 运行时间限制:1000ms: 运行空间限制:256000KB: 代码长度限制:2000000B 试题描述 今天CZY又找到了三个妹子,有着收藏爱好的他想要找三个地方将妹子们藏起来,将一片空地抽象成一个R行C列的表格,CZY要选出3个单元格.但要满足如下的两个条件:(1)任意两个单元格都不在同一行.(2)任意两个单元格都不在同一列.选取格子存在一个花费,而这个花费是三个格子两两之间曼哈顿距离的和(如(x1,y1)和(x,y2)的曼哈顿距离为|x

Java深度历险(三)——Java线程?:基本概念、可见性与同步

开发高性能并发应用不是一件容易的事情.这类应用的例子包括高性能Web服务器.游戏服务器和搜索引擎爬虫等.这样的应用可能需要同时处理成千上万个请求.对于这样的应用,一般采用多线程或事件驱动的架构.对于Java来说,在语言内部提供了线程的支持.但是Java的多线程应用开发会遇到很多问题.首先是很难编写正确,其次是很难测试是否正确,最后是出现问题时很难调试.一个多线程应用可能运行了好几天都没问题,然后突然就出现了问题,之后却又无法再次重现出来.如果在正确性之外,还需要考虑应用的吞吐量和性能优化的话,就

Linux中断(interrupt)子系统之一:中断系统基本原理

这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于ARM这一体系架构,其他架构的原理其实也差不多,区别只是其中的硬件抽象层.内核版本基于3.3.虽然内核的版本不断地提升,不过自从上一次变更到当前的通用中断子系统后,大的框架性的东西并没有太大的改变. /*****************************************************************************************************/ 声明:本博内容

(一)、BOM:Browser Object Model

BOM window 打开关闭窗口 窗口大小和窗口位置 ****定时器 (一).BOM:Browser Object Model 浏览器对象模型:用来访问和操作浏览器窗口,使JS有能力与浏览器交互. 专门操作浏览器窗口的API--没有标准,有兼容性问题 浏览器对象模型的主要对象 window:代表整个窗口是BOM的根对象 2个角色:1.代替global称为全局作用域对象  2.封装所有DOM API 和BOM API 以下为window的子对象 1.history:封装当前窗口打开后,成功访问过

微信公众平台开发教程(四) 实例入门:机器人(附源码)

微信公众平台开发教程(四) 实例入门:机器人(附源码) 上一篇文章,写了基本框架,可能很多人会觉得晕头转向,这里提供一个简单的例子来予以说明,希望能帮你解开谜团. 一.功能介绍 通过微信公众平台实现在线客服机器人功能.主要的功能包括:简单对话.查询天气等服务. 这里只是提供比较简单的功能,重在通过此实例来说明公众平台的具体研发过程.只是一个简单DEMO,如果需要的话可以在此基础上进行扩展. 当然后续我们还会推出比较复杂的应用实例. 二.具体实现 1.提供访问接口 这里不再赘述,参照上一章,微信公

STL笔记(5)条款49:学习破解有关STL的编译器诊断信息

STL笔记(5)条款49:学习破解有关STL的编译器诊断信息 条款49:学习破解有关STL的编译器诊断信息 用一个特定的大小定义一个vector是完全合法的, vector<int> v(10);    // 建立一个大小为10的vector 而string在很多方面像vector,所以你可能希望可以这么做: string s(10);        // 常识建立一个大小为10的string 这不能编译.string没有带有一个int实参的构造函数.我的一个STL平台像这样告诉我那一点: e