scala – FunctionK的类型参数的界限
我正在使用猫
FreeMonad.这是代数的简化版本:
sealed trait Op[A] object Op { final case class Get[T](name: String) extends Op[T] type OpF[A] = Free[Op,A] def get[T](name: String): OpF[T] = liftF[Op,T](Get[T](name)) } 其中一个解释器将是第三方库的包装器,称为Client,其get方法的签名类似于: class Client { def get[O <: Resource](name: String) (implicit f: Format[O],d: Definition[O]): Future[O] = ??? } 我的问题是如何在我的实现中编码该需求? class FutureOp extends (Op ~> Future) { val client = new Client() def apply[A](fa: Op[A]): Future[A] = fa match { case Get(name: String) => client.get[A](name) } } 我尝试了向我的应用引入边界(比如应用[A<:资源:格式:定义])这样的东西. 我知道FunctionK是转换一阶类型的值的类型,但无论如何我可以编码类型参数的要求吗? 我打算像以下一样使用它: def run[F[_]: Monad,A](intp: Op ~> F,op: OpF[A]): F[A] = op.foldMap(intp) val p: Op.OpF[Foo] = Op.get[Foo]("foo") val i = new FutureOp() run(i,d) 解决方法
(我的原始答案包含相同的想法,但显然它没有提供足够的实现细节.这一次,我写了一个更详细的分步指南,讨论了每个中间步骤.每个部分都包含一个单独的可编译代码片段. )
TL; DR > get [T]中出现的每种类型T都需要隐含,因此在构造DSL程序时必须插入和存储它们,而不是在执行时.这解决了隐含问题. >隐式格式和定义 我想孤立地处理这两个问题中的每一个,并为两者提供可重用的解决方案策略.然后,我将对您的问题应用这两种策略. 我的答案结构如下: >首先,我会按照我的理解总结你的问题. 从此以后,我假设您有scalaVersion 2.12.4,依赖项 libraryDependencies += "org.typelevel" %% "cats-core" % "1.0.1" libraryDependencies += "org.typelevel" %% "cats-free" % "1.0.1" 并插入 import scala.language.higherKinds 在适当情况下. 设置 本节的目标是确保我正在解决正确的问题,并提供非常简单的模型定义 我假设您想使用Free monad构建一个特定于域的语言. import cats.free.Free import cats.free.Free.liftF sealed trait DslOp[A] case class Get[A](name: String) extends DslOp[A] type Dsl[A] = Free[DslOp,A] def get[A](name: String): Dsl[A] = liftF[DslOp,A](Get[A](name)) 它定义了一个命令get,它可以在给定字符串的情况下获取类型A的对象 稍后,您希望使用某些客户端提供的get方法来解释此DSL import scala.concurrent.Future trait Resource trait Format[A <: Resource] trait Definition[A <: Resource] object Client { def get[A <: Resource](name: String) (implicit f: Format[A],d: Definition[A]): Future[A] = ??? } 您的问题是客户端的get方法有一个类型绑定,那 在为Free monad定义解释器时处理implicits 让我们首先假装客户端中的get方法需要隐含,但是 import scala.concurrent.Future trait Format[A] trait Definition[A] object Client { def get[A](name: String)(implicit f: Format[A],d: Definition[A]) : Future[A] = ??? } 在我们写下解决方案之前,让我们简要讨论为什么你不能提供所有解决方案 >当传递给foldMap时,应该使用FunctionK 解决方案是将Format [A]和Definition [A]添加为Get [A]案例类的成员, import cats.free.Free import cats.free.Free.liftF import cats.~> sealed trait DslOp[A] case class Get[A](name: String,f: Format[A],d: Definition[A]) extends DslOp[A] type Dsl[A] = Free[DslOp,A] def get[A](name: String)(implicit f: Format[A],d: Definition[A]) : Dsl[A] = liftF[DslOp,A](Get[A](name,f,d)) 现在,我们可以定义?> -interpreter的第一个近似值,至少这个近似值 val clientInterpreter_1: (DslOp ~> Future) = new (DslOp ~> Future) { def apply[A](op: DslOp[A]): Future[A] = op match { case Get(name,d) => Client.get(name)(f,d) } } 在定义DSL操作的case类中键入bounds 现在,让我们单独处理绑定的类型.假设您的客户 import scala.concurrent.Future trait Resource object Client { def get[A <: Resource](name: String): Future[A] = ??? } 如果您尝试以与中的相同方式记下clientInterpreter trait RestrictedNat[R,F[_ <: R],G[_]] { def apply[A <: R](fa: F[A]): G[A] } 它看起来几乎像?>,但有一个额外的A<:R限制.现在我们 import cats.free.Free import cats.free.Free.liftF import cats.~> sealed trait DslOp[A] case class Get[A <: Resource](name: String) extends DslOp[A] { def accept[G[_]](f: RestrictedNat[Resource,Get,G]): G[A] = f(this) } type Dsl[A] = Free[DslOp,A] def get[A <: Resource](name: String): Dsl[A] = liftF[DslOp,A](Get[A](name)) 并写下我们的翻译的第二个近似,没有任何 val clientInterpreter_2: (DslOp ~> Future) = new (DslOp ~> Future) { def apply[A](op: DslOp[A]): Future[A] = op match { case g @ Get(name) => { val f = new RestrictedNat[Resource,Future] { def apply[X <: Resource](g: Get[X]): Future[X] = Client.get(g.name) } g.accept(f) } } } 这个想法可以推广到任意数量的类型构造函数Get_1, 将解决方案策略应用于您的问题 现在我们可以将两种一般策略合并为一种解决方案 import scala.concurrent.Future import cats.free.Free import cats.free.Free.liftF import cats.~> // Client-definition with both obstacles: implicits + type bound trait Resource trait Format[A <: Resource] trait Definition[A <: Resource] object Client { def get[A <: Resource](name: String) (implicit fmt: Format[A],dfn: Definition[A]) : Future[A] = ??? } // Solution: trait RestrictedNat[R,G[_]] { def apply[A <: R](fa: F[A]): G[A] } sealed trait DslOp[A] case class Get[A <: Resource]( name: String,fmt: Format[A],dfn: Definition[A] ) extends DslOp[A] { def accept[G[_]](f: RestrictedNat[Resource,A] def get[A <: Resource] (name: String) (implicit fmt: Format[A],dfn: Definition[A]) : Dsl[A] = liftF[DslOp,fmt,dfn)) val clientInterpreter_3: (DslOp ~> Future) = new (DslOp ~> Future) { def apply[A](op: DslOp[A]): Future[A] = op match { case g: Get[A] => { val f = new RestrictedNat[Resource,Future] { def apply[X <: Resource](g: Get[X]): Future[X] = Client.get(g.name)(g.fmt,g.dfn) } g.accept(f) } } } 现在,clientInterpreter_3可以解决这两个问题:处理类型绑定问题通过为每个案例类定义一个RestrictedNat,对其类型参数施加上限,并且通过向DSL的get-method添加隐式参数列表来解决implicits问题. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |