【问题标题】:Typeclass constraint in typeclass generic类型类泛型中的类型类约束
【发布时间】:2020-09-08 16:26:30
【问题描述】:

在过去一周左右的时间里,我一直在研究 Scala 的类型化、索引数组特征。我想将特征作为类型类提供,并允许库用户按照他们喜欢的方式实现它。下面是一个示例,使用列表列表来实现二维数组类型类:

// crate a 2d Array typeclass, with additional parameters
trait IsA2dArray[A, T, Idx0, Idx1] {
  def get(arr: A, x: Int, y: Int): T // get a single element of the array; its type will be T
}
// give this typeclass method syntax
implicit class IsA2dArrayOps[A, T, Idx0, Idx1](value: A) {
  def get(x: Int, y: Int)(implicit isA2dArrayInstance: IsA2dArray[A, T, Idx0, Idx1]): T = 
    isA2dArrayInstance.get(value, x, y)
}

// The user then creates a simple case class that can act as a 2d array
case class Arr2d[T, Idx0, Idx1] (
  values: List[List[T]],
  idx0: List[Idx0],
  idx1: List[Idx1],
)
// A couple of dummy index element types:
case class Date(i: Int) // an example index element
case class Item(a: Char) // also an example
// The user implements the IsA2dArray typeclass 
implicit def arr2dIsA2dArray[T, Idx0, Idx1] = new IsA2dArray[Arr2d[T, Idx0, Idx1], T, Idx0, Idx1] {
  def get(arr: Arr2d[T, Idx0, Idx1], x: Int, y: Int): T = arr.values(x)(y)
}
// create an example instance of the type
val arr2d = Arr2d[Double, Date, Item] (
  List(List(1.0, 2.0), List(3.0, 4.0)),
  List(Date(0), Date(1)),
  List(Item('a'), Item('b')),
)
// check that it works
arr2d.get(0, 1)

这一切似乎都很好。我遇到困难的地方是我想将索引类型限制为已批准类型的列表(用户可以更改)。由于该程序不是所有已批准类型的原始所有者,因此我想有一个类型类来表示这些已批准的类型,并让已批准的类型实现它:

trait IsValidIndex[A] // a typeclass, indicating whether this is a valid index type
implicit val dateIsValidIndex: IsValidIndex[Date] = new IsValidIndex[Date] {} 
implicit val itemIsValidIndex: IsValidIndex[Item] = new IsValidIndex[Item] {}

然后更改类型类定义以施加约束,即 Idx0Idx1 必须实现 IsValidIndex 类型类(这就是事情开始不起作用的地方):

  trait IsA2dArray[A, T, Idx0: IsValidIndex, Idx1: IsValidIndex] {
    def get(arr: A, x: Int, y: Int): T // get a single element of the array; its type will be T
  }

这不会编译,因为它需要一个 trait 来为 typeclass 提供一个隐式参数,而它们不允许有:(Constraining type parameters on case classes and traits)。

这给我留下了两个潜在的解决方案,但他们都觉得有点不太理想:

  1. 改为将原始 IsA2dArray 类型类实现为抽象类,这样我就可以直接使用上面的 Idx0: IsValidIndex 语法(在上面的链接中建议使用)。这是我最初的想法,但是 a) 它对用户不太友好,因为它要求用户将他们正在使用的任何类型包装在另一个类中,然后扩展这个抽象类。而使用类型类,新功能可以直接用螺栓固定,并且 b) 这很快就变得非常繁琐且难以输入 - 我发现这篇博文 (https://tpolecat.github.io/2015/04/29/f-bounds.html) 与问题相关 - 从长远来看,采用 typeclass 路线会更容易。
  2. Idx0 Idx0Idx1 必须实现 IsValidIndex 的约束可以放在隐式 def 中来实现类型类: implicit def arr2dIsA2dArray[T, Idx0: IsValidIndex, Idx1: IsValidIndex] = ... 但这是在用户手中而不是库作者的手中,并且不能保证他们会强制执行。

如果有人可以建议解决这个问题的方法,或者整体改变方法以实现相同的目标,我将不胜感激。我知道 Scala 3 允许特征具有隐式参数,因此允许我直接在类型类泛型参数列表中使用 Idx0: IsValidIndex 约束,这会很棒。但是仅仅为此切换到 3 感觉就像是一把大锤子来敲击一个相对较小的坚果。

【问题讨论】:

  • 我认为这条线是错误的def arr2dIsA2dArray[T, Idx0, Idx1] = new IsA2dArray[Arr2d[T, Idx0, Idx1], T, Date, Item],它要么是Data, Item,要么最后删除Idx0 和1,要么是Idx0, Idx1
  • @pedrofurla - 谢谢,是的,你是对的 - 我现在会更正它。
  • 顺便说一句,我打算用你的第二个解决方案来回答这个问题,然后我看到你已经搞定了。进一步考虑,我认为您将许多东西捆绑在一起,至少我是这样认为的,而没有看到 Idx0 和 1 的用途。
  • @Chrisper 关于1. 我看不出使用抽象类而不是特征是如何不太理想的。 “它要求用户将他们正在使用的任何类型包装在另一个类中,然后扩展这个抽象类” 为什么?抽象类不会被扩展,它仍然是一个类型类,只是抽象类类型类而不是 trait 类型类。
  • @Mostly。 stackoverflow.com/questions/1991042/… geeksforgeeks.org/… 除非你有类型类的层次结构(如 FunctorApplicativeMonad... 在 Cats 中)。特征或抽象类(一个类型类)不能扩展多个抽象类(类型类),但它可以扩展多个特征(类型类)。

标签: scala generics typeclass


【解决方案1】:

我想解决办法是

  1. 将原来的 IsA2dArray 类型类实现为抽象类,这样我就可以直接使用上面的 Idx0: IsValidIndex 语法(请在上面的链接中建议)。

这是我最初的想法,但是 a) 它对用户不太友好,因为它要求用户将他们正在使用的任何类型包装在另一个类中,然后扩展这个抽象类。

不,抽象类不会被扩展*,它仍然是一个类型类,只是抽象类类型类而不是特征类型类。

我可以假设 trait 和抽象类在定义类型类时可以互换吗?

大部分时间。

What is the advantage of using abstract classes instead of traits?

https://www.geeksforgeeks.org/difference-between-traits-and-abstract-classes-in-scala/

除非您有类型类的层次结构(例如 Cats 中的 FunctorApplicativeMonad...)。特征或抽象类(类型类)不能扩展多个抽象类(类型类),但可以扩展多个特征(类型类)。但无论如何,类型类的继承很棘手

https://typelevel.org/blog/2016/09/30/subtype-typeclasses.html


* 好吧,当我们写 implicit def arr2dIsA2dArray[T, Idx0, Idx1] = new IsA2dArray[Arr2d[T, Idx0, Idx1], T, Idx0, Idx1] {... 时,它在技术上是扩展 IsA2dArray 但这类似于 IsA2dArray 是一个特征和抽象类。

【讨论】:

  • 还有一个link值得一提。
猜你喜欢
  • 2017-01-21
  • 1970-01-01
  • 2022-10-06
  • 1970-01-01
  • 2011-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多