Scala:使隐式 class 可从 classes 访问,注入定义它的 class

Scala: make an implicit class accessible from classes injecting the class defining it

我在 class 中定义了一个隐式 class 注入到其他 class 中,像这样

class A {
  implicit class B(s: String) {
    def b = ???
  }
}

class C(a: A) {}

有没有一种方法可以从 class C 访问隐式 class B(特别是它的方法 b),而无需显式导入它? (请注意 class A 不能是特征,因为它也注入了一些 classes。)

如评论中所述,只是 import a._:

class A {
  implicit class B(s: String) {
    def b: String = "hello "+ s
  }
}
class C(a: A){
  import a._
  val hello = "world".b
}

val c = new C(new A)
c.hello // "hello world"

解决方案 1(导入 a._

嗯,是的,正如评论中已经指出的那样,根据您的要求,您为什么不在 C 的正文中导入 a._ 并不明显:

class A {
  implicit class B(arg: String) {
    def b: String = ???
  }
}

class C(a: A) { 
  import a._
  { 
    println("hello".b)
  }
}

这一行真的伤不了人。

如果您仍然不喜欢它,那么问题可能出在其他地方。因此我的第二个提议。


解决方案 2(将 typeclass-like A-接口与 .b-语法分开)

这个其他解决方案与减少代码中 import 的数量无关,它甚至没有将 class B 保留在 A 中。但它可能会解决另一个您可能不太清楚的问题:它将 A 提供的功能与 B.

提供的语法分开

以下代码段的结构受到 Scala Cats 库设计的启发,该库遵循非常明确的隐式声明策略,始终将类型 class 定义与语法分开。

主要思想是:

  • AIntf 的实现提供了实际功能
  • B 只提供了一些额外的 "pimp-my-library" 风格的方法

并且我们希望将这两件事分开。

以下是如何将它们分开,从而避免 import a._C 内部。首先,定义描述 A:

提供的功能的接口
  trait AIntf {
    def usefulOperationOnStrings(s: String): String
  }

然后你可以通过几个不同的A来实现它:

  class A extends AIntf {
    def usefulOperationOnStrings(s: String): String = "<foo>" + s + "</foo>"
  }

  class A2 extends AIntf {
    def usefulOperationOnStrings(s: String): String = s.toUpperCase
  }

请注意,对象 B 已从 A 中消失。相反,它被移动到一个单独的 syntax 包中,并重命名为 A_Ops。方法 b 也重命名为 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)
      }
    }
  }

你是这样使用它的:

  • 你在导入中说你想引用接口A_Intf
  • 您在导入中说要使用语法 syntax.a._
  • 您将 Ca 参数声明为隐式
  • 然后你可以在 C 中使用 "string".a 语法,无需进一步导入。

在代码中:

import myproject.AIntf
import myproject.syntax.a._

class C(implicit val a: AIntf) {
  {
    println("hello".a)
  }
}

现在 AIntf 的实现和语法 .a 变得独立了。您可以注入 A2 而不是 A。或者您可以将语法从 "str".a 更改为 "str".somethingEntirelyDifferent.

完整代码片段:

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

不幸的是,我不知道 guice 将如何处理隐式参数,还没有尝试过。它可能会迫使你写

class C @Inject()(val a: AIntf) {
  implicit aintf: AIntf = a
  ...
}

然后变得比第一部分中提到的简单 import 更长。