In the following example, is there a way to avoid that implicit resolution picks the defaultInstance
and uses the intInstance
instead? More background after the code:
// the following part is an external fixed API
trait TypeCls[A] {
def foo: String
}
object TypeCls {
def foo[A](implicit x: TypeCls[A]) = x.foo
implicit def defaultInstance[A]: TypeCls[A] = new TypeCls[A] {
def foo = "default"
}
implicit val intInstance: TypeCls[Int] = new TypeCls[Int] {
def foo = "integer"
}
}
trait FooM {
type A
def foo: String = implicitly[TypeCls[A]].foo
}
// end of external fixed API
class FooP[A:TypeCls] { // with type params, we can use context bound
def foo: String = implicitly[TypeCls[A]].foo
}
class MyFooP extends FooP[Int]
class MyFooM extends FooM { type A = Int }
object Main extends App {
println(s"With type parameter: ${(new MyFooP).foo}")
println(s"With type member: ${(new MyFooM).foo}")
}
Actual output:
With type parameter: integer
With type member: default
Desired output:
With type parameter: integer
With type member: integer
I am working with a third-party library that uses the above scheme to provide "default" instances for the type class TypeCls
. I think the above code is a minimal example that demonstrates my problem.
Users are supposed to mix in the FooM
trait and instantiate the abstract type member A
. The problem is that due to the defaultInstance
the call of (new MyFooM).foo
does not resolve the specialized intInstance
and instead commits to defaultInstance
which is not what I want.
I added an alternative version using type parameters, called FooP
(P = Parameter, M = Member) which avoids to resolve the defaultInstance
by using a context bound on the type parameter.
Is there an equivalent way to do this with type members?
EDIT: I have an error in my simplification, actually the foo
is not a def
but a val
, so it is not possible to add an implicit parameter. So no of the current answers are applicable.
trait FooM {
type A
val foo: String = implicitly[TypeCls[A]].foo
}
// end of external fixed API
class FooP[A:TypeCls] { // with type params, we can use context bound
val foo: String = implicitly[TypeCls[A]].foo
}
The simplest solution in this specific case is have foo
itself require an implicit instance of TypeCls[A]
.
The only downside is that it will be passed on every call to foo
as opposed to just when instantiating
FooM
. So you'll have to make sure they are in scope on every call to foo
. Though as long as the TypeCls
instances are in the companion object, you won't have anything special to do.
trait FooM {
type A
def foo(implicit e: TypeCls[A]): String = e.foo
}
UPDATE: In my above answer I managed to miss the fact that FooM
cannot be modified. In addition the latest edit to the question mentions that FooM.foo
is actually a val
and not a def
.
Well the bad news is that the API you're using is simply broken. There is no way FooM.foo
wille ever return anything useful (it will always resolve TypeCls[A]
to TypeCls.defaultInstance
regardless of the actual value of A
). The only way out is to override foo
in a derived class where the actual value of A
is known, in order to be able to use the proper instance of TypeCls
. Fortunately, this idea can be combined with your original workaround of using a class with a context bound (FooP
in your case):
class FooMEx[T:TypeCls] extends FooM {
type A = T
override val foo: String = implicitly[TypeCls[A]].foo
}
Now instead of having your classes extend FooM
directly, have them extend FooMEx
:
class MyFoo extends FooMEx[Int]
The only difference between FooMEx
and your original FooP
class is that FooMEx
does extend FooM
, so MyFoo
is a proper instance of FooM
and can thus be used with the fixed API.
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