簡體   English   中英

類型類泛型中的類型類約束

[英]Typeclass constraint in typeclass generic

在過去一周左右的時間里,我一直在為 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 來為類型類提供一個隱式參數,這是不允許的:( 約束案例類和特征上的類型參數)。

這給我留下了兩個潛在的解決方案,但它們都感覺有點次優:

  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 感覺就像是一把大錘子來敲碎一個相對較小的堅果。

我想解決辦法是

  1. 將原始IsA2dArray類型類實現為抽象類,然后我可以直接使用上面的Idx0: IsValidIndex語法(在上面的鏈接中建議)。

這是我最初的想法,但是 a) 它不太用戶友好,因為它要求用戶將他們正在使用的任何類型包裝在另一個類中,然后擴展這個抽象類。

不,抽象類不會被擴展* ,它仍然是一個類型類,只是抽象類類型而不是特征類型類。

在定義類型類時,我可以假設特征和抽象類可以互換嗎?

大多。

使用抽象類而不是特征有什么好處?

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

除非你有類型類的層次結構(比如Functor , Applicative , Monad ... in Cats)。 Trait 或抽象類(類型類)不能擴展多個抽象類(類型類),而可以擴展多個特征(類型類)。 但是無論如何繼承類型類是很棘手的

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類似一個特征和抽象類。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM