Typeclass泛型中的Typeclass约束 [英] Typeclass constraint in typeclass generic

查看:49
本文介绍了Typeclass泛型中的Typeclass约束的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在过去一周左右的时间里,我一直在为Scala开发带类型的索引数组特征.我想将特征作为类型类提供,并允许库用户根据自己的喜好实现它.这是一个示例,使用列表列表实现2d数组类型类:

Over the past week or so I've been working on a typed, indexed array trait for Scala. I'd like to supply the trait as a typeclass, and allow the library user to implement it however they like. Here's an example, using a list of lists to implement the 2d array typeclass:

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

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

This all seems fine. Where I am having difficulties is that I would like to constrain the index types to a list of approved types (which the user can change). Since the program is not the original owner of all the approved types, I was thinking to have a typeclass to represent these approved types, and to have the approved types implement it:

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类型分类(这是开始不起作用的地方):

then change the typeclass definition to impose a constraint that Idx0 and Idx1 have to implement the IsValidIndex typeclass (and here is where things start not to work):

  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
  }

这不会编译,因为它要求特征必须具有类型类的隐式参数,不允许它们具有:(

This won't compile because it requires a trait to have an implicit parameter for the typeclass, which they are not allowed to have: (Constraining type parameters on case classes and traits).

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

This leaves me with two potential solutions, but both of them feel a bit sub-optimal:

  1. 改为将原始IsA2dArray类型类实现为抽象类,然后允许我直接在上面使用Idx0: IsValidIndex语法(在上面的链接中建议).这是我最初的想法,但是 a)它不太友好,因为它要求用户将他们正在使用的任何类型包装在另一个类中,然后再扩展此抽象类.鉴于使用类型类,可以直接使用新功能,并且 b)这很快变得很奇怪,很难键入-我发现了此博客文章(
  1. Implement the original IsA2dArray typeclass as an abstract class instead, which then allows me to use the Idx0: IsValidIndex syntax directly above (kindly suggested in the link above). This was my original thinking, but a) it is less user friendly, since it requires the user to wrap whatever type they are using in another class which then extends this abstract class. Whereas with a typeclass, the new functionality can be directly bolted on, and b) this quickly got quite fiddly and hard to type - I found this blog post (https://tpolecat.github.io/2015/04/29/f-bounds.html) relevant to the problems - and it felt like taking the typeclass route would be easier over the longer term.
  2. The contraint that Idx0 Idx0 and Idx1 must implement IsValidIndex can be placed in the implicit def to implement the typeclass: implicit def arr2dIsA2dArray[T, Idx0: IsValidIndex, Idx1: IsValidIndex] = ... But this is then in the user's hands rather than the library writer's, and there is no guarantee that they will enforce it.

如果有人可以提出一个解决方案,以使这个圆圈成方形,或者总体上改变实现同一目标的方法,我将不胜感激.我知道Scala 3允许特征具有隐式参数,因此可以让我直接在typeclass通用参数列表中使用Idx0: IsValidIndex约束,这很棒.但是仅仅为此切换到3感觉就像是一把大锤子,可以敲碎相对较小的螺母.

If anyone could suggest either a work-around to square this circle, or an overall change of approach which achieves the same goal, I'd be most grateful. I understand that Scala 3 allows traits to have implicit parameters and therefore would allow me to use the Idx0: IsValidIndex constraint directly in the typeclass generic parameter list, which would be great. But switching over to 3 just for that feels like quite a big hammer to crack a relatively small nut.

推荐答案

我想解决方法是

  1. 改为将原始的IsA2dArray类型类用作抽象类,然后允许我直接在上面使用Idx0: IsValidIndex语法(在上面的链接中建议).
  1. Implement the original IsA2dArray typeclass as an abstract class instead, which then allows me to use the Idx0: IsValidIndex syntax directly above (kindly suggested in the link above).

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

This was my original thinking, but a) it is less user friendly, since it requires the user to wrap whatever type they are using in another class which then extends this abstract class.

不,抽象类不会扩展 * ,它将仍然是类型类,只是抽象类类型类,而不是特征类型类.

No, abstract class will not be extended*, it will be still a type class, just abstract-class type class and not trait type class.

在定义类型类时,我是否可以假设特征和抽象类是可互换的?

Can I just assume that trait and abstract class are interchangeable when defining typeclasses?

主要是

使用抽象的优点是什么类而不是特征?

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

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

Unless you have hierarchy of type classes (like Functor, Applicative, Monad... in Cats). Trait or abstract class (a type class) can't extend several abstract classes (type classes) while it can extend several traits (type classes). But anyway inheritance of type classes is tricky

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是特征和抽象类,这是相似的.

* Well, when we write implicit def arr2dIsA2dArray[T, Idx0, Idx1] = new IsA2dArray[Arr2d[T, Idx0, Idx1], T, Idx0, Idx1] {... technically it's extending IsA2dArray but this is similar for IsA2dArray being a trait and abstract class.

这篇关于Typeclass泛型中的Typeclass约束的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