The following does not compile because of the last line:
object ImplicitWrappedTraitWithType {
trait Wrapper[+T] {
def unwrap: T
}
object Wrapper {
def apply[T](implicit w: Wrapper[T]): Wrapper[T] = w
}
trait IO[In] {
type Out
def out: Out
}
implicit def decoder[In]: Wrapper[IO[In] {type Out = String}] = new Wrapper[IO[In] {type Out = String}] {
override def unwrap: IO[In] {type Out = String} = new IO[In] {
override type Out = String
override val out: Out = "yeah"
}
}
val wrap = Wrapper[IO[String]]
val io: IO[String] = wrap.unwrap
val out: String = io.out //actual type: unwrap.Out
}
What can I do to convince the compiler that val out
is a String
?
Pre-edit - ignore this
Example 1 - this does not compile:
object ImplicitWrappedTraitWithType {
class Wrapper[T]
object Wrapper {
def apply[T](implicit w: Wrapper[T]): Wrapper[T] = w
}
trait IO[In] {
type Out
}
implicit def decoder[In]: Wrapper[IO[In] {type Out = String}] = null
//client code
Wrapper[IO[String]]
}
Example 2 - whereas this does:
object ImplicitWrappedTraitWithType {
class Wrapper[T]
object Wrapper {
def apply[T](implicit w: Wrapper[T]): Wrapper[T] = w
}
trait IO[In] {
type Out
}
implicit def decoder[In]: Wrapper[IO[In]] = null
//client code
Wrapper[IO[String]]
}
In the client code I don't know what the type of Out
will be, but I need to be able to access it when I extract an instance of IO
from Wrapper
(code for that not shown).
How must 'Example 1' be changed for this to compile, while retaining the Out parameter in a way that is visible for the client code.
(Please comment if this formulation is unclear)
You only need two minor edits to your code.
trait Wrapper[+T] {
def unwrap: T
}
object Wrapper {
def apply[T](implicit w: Wrapper[T]): w.type = w
}
trait IO[In] {
type Out
def out: Out
}
implicit def decoder[In]: Wrapper[IO[In] {type Out = String}] = new Wrapper[IO[In] {type Out = String}] {
override def unwrap: IO[In] {type Out = String} = new IO[In] {
override type Out = String
override val out: Out = "yeah"
}
}
val wrap = Wrapper[IO[String]]
val io = wrap.unwrap
val out: String = io.out
The most important is to change the return type of the apply
method to w.type
. That way the full type of w
(including all refinements) is retained. If you write Wrapper[T]
as return type and you ask for a Wrapper[T]
for T
equal to IO[String]
, you will get a Wrapper[IO[String]]
and all extra knowledge like {type Out = String}
will be lost.
Second: in val io: IO[String] = wrap.unwrap
you say that io
is an IO[String]
. Again, all extra knowledge is lost. So just let the compiler infer the type of io
.
Another thing: if you don't want Wrapper
to be covariant in T
, you can just leave off the variance annotation and change your apply
method.
trait Wrapper[T] {
def unwrap: T
}
object Wrapper {
def apply[T](implicit w: Wrapper[_ <: T]): w.type = w
}
That way the compiler still knows it has to look for a subtype of IO[String]
if you call Wrapper.apply[IO[String]]
. Because IO[String]{type out = String}
is a subtype of IO[String]
, they are not equal.