【问题标题】:Implicit argument in constructor vs method signature构造函数与方法签名中的隐式参数
【发布时间】:2018-05-22 00:26:25
【问题描述】:

编译器在BroFinder1BroFinder2 中看到导致第一个失败的哪些差异?我真的需要 BroFinder1 在不使用 Aux Pattern 等模式的情况下正常工作。

trait Brother[I] {
  type Bro
  def get: Bro
}

class Foo
object Foo {
  implicit object bro extends Brother[Foo] {
    override type Bro = Bar

    override def get = new Bar
  }
}

class Bar {
  def barSpecificAction(): Unit = ()
}

class BroFinder1[I](implicit val b: Brother[I]) {
  def brotherOf: b.Bro = b.get
}

class BroFinder2[I] {
  def brotherOf(implicit b: Brother[I]): b.Bro = b.get
}


new BroFinder1[Foo].brotherOf.barSpecificAction() // Doesn't compile
//error: Error:(73, 32) value barSpecificAction is not a member of _1.b.Bro
//new BroFinder1[Foo].brotherOf.barSpecificAction();
                          ^
new BroFinder2[Foo].brotherOf.barSpecificAction() // Ok

//scala version: 2.12.4

【问题讨论】:

  • 请提供错误信息。
  • @RichDougherty 更新了帖子。

标签: scala type-inference generic-programming implicits


【解决方案1】:

这不是一个完美的答案,但可能会提供一些见解。这个问题似乎与implicit 完全无关。它似乎与另一个相当先进的 Scala 特性有关:路径依赖类型。特别是问题似乎来自Scala类型系统不够强大,无法在以下代码中精确表达finder1finder2之间的类型差异:

object Foo {

  implicit object bro extends Brother[Foo] {
    override type Bro = Bar

    override def get = new Bar
  }

  object bro2 extends Brother[Foo] {
    override type Bro = Foo

    override def get = new Foo
  }

}


val finder1 = new BroFinder1[Foo]
val finder2 = new BroFinder1[Foo]()(Foo.bro2)
val bof1 = finder1.brotherOf
val bof2 = finder2.brotherOf

旁注:某些参数是 implicit 的事实并不能使它成为“常数”,因为无论如何您始终可以显式传递参数,或者您可以不同上下文中不同的可见隐含值。

AFAIU 最精确的类型finder1finder2 在此示例中可能被分配只是BroFinder1[Foo]。特别是无法捕获 b 隐式变量的不同值,因此无法进一步准确地传递编码在该值内的路径相关类型的值。因此,最好的编译器知道bof1bof2 的类型是它们都具有_1.b.Bro 形式的类型,其中_1 表示BroFinder1[Foo] 的某些特定实例。所以编译器不能确定bof1实际上是Bar类型,而bof2Foo类型。

第二个例子有效,因为隐式参数是在相同的上下文中捕获的,所以编译器确切地知道brotherOf 的结果类型是什么。

我知道的唯一解决方法并不完全适合您的情况:使用“Aux”类型。如果您的 BroFinder 还采用了 Foo 类型的显式参数,则解决方案可能如下所示:

type BrotherAux[I, B] = Brother[I] {type Bro = B}

class BroFinder3[I, B](val v: I)(implicit val b: BrotherAux[I, B]) {
  def brotherOf: B = b.get
}

new BroFinder3(new Foo).brotherOf.barSpecificAction()

但在你的情况下,它的帮助必须小得多

class BroFinder1[I, B](implicit val b: BrotherAux[I, B]) {
  def brotherOf: B = b.get
}

new BroFinder1[Foo, Bar].brotherOf.barSpecificAction()

【讨论】:

  • 天哪,我真的希望 Aux 不是解决方案。太多的类型参数除了让代码变得丑陋之外,在我的例子中也导致了一些隐含的解析问题,只有在代码中引入类型成员后才能解决。也许我应该问这个问题
  • @shayan,通常使问题尽可能接近真实情况是个好主意。您可以等待一段时间等待其他答案,如果没有更好的答案,请使用更接近的示例提出一个新问题(并可能参考这个问题以获得更有意义的答案)。
  • @shayan,我仍然想知道为什么你真的想要一个完全独立的“兄弟”类型,而不是像 RichIntStringOps 这样的隐式包装类
  • 你的意思是implicit class ExtensionMethods[T](t: T) {def brotherOf[B](implicit b: Brother[T]): b.Bro = b.get} 吗?对不起,我不确定你的意思。
  • @shayan,不,我的意思是完全摆脱brotherOf 并将所有barSpecificAction 方法移动到implicit class FooBrotherMethods(val foo:Foo) extends AnyVal 之类的东西。其中一种方法也可以返回一个伴生对象,类似于 Scala 的标准集合 companion 方法所做的。换句话说,我不明白你真正的问题,你所展示的看起来像是XY problem 的一个例子
【解决方案2】:

因为new BroFinder2[Foo].brotherOf 属于Bar 类型,而Bar 有方法barSpecificActionnew BroFinder1[Foo].brotherOf 属于存在类型

x.Bro forSome { val x: Brother[Foo] }

它没有方法barSpecificAction

【讨论】:

  • 在 dotty 中删除存在类型是否意味着它的编译方式会发生变化?
  • 在控制台中的 dotty 0.6.0-bin-20171207-63e491a-NIGHTLY scala> val bar1 = new BroFinder1[Foo].brotherOf 产生 val bar1: Main.BroFinder1[Main.Foo]#b.Bro = Main$Bar@4657d37a 尽管 val bar1: BroFinder1[Foo]#b.Bro = new BroFinder1[Foo].brotherOf 无法编译。所以存在类型存在但你不能使用它们。当然val bf = new BroFinder1[Foo]; val bar1: bf.b.Bro = bf.brotherOf 在 Dotty 和 Scala 中都可以编译。
猜你喜欢
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 1970-01-01
  • 2011-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多