FunctionK 类型参数的界限

2023-12-02

我在用着cats 自由单子。这是代数的简化版本:

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)
        }
}

我尝试过诸如引入边界之类的事情apply (like apply[A <: Resource : Format : Definition])这不起作用。

我明白那个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

  1. 每种类型都需要隐式T发生在get[T],因此必须在 DSL 程序运行时插入并存储它们,不是当它是executed。这解决了隐式问题。
  2. 有一个粘合自然变换的通用策略~>从几个有限的自然变换 trait RNT[R, F[_ <: R], G[_]]{ def apply[A <: R](x: F[A]): G[A] }使用模式匹配。这解决了问题A <: Resource类型绑定。详细信息如下。

在您的问题中,您有两个独立的问题:

  1. 隐含的Format and Definition
  2. <: Resource-类型绑定

我想单独处理这两个问题,并为这两个问题提供可重用的解决方案策略。然后我会将这两种策略应用于您的问题。

我的回答结构如下:

  1. 首先,我按照我的理解总结一下你的问题。
  2. 然后我将解释如何处理隐式,忽略类型绑定。
  3. 然后我将处理类型绑定,这次忽略隐式。
  4. 最后,我将这两种策略应用于您的特定问题。

从今往后,我假设你已经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图书馆。


设置

本节的目标是确保我解决正确的问题,并提供非常简单的模型定义 的Resource, Format, Client等等,所以这个答案是独立的 并且可以编译。

我假设您想使用以下方法构建一种特定于领域的语言Free单子。 理想情况下,您希望有一个看起来大约像这样的 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一些人提供的方法Client您无法修改:

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的方法Client有一个类型绑定,并且 它需要额外的隐式。

为 Free monad 定义解释器时处理隐式

我们首先假设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, the apply of FunctionK应该 能够处理任意长的程序类型Dsl[X]生产Future[X].

  • 任意长的程序类型Dsl[X]可以包含无限数量的get[T1], ..., get[Tn]不同类型的命令T1, ..., Tn.

  • 对于每一个T1, ..., Tn,你必须得到一个Format[T_i] and Definition[T_i]某处。

  • 这些隐式参数必须由编译器提供。

  • 当你解释整个类型的程序时Dsl[X],仅类型X但不是类型T1, ..., Tn可用, 所以编译器无法插入所有必需的Definitions and Format位于呼叫站点。

  • 因此,所有的Definitions and Formats 必须作为隐式参数提供给get[T_i]当你是建造 the Dsl- 程序,而不是当你在的时候口译 it.

解决方案是添加Format[A] and Definition[A]作为成员Get[A]案例类, 并定义get[A] with lift[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))

现在,我们可以定义第一个近似值~>-口译员,至少 可以应对隐式:

val clientInterpreter_1: (DslOp ~> Future) = new (DslOp ~> Future) {
  def apply[A](op: DslOp[A]): Future[A] = op match {
    case Get(name, f, d) => Client.get(name)(f, d)
  }
}

定义 DSL 操作的案例类中的类型界限

现在,让我们单独处理类型绑定。假设你的Client不需要任何隐式,但施加了额外的限制A:

import scala.concurrent.Future

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

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

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

看起来几乎就像~>,但还有一个额外的A <: R限制。现在我们 可以定义accept in Get:

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, Get, 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, F[_ <: 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, Get, G]): G[A] = f(this)
}

type Dsl[A] = Free[DslOp, A]
def get[A <: Resource]
  (name: String)
  (implicit fmt: Format[A], dfn: Definition[A])
