【问题标题】:Are DummyImplicits used, and if so, how?是否使用了 DummyImplicits,如果使用,如何使用?
【发布时间】:2021-04-05 16:03:33
【问题描述】:

在琢磨Predef.scala的代码时,我注意到了以下几点:

/** A type for which there is always an implicit value.
 *  @see [[scala.Array$]], method `fallbackCanBuildFrom`
 */
class DummyImplicit

object DummyImplicit {

  /** An implicit value yielding a `DummyImplicit`.
   *   @see [[scala.Array$]], method `fallbackCanBuildFrom`
   */
  implicit def dummyImplicit: DummyImplicit = new DummyImplicit
}

有没有人知道为什么这段看似无用的代码存在?

【问题讨论】:

  • 你有没有按照评论的提示去​​scala.Array$方法fallbackCanBuildFrom的源代码?也许它会让你知道这是做什么用的。

标签: scala


【解决方案1】:

最终归结为type erasure(Java 和 Scala 都使用)。

想象一下这段代码:

object Foo { 
   def foo(p: String) = 1
   def foo(p: Int) = 2
   def foo(p: Any) = 3
} 

object Main extends App {
    Foo.foo("1")
}

这里一切都很好。但是,如果我们将参数从单个值更改为序列呢?

object Foo { 
   def foo(ps: String*) = 1
   def foo(ps: Int*) = 2
   def foo(ps: Any*) = 3
} 

object Main extends App {
    Foo.foo("1")
}

现在我们有一个错误:

Main.scala:4: error: double definition:
def foo(ps: Int*): Int at line 3 and
def foo(ps: Any*): Int at line 4
have same type after erasure: (ps: Seq)Int
       def foo(ps: Any*) = 3
           ^
Main.scala:3: error: double definition:
def foo(ps: String*): Int at line 2 and
def foo(ps: Int*): Int at line 3
have same type after erasure: (ps: Seq)Int
       def foo(ps: Int*) = 2
           ^
two errors found

并看到消息“擦除后具有相同的类型” - 这就是我们的线索。

那么为什么序列失败了?

因为 JVM 不支持泛型 - 这意味着您的强类型集合(如整数或字符串的序列)实际上不支持。它们被编译成 Object 的容器,因为这是 JVM 所期望的。类型被“擦除”。

所以在编译之后,所有这些看起来都像这样(我们马上就会看到它们到底是什么):

object Foo { 
   def foo(ps: Object*) = 1
   def foo(ps: Object*) = 2
   def foo(ps: Object*) = 3
} 

显然这不是我们想要的。

那么 Java 是如何处理这个问题的呢?

它在幕后创建Bridge Methods。魔法!

Scala 并没有做到这一点(尽管它一直是discussed)——而是使用了伪隐式。

让我们改变我们的定义

object Foo { 
   def foo(ps: String*) = 1
   def foo(ps: Int*)(implicit i: DummyImplicit) = 2 
   def foo(ps: Any*)(implicit i1: DummyImplicit, i2: DummyImplicit) = 3 
} 

现在它编译了!但是……为什么?

我们看一下scalac生成的代码(scalac -print foo.scala)

object Foo extends Object {
    def foo(ps: Seq): Int = 1;
    def foo(ps: Seq, i: Predef$DummyImplicit): Int = 2;
    def foo(ps: Seq, i1: Predef$DummyImplicit, i2: Predef$DummyImplicit): Int = 3;
    def <init>(): Foo.type = {
      Foo.super.<init>();
      ()
    }
  };

好的 - 所以我们有三个不同的 foo 方法,它们的不同之处仅在于它们的隐式参数。

现在让我们称呼他们:

object Main extends App {
    Foo.foo("1")
    Foo.foo(1)
    Foo.foo(1.0)
}

那是什么样的(我在这里删除了很多其他代码......)

  Foo.foo(scala.this.Predef.wrapRefArray(Array[String]{"1"}.$asInstanceOf[Array[Object]]()));
  Foo.foo(scala.this.Predef.wrapIntArray(Array[Int]{1}), scala.Predef$DummyImplicit.dummyImplicit());
  Foo.foo(scala.this.Predef.genericWrapArray(Array[Object]{scala.Double.box(1.0)}), scala.Predef$DummyImplicit.dummyImplicit(), scala.Predef$DummyImplicit.dummyImplicit());

因此,每个调用都被赋予了正确消除调用歧义所需的隐式参数。

那么为什么 DummyImplicit 存在呢?确保总有一个隐含值的类型(否则您需要确保它可用)。

它的文档指出“一种始终存在隐含值的类型”。 - 所以隐含值总是存在的,可以在这种情况下使用。

【讨论】:

  • 您完美地解释了它们最常见的用途,但具有讽刺意味的是,您没有提到它似乎被引入的原始原因,正如DummyImplicit 的评论中暗示的那样,这表明它在@ 987654333@。在那里,它用于控制隐式查找的顺序。另外,据我所知,Java 的桥接方法的目的根本不是解决这里讨论的问题。
猜你喜欢
  • 2021-08-12
  • 2014-02-16
  • 1970-01-01
  • 1970-01-01
  • 2022-01-14
  • 2018-12-16
  • 1970-01-01
  • 1970-01-01
  • 2017-07-31
相关资源
最近更新 更多