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..
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With