【发布时间】: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] {}
然后更改类型类定义以施加约束,即 Idx0 和 Idx1 必须实现 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)。
这给我留下了两个潜在的解决方案,但他们都觉得有点不太理想:
- 改为将原始 IsA2dArray 类型类实现为抽象类,这样我就可以直接使用上面的
Idx0: IsValidIndex语法(在上面的链接中建议使用)。这是我最初的想法,但是 a) 它对用户不太友好,因为它要求用户将他们正在使用的任何类型包装在另一个类中,然后扩展这个抽象类。而使用类型类,新功能可以直接用螺栓固定,并且 b) 这很快就变得非常繁琐且难以输入 - 我发现这篇博文 (https://tpolecat.github.io/2015/04/29/f-bounds.html) 与问题相关 - 从长远来看,采用 typeclass 路线会更容易。 - Idx0
Idx0和Idx1必须实现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/… 除非你有类型类的层次结构(如
Functor、Applicative、Monad... 在 Cats 中)。特征或抽象类(一个类型类)不能扩展多个抽象类(类型类),但它可以扩展多个特征(类型类)。