: Dsl[A] = liftF[DslOp, A](Get[A](name, 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, Get, 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.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FunctionK 类型参数的界限 的相关文章

  • 通过 Gradle 进行测试时记录日志

    在测试时 Gradle 似乎将 stdout stderr 重定向到project dir build reports tests index html 有没有办法避免这种重定向 并将内容打印到控制台 附加信息 这是一个 Scala 2 9
  • 副作用是纯函数中找不到的一切吗?

    可以肯定地说 以下二分法成立 每个给定的函数是 要么纯粹 或有副作用 如果是这样 函数的 副作用就是纯函数中找不到的任何东西 这很大程度上取决于您选择的定义 可以公平地说 函数是pure or impure 纯函数始终返回相同的结果并且不会
  • Scala 2.10、Double.isNaN 和拳击

    在 Scala 2 10 中 是someDouble isNaN预计装箱 运行我的代码调用 isNaN通过反编译器 我仍然看到对double2Double在我的代码中 鉴于新的AnyVal在 2 10 中工作 我希望它不会比java lan
  • Scalaz 7 Iteratee 处理大型 zip 文件(OutOfMemoryError)

    我正在尝试使用 scalaz iteratee 包在恒定空间中处理大型 zip 文件 我需要对 zip 文件中的每个文件执行一个长时间运行的进程 这些进程可以 并且应该 并行运行 我创建了一个EnumeratorT使每个膨胀ZipEntry
  • 了解如何使用 apply 和 unappy

    我试图更好地理解 的正确用法apply and unapply方法 考虑到我们想要序列化和反序列化的对象 这是正确的用法吗 即斯卡拉方式 的使用apply and unapply case class Foo object Foo appl
  • 哪些 ORM 与 Scala 配合得很好? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • Spark RDD默认分区数

    版本 Spark 1 6 2 Scala 2 10 我正在执行以下命令spark shell 我试图查看 Spark 默认创建的分区数量 val rdd1 sc parallelize 1 to 10 println rdd1 getNum
  • 自定义 NIO 文件系统无法通过 SBT 的测试任务加载

    为了进行测试 我使用内存中的 NIOFileSystem执行 memoryfs https github com openCage memoryfs 我以前已经利用过它 并且它似乎运行良好 例如梅文 然而 现在 在SBT项目中 不可能初始化
  • 如何使用 apply/unapply 方法重现案例类行为?

    我尝试用普通类和伴生对象替换案例类 但突然出现类型错误 编译良好的代码 综合示例 trait Elem A B def C other Elem C A Elem C B other match case Chain head tail g
  • 最小重复子串

    我正在看 Perl代码高尔夫页面 http www perlmonks org node id 82878 不要问为什么 并遇到了这个 第 3 洞 最小重复图案 编写一个子例程 它接受一个字符串 该字符串可能包含 重复模式 并返回最小的重复
  • 如何抑制spark输出控制台中的“Stage 2===>”?

    我有数据帧并试图获取不同的计数并且能够成功获取不同的计数 但是每当 scala 程序执行时我都会收到此消息 Stage 2 gt 1 1 2 我如何在控制台中抑制特定的此消息 val countID dataDF select substr
  • 有没有办法捕获 Spark 中使用通配符读取的多个 parquet 文件的输入文件名?

    我使用 Spark 将多个 parquet 文件读取到单个 RDD 中 并使用标准通配符路径约定 换句话说 我正在做这样的事情 val myRdd spark read parquet s3 my bucket my folder parq
  • 如何发现 Scala 远程 Actor 已死亡?

    在 Scala 中 当另一个 远程 actor 终止时 可以通过设置 trapExit 标志并以第二个 actor 作为参数调用 link 方法来通知一个 actor 在这种情况下 当远程参与者通过调用 exit 结束其工作时 第一个参与者
  • 通用特征的隐式转换

    我正在实现一个数据结构 并希望用户能够使用任何类型作为密钥 只要他提供一个合适的密钥类型来包装它 我有这个关键类型的特质 这个想法是进行从基类型到键类型的隐式转换 反之亦然 实际上 只使用基类型 该特征看起来像这样 trait Key T
  • Play Framework 2.3 (Scala) 中的自定义 JSON 验证约束

    我设法使用自定义约束实现表单验证 但现在我想对 JSON 数据执行相同的操作 如何将自定义验证规则应用于 JSON 解析器 示例 客户端的 POST 请求包含用户名 username 我不仅要确保该参数是非空文本 而且还要确保该用户确实存在
  • Scala 模式匹配变量绑定

    为什么提取器返回时不能以 样式绑定变量Option
  • 使用spark phoenix从表中读取rdd分区号为1

    当我运行我的火花代码时 val sqlContext spark sqlContext val noact table primaryDataProcessor getTableData sqlContext zookeeper table
  • 对 Scala Not Null 特征的库支持

    Notice 从 Scala 2 11 开始 NotNull已弃用 据我了解 如果您希望引用类型不可为空 则必须混合魔法NotNull特征 编译器会自动阻止你输入null 可以值在里面 看到这个邮件列表线程 http www nabble
  • 使用 scala 集合 - CanBuildFrom 麻烦

    我正在尝试编写一个接受任何类型集合的方法CC 并将其映射到一个新的集合 相同的集合类型但不同的元素类型 我正在挣扎 基本上我正在尝试实施map but 不在集合本身上 问题 我正在尝试实现一个带有签名的方法 它看起来有点像 def map
  • Scala 解析器组合器的运算符优先级

    我正在研究需要考虑运算符优先级的解析逻辑 我的需求并不太复杂 首先 我需要乘法和除法比加法和减法具有更高的优先级 例如 1 2 3 应视为 1 2 3 这是一个简单的例子 但你明白了 我需要将更多自定义标记添加到优先级逻辑中 我可以根据此处

随机推荐