5
votes

I want to define a trait that is parameterized by an upper bound R and a higher kinded type constructor F[_] that accepts only arguments that are subtypes of R. I want that this trait implements a polymorphic apply that can transform any F[A] into Unit, provided that A <: R.

This code works perfectly fine:

import scala.language.higherKinds

// this is the trait with polymorphic `apply`
trait CoCone[R, F[_ <: R]] {
  def apply[A <: R](x: F[A]): Unit
}

// Example:
sealed trait Domain
class Dom1 extends Domain

class Fnctr[X <: Domain]

val c = new CoCone[Domain, Fnctr] {
  def apply[D <: Domain](x: Fnctr[D]): Unit = ()
}

(see remark about the naming below)

Now, if I abstract over the R by declaring it a type member of some module, and define Fnctr[A <: R] inside this module, like this:

import scala.language.higherKinds

trait CoCone[R, F[_ <: R]] {
  def apply[A <: R](x: F[A]): Unit
}

trait ModuleIntf {
  type AbstractDomain
  class Fnctr[X <: AbstractDomain]
}

// No mention of an actual concrete `Domain` up to
// this point. Now let's try to implement a concrete
// implementation of `ModuleIntf`:

sealed trait Domain
class Dom1 extends Domain

object ModuleImpl extends ModuleIntf {
  type AbstractDomain = Domain
  val c = new CoCone[Domain, Fnctr] { // error [1], error [2]
    def apply[D <: Domain](x: Fnctr[D]): Unit = ()
  }
}

everything breaks, and I get two error messages that I don't know how to interpret:

[1] error: kinds of the type arguments (Domain,Main.$anon.ModuleImpl.Fnctr) do not 
conform to the expected kinds of the type parameters (type R,type F) in trait CoCone.
Main.$anon.ModuleImpl.Fnctr's type parameters do not match type F's expected parameters:
type X's bounds <: ModuleIntf.this.AbstractDomain are stricter than type _'s declared bounds <: R
      val c = new CoCone[Domain, Fnctr] {
          ^

[2] error: kinds of the type arguments (Domain,Main.$anon.ModuleImpl.Fnctr) do not 
conform to the expected kinds of the type parameters (type R,type F) in trait CoCone.
Main.$anon.ModuleImpl.Fnctr's type parameters do not match type F's expected parameters:
type X's bounds <: ModuleIntf.this.AbstractDomain are stricter than type _'s declared bounds <: R
      val c = new CoCone[Domain, Fnctr] {
              ^

I expected that the compiler would recognize that inside ModuleImpl in CoCone[Domain, Fnctr] all three Domain = AbstractDomain = R are the same type.

Am I missing something obvious here, or is it a limitation of scalac 2.12.4 ? If it's a limitation, has someone ever reported it anywhere?

Edit Found something similar: issue #10186. Is it "the same"? Is not "the same"? Should I propose it as another test-case, if it is a bug? If someone can confirm that it's not entirely my fault, and/or that it's indeed closely related to the linked issue, that would be an acceptable resolution of the problem.

Edit2: As @Evgeny has pointed out, it cannot be exactly the issue 10186, because it fails in a different compiler phase (refchecks instead of typer).


Remark about the name: I've called the trait CoCone here, by analogy to the commonly defined ~> that can be thought of as a natural transformation, sort-of. In a way, the CoCone[Dom, Fctr] is something like Fctr ~> Const_Unit, but with domain of F restricted to subtypes of Dom. In reality, the CoCone[R, F] is a thing of shape F that can send certain subclasses of R over the network, but that's not important, so I've abstracted the names away. This thing is a rather common mathematical construction, nothing too contrived, would be nice if one could compile it.

1
Issue 10186 failes on typer phase, your case failes on refchecks phase. It is not an answer for question, but probably means "not the same". And I see the same error with either scalac 2.11 or scalac 2.12Evgeny
I used scalac -Xprint:typer,refchecks -Ydebug -Xprint-types -Ytyper-debug test3.scala &> test3.scalacwhere test3.scala contains your case. Same keys (without refchecks, because it stopped on typer) for issue's code gave me stop with error on typer.Evgeny
@Evgeny, Thanks. Indeed, scalac -Xprint:all shows that it fails during refchecks. I think I'll wait till next Tuesday-Wednesday, see if someone knows more... If you are reasonably certain that this has not been reported, I wouldn't mind if you convert comment into answer.Andrey Tyukin
Definitely, this is not an issue #10186, there was a patch proposal, and this patch fixes issue, but your case is still broken. -explaintypes could add some output on error, but it does not give me anything to understand.. I tried move param which parameterizes Fnctr to trait (trait ModuleIntf[AbstractDomain, X <: AbstractDomain]) but finished with class ModuleImpl[X] where X is also parameter for apply. This compiles, but again, does not give me understanding in original problem..Evgeny

1 Answers

5
votes

Working approach with abstract type members (tried with scalac 2.12.4):

import scala.language.higherKinds

trait CoCone[R, F[_ <: R]] {
  def apply[A <: R](x: F[A]): Unit
}

trait ModuleIntf {
  type AbstractDomain
  type X = ({type XX <: AbstractDomain; type XXX = XX with AbstractDomain})
  class Fnctr[X]
}

sealed trait Domain

case class Dom1() extends Domain

object ModuleImpl extends ModuleIntf {
  type AbstractDomain = Domain
  val f = new Fnctr[Dom1]()
  val c = new CoCone[Domain, Fnctr] {
    def apply[X](x: Fnctr[X]): Unit = ()
  }
  c(f)
}

Idea is taken from comments for issue #4745. If I do not miss anything, this should be equivalent to original non-compilable approach.

As I found current problem compilation fails on different compiler phase (refchecks) when #10186 fails on typer, but anyway, in #10186 is mentioned patch, I tried it and it fixes #10186 itself, but current errors are still reported.

I would say that it should compile, but I did not find any issue similar to current problem, so, suppose, it is not yet reported compiler bug.

Updated after @Andrey comment.

Yes, was too focused to get compilable version and lost upper bound in trait. Sorry.

Updated after some more diving into compiler internals

I debug a bit validating higher kinded types (scala.reflect.internals.Kinds around checkKindBoundsHK) and looks like at the moment of checking Fnctr bounds, there are no info in bound types tree that AbstractDomain is alias for Domain. If I change CoCone first type to AbstractDomain in object, than anyway in tree I see that it is Domain, but not for Fnctr bounds.

By the way, fix for #10186 tries to solve something similar, evaluating bound argument asSeenFrom, as I understand trying to get concrete types, but as soon as in our case there is no into about concrete class in tree, AbstractDomain is returned..