I have an implicit class defined in a class injected in other classes like this
class A {
implicit class B(s: String) {
def b = ???
}
}
class C(a: A) {}
Is there a way to access implicit class B (and in particular its method b) from the class C, without importing it explicitly? (Please note that class A could not be a trait since it also injects some classes.)
Solution 1 (import a._
)
Well, yes, as already noted in the comments, from your requirements it is not obvious why you wouldn't just import a._
in the body of C
:
class A {
implicit class B(arg: String) {
def b: String = ???
}
}
class C(a: A) {
import a._
{
println("hello".b)
}
}
This one line really doesn't hurt anyone.
If you still don't like it, then the problem might be elsewhere. Thus my second proposal.
Solution 2 (separating typeclass-like A
-interface from .b
-syntax)
This other solution is less about the reduction of number of import
s in your code, and it doesn't even keep class B
inside A
. But it might address another issue that you maybe just can't quite articulate: it separates the functionality provided by A
from the syntax provided by B
.
The structure of the following snippet is inspired by the design of the Scala Cats library, that follows a very clear policy with the implicit declarations, always separating the typeclass defintions from the syntax.
The main idea is that:
AIntf
provide actual functionalityB
provides only some additional "pimp-my-library"-style methodsand that we want to keep these two things separate.
Here is how to separate them, thereby also avoiding import a._
inside of C
. First, you define the interface that describes the functionality provided by A
:
trait AIntf {
def usefulOperationOnStrings(s: String): String
}
Then you can implement it by a few different A`s:
class A extends AIntf {
def usefulOperationOnStrings(s: String): String = "<foo>" + s + "</foo>"
}
class A2 extends AIntf {
def usefulOperationOnStrings(s: String): String = s.toUpperCase
}
Note that the object B
has disappeared from A
. Instead, it is moved in a separate syntax
-package, and renamed to A_Ops
. The method b
is also renamed to a
:
object syntax /* should be package, object only for script */ {
object a {
class A_Ops(wrapped: String, ai: AIntf) {
def a: String = ai.usefulOperationOnStrings(wrapped)
}
implicit def everyStringHasAOps(s: String)(implicit ai: AIntf): A_Ops = {
new A_Ops(s, ai)
}
}
}
This is how you use it:
A_Intf
syntax.a._
a
-argument of C
as implicit"string".a
syntax inside C
without further imports.In code:
import myproject.AIntf
import myproject.syntax.a._
class C(implicit val a: AIntf) {
{
println("hello".a)
}
}
Now the implementations of AIntf
and the syntax .a
become independent. You can inject A2
instead of A
. Or you can change the syntax from "str".a
to "str".somethingEntirelyDifferent
.
The full code snippet:
import scala.language.implicitConversions
object myproject /* should be package, object only for script */ {
trait AIntf {
def usefulOperationOnStrings(s: String): String
}
object syntax /* should be package, object only for script */ {
object a {
class A_Ops(wrapped: String, ai: AIntf) {
def a: String = ai.usefulOperationOnStrings(wrapped)
}
implicit def everyStringHasAOps(s: String)(implicit ai: AIntf): A_Ops = {
new A_Ops(s, ai)
}
}
}
class A extends AIntf {
def usefulOperationOnStrings(s: String): String = "<foo>" + s + "</foo>"
}
class A2 extends AIntf {
def usefulOperationOnStrings(s: String): String = s.toUpperCase
}
}
import myproject.AIntf
import myproject.syntax.a._
class C(implicit val a: AIntf) {
{
println("hello".a)
}
}
val c1 = new C()(new myproject.A)
val c2 = new C()(new myproject.A2)
// prints:
// <foo>hello</foo>
// HELLO
Unfortunately, I have no clue what guice
is going to do with an implicit argument, have not tried it yet. It might force you to write
class C @Inject()(val a: AIntf) {
implicit aintf: AIntf = a
...
}
which then becomes longer then the simple import
mentioned in the first part.