Scala 3 中的最小通用案例类“复制”函数

发布于 2025-01-20 09:38:24 字数 3320 浏览 6 评论 0原文

在 Scala(2 和 3)中,case 类的 copy 方法是合成的,因此不能使用简单类型子句进行泛化。在 Scala 2 中,这导致一般处理案例类通过 shapeless 完成。然而,在 Scala 3 中,支持抽象 产品类型得到了大幅改进,以及其他新的语言功能,这导致了以下问题:


在 Scala 3 中,如何创建通用副本 功能对于所有情况类,同时最小化实现复杂性、使用复杂性和运行时资源成本?

尤其应该避免

  • 类型递归的显式实例化“见证”对象处理,例如 在此示例中
  • 任何需要为“泛型复制”函数调用显式定义泛型类型:理想情况下,所有泛型和给定都应该是可推断的;
  • 使用任何外部库,除非它们满足此处定义的标准。

尝试过的内容:

假设我们有以下两个测试用例类:

case class CC1(a: String)

case class CC2(a: String, b: Int)

天真的第一次尝试是这样的:

def genCopyV1[CC <: Product](
    cc: CC,
    copy: ccMirror.MirroredElemTypes => ccMirror.MirroredElemTypes
)(using
    ccMirror: ProductOf[CC]
): CC =
  ccMirror.fromProduct(copy(Tuple.fromProduct(cc).asInstanceOf[ccMirror.MirroredElemTypes]))

由于 Scala,此操作失败并出现两个 Notfound: ccMirror 错误3 仅支持返回值中的依赖类型。

我们也许可以尝试使用一些依赖于路径的类型来改进它:

def genCopyV2[CC <: Product](
    cc: CC,
    copy: ProductOf[CC]#MirroredElemTypes => ProductOf[CC]#MirroredElemTypes
)(using
    ccMirror: ProductOf[CC]
): CC =
  ccMirror.fromProduct(copy(Tuple.fromProduct(cc).asInstanceOf[ccMirror.MirroredElemTypes]))

这实际上可以编译,但只有在尝试实际使用之前:

println(generic.genCopyV2(CC1("a"), _ => Tuple1("b")))
//Found:    Tuple1[String]
//Required: deriving.Mirror.ProductOf[CC1]#MirroredElemTypes

println(generic.genCopyV2(CC2("a", 1), (a, b) => (a + "b", b + 1)))
// Wrong number of parameters, expected: 1

错误可能是由 ProductOf[CC]#MirroredElemTypes 未实际绑定引起的到为 ProductOf[CC] 解析的特定 Mirror 实例。

最后,如果我们绑定元组表示 type*

def genCopyV3[CC <: Product, TupleType <: Tuple](cc: CC, copy: TupleType => TupleType)(using
    ccMirror: ProductOf[CC],
    tupleEqEnv: TupleType =:= ccMirror.MirroredElemTypes
): CC =
  ccMirror.fromProduct(copy(Tuple.fromProduct(cc).asInstanceOf[TupleType]))

我们终于可以使用该函数了:

println(generic.genCopyV3[CC1, String *: EmptyTuple](CC1("a"), _ => Tuple1("b")))
// CC1(b)

println(generic.genCopyV3[CC2, (String, Int)](CC2("a", 1), (a, b) => (a + "b", b + 1)))
// CC2(ab,2)

println(generic.genCopyV3(CC2("a", 1), (a: String, b: Int) => (a + "b", b + 1)))
// CC2(ab,2)

当然,这不是很有用,因为调用代码需要显式指定泛型参数,或者绑定它们其他方式(例如最终使用示例的参数函数签名)。


* 为了使用简单(不需要 Tuple1),我们还可以添加 1 元素案例类特殊情况:

def genCopyV3[CC <: Product, Single](cc: CC, copy: Single => Single)(using
    ccMirror: ProductOf[CC],
    tupleEqEnv: Single *: EmptyTuple =:= ccMirror.MirroredElemTypes,
    singleNotTupleEnv: NotGiven[Single =:= Tuple]
): CC =
  ccMirror.fromProduct(Tuple(copy(cc.productElement(0).asInstanceOf[Single])))

