0

I have a generic F-bounded trait in Scala. Lets me write methods that return the same underlying implementation type, super! But now lets say a sub-trait also defines methods that require F-bounding. Scala is sending me back compile errors that don't make sense:

package sandbox

import sandbox.ComplexImpl.AnyComplexImpl

import scala.language.existentials

trait FBounded[IMPL <: FBounded[IMPL]] { self: IMPL =>
  def foo: IMPL
}

trait FBoundedUser[F <: FBounded[F]] {
  def bar(value: F): F = value.foo
}

trait SimpleImpl extends FBounded[SimpleImpl] {
  override def foo: SimpleImpl = this
}

object SimpleUser extends FBoundedUser[SimpleImpl]

// A-OK so far...

trait ComplexImpl[IMPL <: ComplexImpl[IMPL]] extends FBounded[IMPL] { self: IMPL =>
  def baz: IMPL
}

object ComplexImpl {
  type AnyComplexImpl = ComplexImpl[T] forSome { type T <: ComplexImpl[T] }
}

object ComplexUser1 extends FBoundedUser[ComplexImpl[_]]
object ComplexUser2 extends FBoundedUser[AnyComplexImpl]

Trying to compile with either of ComplexUser1 or ComplexUser2 results in:

Error:(32, 29) type arguments [sandbox.ComplexImpl.AnyComplexImpl] do not conform to trait
               FBoundedUser's type parameter bounds [F <: sandbox.FBounded[F]]

Which doesn't make sense to me. AnyComplexImpl definitely implements FBounded. Am I missing something, or is the type checker just failing me here?

EDIT:

class Concrete() extends ComplexImpl[Concrete] {
  override def baz: Concrete = this

  override def foo: Concrete = this
}
object ComplexUser3 extends FBoundedUser[Concrete]

Compiles just fine. So why won't the generic version?

1 Answer 1

1

The constraint requires AnyComplexImpl to implement FBounded[AnyComplexImpl], which it doesn't, not just FBounded[T] forSome { type T <: ComplexImpl[T] }.

It works if you make FBounded and ComplexImpl covariant. The compiler reasons that:

  1. AnyComplexImpl is a supertype of any ComplexImpl[T], where T <: ComplexImpl[T];

  2. So FBounded[T] forSome { type T <: ComplexImpl[T] } is a subtype of FBounded[AnyComplexImpl];

  3. So AnyComplexImpl is a subtype of FBounded[AnyComplexImpl].

But I suspect trying to mix F-bounded types and existentials is likely to lead to other problems down the line. The reason ComplexUser1 and ComplexUser2 don't compile is precisely that they are not generic. Consider using actually generic version instead:

def complexUser4[T <: ComplexImpl[T]] = new FBoundedUser[T] {}
Sign up to request clarification or add additional context in comments.

1 Comment

Aha! Co/contravariance is one corner of the type system I'm still trying to get a better grasp on. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.