0

I'm trying to extend a base class with a single [A] type parameter, with a subclass that has an A[_] type parameter - like this:

abstract class IsBase[A]

abstract class IsSub[A[_]] extends IsBase[A[_]] {
  type T
  def get(self: A[T]): T 
}

implicit def listIsSub[_T] = new IsSub[List] {
  type T = _T;
  def get(self: List[T]): T = self(0)
}
val list = List(1, 2)

implicitly[IsSub[List]{ type T = Int }].get(list) // works fine
implicitly[IsBase[List[Int]]] // Could not find implicit value for parameter e

I understand one way to do this would just be to move the abstract type T up to a type parameter, likeso:

abstract class IsSub1[A[_], T] extends IsBase[A[T]]

But before I go ahead with this route I'd just like to check that there isn't a straightforward way to make this work as it is.

Thanks!

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Chris J Harris
  • 1,597
  • 2
  • 14
  • 26
  • 1
    I don't understand what you need `IsSub` and `IsBase` for, but I'm pretty sure you have to make `T` a type parameter to `IsSub`. When you say `extends IsBase[A[_]]`, that `A[_]` is not the same as the `A[_]` type parameter. You are using a wildcard, which makes it pretty much useless. I'd recommend using `IsSub[A[_], T] extends IsBase[A[T]]` or something like that. It's a lot easier than using a structural type anyways. – user Oct 30 '20 at 22:47
  • 2
    You are declaring Base to take a full type, while declaring Sub to take a type constructor. They are different signatures. You need to be consistent. Other than declaring a second parameter T, you can also declare `IsBase[C[_]]` and then same for sub `IsSub[C[_]] extends IsBase[C]` – SwiftMango Oct 30 '20 at 22:59
  • @user - thank you - I did mention that solution in my question but it's still nice to get some external validation. I get the impression that's the better long-term way to go. – Chris J Harris Oct 30 '20 at 23:06

1 Answers1

3

One of the first things you should try is to resolve the implicit manually and see what will happen

implicitly[IsBase[List[Int]]](listIsSub[Int])

//type mismatch;
// found   : App.IsSub[List]{type T = Int}
// required: App.IsBase[List[Int]]
//Note: List[_] >: List[Int] 
// (and App.IsSub[List]{type T = Int} <: App.IsBase[List[_]]), 
//  but class IsBase is invariant in type A.
//You may wish to define A as -A instead. (SLS 4.5)

So you can see that you just didn't define necessary implicit. You defined an implicit of type IsSub[List]{type T = Int}, which is not connected to IsBase[List[Int]] because IsSub (with type-constructor type parameter A[_]) extends IsBase[A[_]] with existential type parameter. Existentials (and type projections) rarely play well with implicits.

You can see the hint. You can make IsBase contravariant

abstract class IsBase[-A]

Then

implicitly[IsBase[List[Int]]]

compiles.

Whether to define your type class contravariant (1 2 3 4) depends on your logic.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66