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.
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..