Принудительная ковариация для абстрактного типа Scala с помощью @uncheckedVariance

#scala #higher-kinded-types

#scala #типы более высокого типа

Вопрос:

Темы различий хорошо представлены во многих местах, но, к сожалению, я не нахожу ни одного места, посвященного этой теме с абстрактными типами.

Затем, пытаясь понять коллекцию, я написал следующее содержимое

 trait CollectionOps[ A,  C] {
  this: C =>

  type CC[B] <: BasicColl[B] // Invariant position

  def filter(f: A => Boolean): C

  def map[B](f: A => B): CC[B] // CC is in a covariant position but declared invariant via abstract type 
}

trait BasicColl[ A] extends CollectionOps[A, BasicColl[A]] {
  type CC[B] = BasicColl[B] // this is the problem, the equality
  // But if i let inequality: type CC[B] <: BasicColl[B] 
}

// Do not compile due to previous equality and invariance on CC
trait BasicColl2[ A] extends BasicColl[A] with CollectionOps[A, BasicColl2[A]] {
  type CC[B] = BasicColl2[B]
  // But if i let the previous inequality i can writte type CC[B] <: BasicColl2[B] 
}

trait Test_With_Abstract_Type_Equality {

  // Only BasicColl extist
  def coll1: BasicColl[Int]
  // We have exact type due to equality of CC with BasicColl
  def mapped: BasicColl[Double] = coll1.map(_.toDouble)

  // But no specialized inhéritence possible

}
trait Test_With_Abstract_Type_Inequality {

  def coll1: BasicColl[Int]
  // We have abstract type due to inequality of CC with BasicColl
  def mappedWithAbstractTypeDef: BasicColl[Int]#CC[Double] = coll1.map(_.toDouble)

  // BasicColl2 can exist
  def coll2: BasicColl2[Int]
  // We have abstract type due to inequality of CC with BasicColl2
  def mappedWithAbstractTypeDef2: BasicColl2[Int]#CC[Double] = coll2.map(_.toDouble)

}


 

Шаблон Scala использует не абстрактный тип, а непосредственно ковариантный параметризованный тип CC следующим образом

 trait IterableOnceOps[ A,  CC[_],  C]
 

Преимущество ковариации заключается в облегчении смешивания признаков.

Но использование более высокого типа этого типа на уровне параметров типа предотвращает любое расширение класса с более чем 1 параметризованным типом, где использование абстрактного типа позволяет расширить более сложный класс за счет наличия абстрактного типа в качестве вывода. Вот в чем дело, я попробовал это, но я недостаточно уверен в этом риске.

 // Redefinition
trait CollectionOps[ A,  C] {
  this: C =>

  type CC[B] <: BasicColl[B] // Invariant position

  def filter(f: A => Boolean): C

  def map[B](f: A => B): CC[B] // but CC is in a covariant position 
}
// Redefinition
trait BasicColl[ A] extends CollectionOps[A, BasicColl[A]]


trait CollectionOpsLocked1[ A,  CCC[B] <: BasicColl[B],  C] extends CollectionOps[A, C] {
  this: C =>

  type CC[B] = CCC[B] @uncheckedVariance // this is my question

  // If i use CC ONLY in covariant position is there any risk ?

}

trait BasicColl2[ A] extends BasicColl[A] with CollectionOpsLocked1[A, BasicColl2, BasicColl2[A]]
trait BasicColl3[ A] extends BasicColl2[A] with CollectionOpsLocked1[A, BasicColl3, BasicColl3[A]]

trait Test {

  def coll1: BasicColl[Int]
  def mappedWithAbstractTypeDef: BasicColl[Int]#CC[Double] = coll1.map(_.toDouble)

  // BasicColl2 can exist
  def coll2: BasicColl2[Int]
  def mapped2: BasicColl2[Double] = coll2.map(_.toDouble)

  // BasicColl3 can exist
  def coll3: BasicColl3[Int]
  def mapped3: BasicColl3[Double] = coll3.map(_.toDouble)

}

 

В более общем случае существует ли какой-либо риск использования @uncheckedVariance в каком-либо месте, где ковариация / контравариантность являются законными?

Комментарии:

1. «Но использование более высокого типа этого типа на уровне параметров типа предотвращает любое расширение класса с более чем 1 параметризованным типом, где использование абстрактного типа позволяет расширять более сложный класс за счет наличия абстрактного типа в качестве выходного.» — это не совсем так. Ваш CC[_] ожидает ровно один параметр типа. Ваш возвращаемый тип применяет ровно один параметр. Даже если вы расширяете свой класс, применяется принцип подстановки Liskov, поэтому вашему расширению также придется ожидать и применять ровно один параметр.

2. И если вы хотите использовать что-то с 2 параметрами и исправить один из них самостоятельно … тогда это равносильно использованию kind projector. Таким образом, мощность выражения примерно такая же.