In Scala (both 2 and 3), the copy method of case classes is synthetic, and so cannot be generalized using simple type clauses. In Scala 2, this led to generically processing case classes being done via shapeless. In Scala 3, however, support for abstracting over product types is substantially improved, alongside with other new language features, which leads to the following question:


In Scala 3, how can one create a generic copy function for all cases classes, while minimizing implementation complexity, usage complexity, and runtime resource cost?

The following, in particular, should be avoided:

  • explicit, instantiated "witness" objects for type-recursive processing, like in this example;
  • any need to explicitly define generic types for the "genericy copy" function call: ideally, all generics and givens should be infer-able;
  • use of any external libraries, unless they fulfill the criteria defined here.

What has been tried:

Let's suppose we have the following two test case classes:

case class CC1(a: String)

case class CC2(a: String, b: Int)

A naive first attempt would be this:

def genCopyV1[CC <: Product](
    cc: CC,
    copy: ccMirror.MirroredElemTypes => ccMirror.MirroredElemTypes
)(using
    ccMirror: ProductOf[CC]
): CC =
  ccMirror.fromProduct(copy(Tuple.fromProduct(cc).asInstanceOf[ccMirror.MirroredElemTypes]))

This fails with two Not found: ccMirror errors, since Scala 3 only supports dependent types in return values.

We can maybe try to improve it with some path-dependent types:

def genCopyV2[CC <: Product](
    cc: CC,
    copy: ProductOf[CC]#MirroredElemTypes => ProductOf[CC]#MirroredElemTypes
)(using
    ccMirror: ProductOf[CC]
): CC =
  ccMirror.fromProduct(copy(Tuple.fromProduct(cc).asInstanceOf[ccMirror.MirroredElemTypes]))

This actually compiles, but only until trying out actual usage:

println(generic.genCopyV2(CC1("a"), _ => Tuple1("b")))
//Found:    Tuple1[String]
//Required: deriving.Mirror.ProductOf[CC1]#MirroredElemTypes

println(generic.genCopyV2(CC2("a", 1), (a, b) => (a + "b", b + 1)))
// Wrong number of parameters, expected: 1

the errors are likely caused by the ProductOf[CC]#MirroredElemTypes not actually being bound to the particular Mirror instance resolved for ProductOf[CC].

Finally, if we bind the tuple representation type* :

def genCopyV3[CC <: Product, TupleType <: Tuple](cc: CC, copy: TupleType => TupleType)(using
    ccMirror: ProductOf[CC],
    tupleEqEnv: TupleType =:= ccMirror.MirroredElemTypes
): CC =
  ccMirror.fromProduct(copy(Tuple.fromProduct(cc).asInstanceOf[TupleType]))

we can finally use the function:

println(generic.genCopyV3[CC1, String *: EmptyTuple](CC1("a"), _ => Tuple1("b")))
// CC1(b)

println(generic.genCopyV3[CC2, (String, Int)](CC2("a", 1), (a, b) => (a + "b", b + 1)))
// CC2(ab,2)

println(generic.genCopyV3(CC2("a", 1), (a: String, b: Int) => (a + "b", b + 1)))
// CC2(ab,2)

Of course, this is not very useful, since the calling code needs to either explicitly specify the generic parameters, or bind them some other way (like in the argument function signature of the final usage example).


* for simplicity of use (not needing Tuple1), we can also add the 1-element case class special case:

def genCopyV3[CC <: Product, Single](cc: CC, copy: Single => Single)(using
    ccMirror: ProductOf[CC],
    tupleEqEnv: Single *: EmptyTuple =:= ccMirror.MirroredElemTypes,
    singleNotTupleEnv: NotGiven[Single =:= Tuple]
): CC =
  ccMirror.fromProduct(Tuple(copy(cc.productElement(0).asInstanceOf[Single])))

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文