Consider the following piece of code:
trait Foo {
type T
def value: T
}
object Foo {
def apply[A](v: A): Foo = new Foo {
override type T = A
override def value = v
}
}
trait Decode[A] {
def apply(x: A): String
}
object Decode {
def apply[A](f: A => String): Decode[A] = new Decode[A] {
override def apply(x: A) = f(x)
}
implicit val decodeStr: Decode[String] = Decode(identity)
}
class Sandbox {
def decodeFoo(foo: Foo)(implicit decoder: Decode[foo.T]): String =
decoder(foo.value)
val foo = Foo("hello")
println(decodeFoo(foo))
}
The above code should work fine and print hello
but instead it fails to compile:
could not find implicit value for parameter decoder: Decode[Sandbox.this.foo.T]
[error] println(decodeFoo(foo))
Even when I explicitly pass in the implicit param:
println(decodeFoo(foo = foo)(decoder = Decode.decodeStr))
I still get this error now:
type mismatch;
[error] found : Decode[String]
[error] required: Decode[Sandbox.this.foo.T]
[error] println(decodeFoo(foo = foo)(decoder = Decode.decodeStr))
[error] ^
Ofcourse, I can make Foo
a Foo[T]
and define decoders for it but that's not the point of this question - I want to understand why the above code fails to compile.
Scala has a notion of a type dependent on a value. This dependency is not expressed in the type signature but rather in the type placement.
Implicit parameters are the parameters that are passed to a function with implicit keyword in Scala, which means the values will be taken from the context in which they are called.
In Scala, objects and values are treated mostly the same. An implicit object can be thought of as a value which is found in the process of looking up an implicit of its type.
Scala will first look for implicit definitions and implicit parameters that can be accessed directly (without a prefix) at the point the method with the implicit parameter block is called. Then it looks for members marked implicit in all the companion objects associated with the implicit candidate type.
The problem exists here:
object Foo {
def apply[A](v: A): Foo = new Foo {
override type T = A
override def value = v
}
}
There, you've established that you will return a Foo
but not, specifically, which Foo
. Hence, that function only knows that it can return a Foo
for any type T
. You need an Aux
pattern to recapture the type which is lost when establishing your new Foo
(yes, yes...)
object Foo {
type Aux[A] = Foo{ type T = A }
def apply[A](v: A): Aux[A] = new Foo {
type T = A
def value = v
}
}
which then says that for a given A
produce the Foo
which has it's T
dependent type set to A
.
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