gpt4 book ai didi

在隐式类中创建的 Scala 不兼容的嵌套类型

转载 作者:行者123 更新时间:2023-12-04 19:26:37 27 4
gpt4 key购买 nike

提供的代码片段是一个虚构的简约示例,仅用于演示问题,与实际业务逻辑类型无关。

在下面的代码中,我们有一个嵌套的 Entry在里面输入 Registry类型。

class Registry[T](name: String) {
case class Entry(id: Long, value: T)
}

这是有道理的,因为不同注册表的条目是不同的、无与伦比的类型。

然后我们可能有一个隐式的 Ops 类,例如,在测试中使用,它将我们的注册表绑定(bind)到一些测试存储实现,一个简单的可变映射
object testOps {

import scala.collection.mutable

type TestStorage[T] = mutable.Map[Long, T]

implicit class RegistryOps[T](val self: Registry[T])(
implicit storage: TestStorage[T]
) {

def getById(id: Long): Option[self.Entry] =
storage.get(id).map(self.Entry(id, _))

def remove(entry: self.Entry): Unit = storage - entry.id
}
}

问题是: Entry在 Ops 包装器内部构造的被视为与原始 Registry 对象无法比较的类型
object problem {

case class Item(name: String)

val items = new Registry[Item]("elems")

import testOps._

implicit val storage: TestStorage[Item] =
scala.collection.mutable.Map[Long, Item](
1L -> Item("whatever")
)
/** Compilation error:
found : _1.self.Entry where val _1: testOps.RegistryOps[problem.Item]
required: eta$0$1.self.Entry
*/
items.getById(1).foreach(items.remove)
}

问题是:有没有办法声明 Ops 签名以使编译器了解我们正在使用相同的内部类型? (
我也试过 self.type#EntryRegistryOps没有运气)
如果我错过了一些理解并且它们实际上是不同的类型,我将不胜感激任何解释和示例,为什么将它们视为相同可能会破坏类型系统。谢谢!

最佳答案

首先,值得注意的是,这里的隐含性并不是真正的问题——如果你写出如下内容,它会以完全相同的方式失败:

new RegistryOps(items).getById(1).foreach(e => new RegistryOps(items).remove(e))

有很多方法可以做你想做的事情,但它们并不令人愉快。一种是对隐式类进行脱糖,以便您可以让它为注册表值捕获更具体的类型:
class Registry[T](name: String) {
case class Entry(id: Long, value: T)
}

object testOps {
import scala.collection.mutable

type TestStorage[T] = mutable.Map[Long, T]

class RegistryOps[T, R <: Registry[T]](val self: R)(
implicit storage: TestStorage[T]
) {
def getById(id: Long): Option[R#Entry] =
storage.get(id).map(self.Entry(id, _))

def remove(entry: R#Entry): Unit = storage - entry.id
}

implicit def toRegistryOps[T](s: Registry[T])(
implicit storage: TestStorage[T]
): RegistryOps[T, s.type] = new RegistryOps[T, s.type](s)
}

这工作得很好,无论是你使用它的形式,还是稍微更明确一点:
scala> import testOps._
import testOps._

scala> case class Item(name: String)
defined class Item

scala> val items = new Registry[Item]("elems")
items: Registry[Item] = Registry@69c1ea07

scala> implicit val storage: TestStorage[Item] =
| scala.collection.mutable.Map[Long, Item](
| 1L -> Item("whatever")
| )
storage: testOps.TestStorage[Item] = Map(1 -> Item(whatever))

scala> val resultFor1 = items.getById(1)
resultFor1: Option[items.Entry] = Some(Entry(1,Item(whatever)))

scala> resultFor1.foreach(items.remove)

请注意, resultFor1 的推断静态类型正是您所期望和想要的。不同于 Registry[T]#Entry在上面的评论中提出的解决方案,这种方法将禁止您从一个注册表中获取条目并使用相同的 T 从另一个注册表中删除它.大概你做了 Entry一个内部案例类,特别是因为你想避免这种事情。如果你不在乎,你真的应该宣传 Entry到自己的顶级案例类和自己的 T .

作为旁注,您可能认为只需编写以下内容即可:
implicit class RegistryOps[T, R <: Registry[T]](val self: R)(
implicit storage: TestStorage[T]
) {
def getById(id: Long): Option[R#Entry] =
storage.get(id).map(self.Entry(id, _))

def remove(entry: R#Entry): Unit = storage - entry.id
}

但是你错了,因为编译器在对隐式类进行脱糖时生成的合成隐式转换方法将使用更宽的 R比您需要的多,您将回到没有 R 时的相同情况.所以你必须自己写 toRegistryOps并指定 s.type .

(作为一个脚注,我不得不说,你隐含地传递一些可变状态听起来像是一场绝对的噩梦,我强烈建议不要像你在这里所做的那样远程做任何事情。)

关于在隐式类中创建的 Scala 不兼容的嵌套类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54513272/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com