【问题标题】:What is "Shadowed Implicit Value Members" in Scala doc?什么是 Scala 文档中的“隐藏的隐式值成员”?
【发布时间】:2013-10-27 07:49:24
【问题描述】:

StringOps 文档中有一个 Shadowed Implicit Value Members 部分。例如:

def split(arg0: String, arg1: Int): Array[String]

隐含信息
该成员是通过 scala.Predef 中的方法 unaugmentString 执行的从 StringOps 到 String 的隐式转换添加的。

阴影
这个隐式继承的成员被这个类中的一个或多个成员遮蔽。 要访问此成员,您可以使用类型归属:

(stringOps: String).split(arg0, arg1)

定义类
字符串

但是当我尝试运行以下程序时:

"aaa bbb ccc".split(" ", 2) //> res0: Array[String] = Array(aaa, bbb ccc)

调用String.split(arg0: String, arg1: Int) 不需要像文档中描述的那样使用type ascription

那么 Shadowed Implicit Value Members 指的是什么?我试图问谷歌,但找不到任何参考。

是不是有点像:

class A {
  def foo() = println("foo")
}

class AOps(a: A) {
  def bar() = println("bar")
  def foo() = println("new foo")
  def foo(i: Int) = println("foo %d".format(i))
}

object Program {
  implicit def A2AOps(a: A) = new AOps(a)         //> A2AOps: (a: A)AOps

  val a = new A()                                 //> a  : A = A@15669ae
  a.foo                                           //> foo
  a.bar                                           //> bar
  (a: AOps).foo                                   //> new foo
  a.foo(1)                                        //> foo 1
}

那么String.split(...)StringOps.split函数签名不同,就不需要“类型归属”了。

这就是“Shadowed Implicit Value Members”的含义吗?我有点疑惑。谢谢!

【问题讨论】:

    标签: scala


    【解决方案1】:

    通常,当您调用给定类型不存在的方法时,Scala 编译器会执行隐式转换:

    case class Foo(x :String)
    
    implicit class Bar(foo: Foo) {
      def barOnly = "w00p"
    }
    
    println( Foo("test").barOnly )
    

    在这里,当我们在 Foo 的实例上调用方法 barOnly 时,scala 编译器可以看到它需要进行从 FooBar 的隐式转换来为我们提供该方法,我们得到了预期w00p 的输出。 Try it

    但是,如果在FooBar 中存在具有相同签名的方法,那么我们会有一些阴影,除非我们使用类型归属明确要求它,否则 scala 编译器不会进行隐式转换:

    case class Foo(x :String) {
      def whoAmI = "Foo: " + x
    }
    
    implicit class Bar(foo: Foo) {
      def whoAmI = "Bar: " + foo.x
    }
    
    println( Foo("test").whoAmI )
    println( (Foo("test"): Bar).whoAmI )
    

    输出是:

    Foo: test
    Bar: test
    

    Try it

    在 scaladocs 中的 split 示例中,StringStringOps 都有名为 split 的方法,但是它们采用不同的参数类型,所以我不完全确定文档为什么会发出警告我们必须使用类型归属。在这种情况下,我们不需要为编译器消除任何歧义,类型归属没有任何影响:

    import scala.collection.immutable.StringOps
    
    val stringOps: StringOps = "aaa bbb ccc"
    
    println( stringOps.split("a", 2).mkString )
    println( (stringOps: String).split("a", 2).mkString )
    

    这两行的输出是相同的:

    aa bbb ccc
    aa bbb ccc
    

    Try it

    也许只是文档中的一个错误。

    【讨论】:

      【解决方案2】:

      "aaa bbb ccc" 是String对象,不是StringOps对象,所以注释不相关。

      这是一个极端情况,您可能永远不会显式创建 StringOps 对象,只会依赖隐式转换,所以这个 Scaladoc 注释似乎有点没用。

      【讨论】:

        【解决方案3】:

        scaladoc 告诉您可以在 StringOps 实例上调用的非 StringOps 方法。调用此方法将触发从 StringOps 到 String 的隐式转换。

        我们几乎从不使用原始 StringOps 对象,因此在实践中不太可能进行隐式转换。

        类型归属解释in this Stack Overflow question

        scala> val so=new scala.collection.immutable.StringOps("aaa bbb ccc")
        so: scala.collection.immutable.StringOps = aaa bbb ccc
        
        scala> so.split(" ",2)
        res8: Array[String] = Array(aaa, bbb ccc)
        
        scala> 
        

        【讨论】:

          【解决方案4】:

          当您有一个使用不同签名重载方法的隐式类时,类型归属似乎是必要的。这与上述其他答案所说的相冲突。

          TreeMapapply方法为例。假设我想用一个采用零索引整数位置的方法来重载它,所以我可以索引到 TreeMap,就好像它是一个数组一样:

           implicit class MapIndexerExtension(m : TreeMap[Double,String]) {
             def apply(idx : Int) = m.take(idx + 1).last
           }
          
           val m = TreeMap( 0.3333333 -> "A third",
                            2.71828 -> "Natural E",
                            3.142 -> "Pi")              //> m  : scala.collection.immutable.TreeMap[Double,String] = Map(0.3333333 -> A 
                                                        //| third, 2.71828 -> Natural E, 3.142 -> Pi)
          
           m(3.142)                                     //> res0: String = Pi
           (m: MapIndexerExtension)(2)                  //> res1: (Double, String) = (3.142,Pi)
           m(2 : Int)                                   //> java.util.NoSuchElementException: key not found: 2.0
           m(2)                                         //> java.util.NoSuchElementException: key 
          

          您会注意到最后几行失败了。这里发生的是 2 被转换成双精度,然后用TreeMap.apply 查找,当然它失败了。即使我们将其归为: Int,也会发生这种情况 res1 没问题,因为我们在 m 上使用类型归属。

          在 Theon 回答的示例中,您看不出 StringOps 和对 split 的 String 调用之间有任何区别,但我认为这是因为它们旨在产生相同的输出,即使它们的实现方式不同.

          为什么会这样,我不知道。如果你问我,这很令人困惑;但我认为这是为了阻止人们做我在上面的例子中展示的事情。

          【讨论】:

          • "...但我认为这是因为它们旨在产生相同的输出,即使它们的实现方式不同。"这是因为调用了类java.lang.String 中定义的相同方法。 StringOps 没有 split(arg0: String)
          猜你喜欢
          • 2011-04-20
          • 2014-03-16
          • 2014-04-30
          • 2010-09-05
          • 2010-12-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-07-03
          相关资源
          最近更新 更多