简体   繁体   中英

Type bounds constraints restrictions in Higher Kinds when extending a trait

Let's set up issue conditions

trait Bound
trait Bound2 extends Bound

trait T1[B <: Bound]
trait T2[B <: Bound2] extends T1[B]

trait WrapperT1[Tz[B2 <: Bound] <: T1[B2]]

This code compile without problem, issue comes when trying to extend WrapperT1

trait WrapperT2[Tz[B2 <: Bound2] <: T2[B2]] extends WrapperT1[T2]

// Compilator error
kinds of the type arguments (T2) do not conform to the expected kinds of the type parameters (type Tz) in trait WrapperT1.

[error] ex.T2's type parameters do not match type Tz's expected parameters:

[error] type B's bounds <: Bound2 are stricter than type B2's declared bounds <: ex.Bound

[error]     trait WrapperT2[Tz[B2 <: Bound2] <: T2[B2]] extends WrapperT1[T2]

Yes B2 <: Bound2 is stricter than B2 <: Bound but i'm not understanding why the compilator complains for this reason and i would be grateful to know more about it.

Potential solution but does it has some drawbacks?

trait Bound
trait Bound2 extends Bound

trait T1[B] {
    implicit val ev: B <:< Bound
}

trait T2[B] extends T1[B] {
    // this is possible thank's to covariance of class `<:<[-From, +To]` if i'm not wrong
    implicit val ev: B <:< Bound2
}

trait WrapperT1[Tz[B2] <: T1[B2]]
// Compiles
trait WrapperT2[Tz[B2] <: T2[B2]] extends WrapperT1[Tz]

This looks nice and we keep the compilation checks about B2 generic type but is there any inconveniant using it?

To understand why the compiler complains, let's look at this piece of code (which is the same as your code, but with different names)

trait Show[A <: AnyRef] {
  def show(a: A): Unit
}
trait ShowString[S <: CharSequence] extends Show[S] {
  def show(s: S): Unit = println(s)
}

trait Wrapper1[S[A <: AnyRef] <: Show[A]] {
  def show[A <: AnyRef](a: A)(implicit show: S[A]): Unit = show.show(a)
}

trait Wrapper2[S[C <: CharSequence] <: ShowString[C]] extends Wrapper1[S]

Here, too, the compiler emits the error type C's bounds <: CharSequence are stricter than type A's declared bounds <: AnyRef , but let's pretend you somehow get it to compile here. That would mean if someone wanted to use your wrapper class to print, say, a list, they wouldn't be able to do that, meaning that your wrapper class would be basically useless.

val w2: Wrapper[ShowString] = ???
w2.show(List(1, 2))
//Error: could not find implicit value for parameter show: ShowString[List[Int]]

In this case, it's just that the implicit isn't found. However, if you have a method in wrapper that accepts a S[_] (or Tz[_] , from your example) and you try to shoehorn an object of the wrong type into your show method, you could get a runtime exception.

It fails for the same reason you can't do this

trait T1[A] {
  def process(a: A): Unit
}
trait T2 extends T1[Int] {
  def process(i: Int): Unit = ???
}
val t2: T2 = ???

t2.process("not an int")

T2 is just not equipped to handle anything that is not an Int , and the same applies here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM