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

相关文章

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、执行提交命

Ollama Qwen2 支持 Function Calling

默认 Ollama 中的 Qwen2 模型不支持 Function Calling,使用默认 Qwen2,Ollama 会报错。本文将根据官方模板对 ChatTemplate 进行改进,使得Qwen2 支持 Tools,支持函数调用。 Ollama 会检查对话模板中是否存在 Tools,如果不存在就会报错,下面的代码是 Ollama 解析模板的代码。 Ollama 3.1 是支持 Tools

android kotlin复习 Anonymous function 匿名函数

1、还是先上个图,新建kt: 2、代码: package com.jstonesoft.myapplication.testfun main(){val count = "helloworld".count()println(count);println("------------------------")var count2 = "helloworld".count(){it ==