加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

scala – FunctionK的类型参数的界限

发布时间:2020-12-16 18:10:01 所属栏目:安全 来源:网络整理
导读:我正在使用猫 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))} 其中一个解释器将是第三方库
我正在使用猫 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程序时必须插入和存储它们,而不是在执行时.这解决了隐含问题.
>有一种粘合自然转变的一般策略?>来自几个受限制的自然变换特征RNT [R,F [_<:R],G [_]] {def apply [A<:R](x:F [A]):G [A]}使用模式匹配.这解决了A<:资源类型绑定的问题.详情如下.
在您的问题中,您有两个不同的问题:

>隐式格式和定义
>&lt ;:资源类型绑定

我想孤立地处理这两个问题中的每一个,并为两者提供可重用的解决方案策略.然后,我将对您的问题应用这两种策略.

我的答案结构如下:

>首先,我会按照我的理解总结你的问题.
>然后我将解释如何处理implicits,忽略类型绑定.
>然后我会处理类型绑定,这次忽略了含义.
>最后,我将这两种策略应用于您的特定问题.

从此以后,我假设您有scalaVersion 2.12.4,依赖项

libraryDependencies += "org.typelevel" %% "cats-core" % "1.0.1"
libraryDependencies += "org.typelevel" %% "cats-free" % "1.0.1"

并插入

import scala.language.higherKinds

在适当情况下.
请注意,解决方案策略并非特定于此特定scala版本或cats库.

设置

本节的目标是确保我正在解决正确的问题,并提供非常简单的模型定义
资源,格式,客户等,以便这个答案是独立的
并且可编译.

我假设您想使用Free monad构建一个特定于域的语言.
理想情况下,您希望有一个看起来大致相似的DSL(我使用名称DslOp进行操作,Dsl使用生成的自由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] = ???
}

在我们写下解决方案之前,让我们简要讨论为什么你不能提供所有解决方案
在?>中调用apply方法时必要的含义.

>当传递给foldMap时,应该使用FunctionK
能够处理Dsl [X]类型的任意长程序以产生Future [X].
> Dsl [X]类型的任意长程序可以包含无限数量的程序
获取[T1],…,获取不同类型T1,Tn的[Tn]命令.
>对于T1,Tn中的每一个,您必须在某处获得Format [T_i]和Definition [T_i].
>这些隐式参数必须由编译器提供.
>当您解释Dsl [X]类型的整个程序时,只有X类型而不是类型T1,Tn可用,
因此编译器无法在调用站点插入所有必需的定义和格式.
>因此,必须提供所有定义和格式作为隐式参数来获取[T_i]
当你构建Dsl程序时,而不是在解释时.

解决方案是将Format [A]和Definition [A]添加为Get [A]案例类的成员,
并使get [A]的定义与提升[DslOp,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

现在,让我们单独处理绑定的类型.假设您的客户
不需要任何暗示,但对A施加额外限制:

import scala.concurrent.Future

trait Resource
object Client {
  def get[A <: Resource](name: String): Future[A] = ???
}

如果您尝试以与中的相同方式记下clientInterpreter
在前面的例子中,你会注意到类型A太笼统了
因此,您无法使用Client.get中的Get [A]的内容.
相反,您必须找到其他类型信息A<:Resource的范围
不会丢失.实现它的一种方法是在Get本身上定义一个accept方法.
这种接受方法将取代完全一般的自然变换?>
能够使用受限域的自然变换.
这是一个模型的特征:

trait RestrictedNat[R,F[_ <: R],G[_]] {
  def apply[A <: R](fa: F[A]): G[A]
}

它看起来几乎像?>,但有一个额外的A<:R限制.现在我们
可以在Get中定义accept:

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,
Get_N,类型限制为R1,RN.一般的想法对应于
从较小的角度构造分段定义的自然变换
仅适用于某些子类型的碎片.

将解决方案策略应用于您的问题

现在我们可以将两种一般策略合并为一种解决方案
你的具体问题:

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问题.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读