I'm trying to make a builder with a number of unconstrained types that can build a class with constrained types. How can I use implicit evidence of type boundaries to satisfy the type constraints?
For instance say I want to build a class:
class NameClass[N <: String](name: N)
And I use a builder that looks like this:
class NameClassBuilder[N](n: N) {
def name[NewN <: String](name: NewN) = new NameClassBuilder[NewN](name)
def build(implicit ev: N <:< String) : NameClass[N] = new NameClass[N](n)
}
object NameClassBuilder {
def builder : NameClassBuilder[Unit] = new NameClassBuilder[Unit]()
}
The idea is that you can start out with a dummy type/value and then update the type as you add fields. By using evidence that the types are correct, you can only build it when the types make sense.
The problem is it doesn't compile, on the grounds that the N
from NameClassBuilder
does not satisfy the type constraint N <: String
in NameClass
, even though I've included evidence that it does.
error: type arguments [N] do not conform to class NameClass's type parameter bounds [N <: String]
def build(implicit ev: N <:< String) : NameClass[N] = new NameClass[N](n)
Is there any way to use the evidence to satisfy this constraint?
CodePudding user response:
You are telling the compiler that when function build
is called, there should be implicit evidence present that conforms to N <:< String
. But within the scope of NameClassBuilder
, N
conforms to Any
.
You could try:
class NameClassBuilder[N](n: N) {
def name[NewN <: String](name: NewN) = new NameClassBuilder[NewN](name)
def build[U <: String](implicit ev: N <:< String): NameClass[U] = new NameClass[U](n.asInstanceOf)
}
CodePudding user response:
You can return NameClass[N with String]
instead. N with String <: String
for any N
, and if actually N <: String
then N with String = N
. The N <:< String
you have in build
is required to cast n: N
to N with String
.
def build(implicit ev: N <:< String): NameClass[N with String] = {
type F[ X] = N with X
new NameClass(ev.liftCo[F](n))
}