Scalaz(7)- typeclass:Applicative-idomatic function application

2024-04-09 05:08

本文主要是介绍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 pointfinal 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
12.point[Configure]                               //> res2: Exercises.ex4.Configure[Int] = Exercises.ex4$Configure$$anon$3@6302168//| 9
5.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(1),Configure(2))(((_: Int) + (_: Int)).point[Configure])//> res4: Exercises.ex4.Configure[Int] = Exercises.ex4$Configure$$anon$3@64cd705//| f

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

(Configure(1) <*> {Configure(2) <*> {Configure(3) <*> {(((_: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(1),Configure(2))(((_: Int) + (_: Int)))).get//> res6: Int = 3
(^(Configure(1),Configure(2))((_:Int)+(_:Int))).get//> res7: Int = 3
(^^(Configure(1),Configure(2),Configure(3))((_: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(1) |@| Configure(2) |@| Configure(3))((_: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(123))//> res10: Exercises.ex4.Configure[(String, Int)] = Exercises.ex4$Configure$$an//| on$3@5ffead27
Apply[Configure].tuple3(Configure("abc"),Configure(123),Configure(true))//> res11: Exercises.ex4.Configure[(String, Int, Boolean)] = Ex<pre name="code" class="plain">

 


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


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(1),Some(2))                              //> 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.DriverManagerval 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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/887245

相关文章

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

AutoGen Function Call 函数调用解析(一)

目录 一、AutoGen Function Call 1.1 register_for_llm 注册调用 1.2 register_for_execution 注册执行 1.3 三种注册方法 1.3.1 函数定义和注册分开 1.3.2 定义函数时注册 1.3.3  register_function 函数注册 二、实例 本文主要对 AutoGen Function Call

(南京观海微电子)——GH7006 Application Note

Features ⚫ Single chip solution for a WXGA α-Si type LCD display ⚫ Integrate 1200 channel source driver and timing controller ⚫ Display Resolution: ◼ 800 RGB x 480 ◼ 640 RGB x 480 ⚫ Display int

(function() {})();只执行一次

测试例子: var xx = (function() {     (function() { alert(9) })(); alert(10)     return "yyyy";  })(); 调用: alert(xx); 在调用的时候,你会发现只弹出"yyyy"信息,并不见弹出"10"的信息!这也就是说,这个匿名函数只在立即调用的时候执行一次,这时它已经赋予了给xx变量,也就是只是

js私有作用域(function(){})(); 模仿块级作用域

摘自:http://outofmemory.cn/wr/?u=http%3A%2F%2Fwww.phpvar.com%2Farchives%2F3033.html js没有块级作用域,简单的例子: for(var i=0;i<10;i++){alert(i);}alert(i); for循环后的i,在其它语言像c、java中,会在for结束后被销毁,但js在后续的操作中仍然能访

rtklib.h : RTKLIB constants, types and function prototypes 解释

在 RTKLIB 中,rtklib.h 是一个头文件,包含了与 RTKLIB 相关的常量、类型和函数原型。以下是该头文件的一些常见内容和翻译说明: 1. 常量 (Constants) rtklib.h 中定义的常量通常包括: 系统常量: 例如,GPS、GLONASS、GALILEO 等系统的常量定义。 时间常量: 如一年、一天的秒数等。 精度常量: 如距离、速度的精度标准。 2. 类型

【AI大模型应用开发】2.1 Function Calling连接外部世界 - 入门与实战(1)

Function Calling是大模型连接外部世界的通道,目前出现的插件(Plugins )、OpenAI的Actions、各个大模型平台中出现的tools工具集,其实都是Function Calling的范畴。时下大火的OpenAI的GPTs,原理就是使用了Function Calling,例如联网检索、code interpreter。 本文带大家了解下Function calling,看

Vite + Vue3 +Vant4出现Toast is not a function

今天写前端的时候出现了这个问题搞了我一会 搜集原因: 1:是vant版本的问题,Toast()的方法是vant3版本的写法,而我用的是vant4,vant4中的写法改成了showToast()方法,改正过来 import {showToast} from "vant";  发现还是报错,说是找不到对应的样式文件 2:Vant 从 4.0 版本开始不再支持 babel-plugin-i

git中,隐藏application.properties文件,修改不用提交了

git中,隐藏application.properties文件,修改不用提交了 A、将文件名放入 .gitignore 文件中 B、执行git命令隐藏文件         执行在ide上执行命令         a、执行隐藏命令 git rm --cached src/main/resources/application.properties          b、执行提交命