6
votes

There is a Shadowed Implicit Value Members section in StringOps doc. E.g.:

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

Implicit information
This member is added by an implicit conversion from StringOps to String performed by method unaugmentString in scala.Predef.

Shadowing
This implicitly inherited member is shadowed by one or more members in this class. To access this member you can use a type ascription:

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

Definition Classes
String

But when I try to run following program:

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

Calling the String.split(arg0: String, arg1: Int) doesn't need using type ascription as in the doc described.

So what is the Shadowed Implicit Value Members referring to? I tried to ask google but cannot find any reference.

Is it something like:

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
}

Then the String.split(...) and StringOps.split function signatures are different, so there will no need be "type ascription".

Is this what "Shadowed Implicit Value Members" stands for? I'm a little puzzled. Thanks!

4

4 Answers

6
votes

Normally the Scala compiler will look to perform implicit conversions when you call a method that doesn't exist for a given type:

case class Foo(x :String)

implicit class Bar(foo: Foo) {
  def barOnly = "w00p"
}

println( Foo("test").barOnly )

Here when we call the method barOnly on an instance of Foo the scala compiler can see it needs to do an implicit conversion from Foo to Bar to provide us with that method and we get the expected output of w00p. Try it

However if a method with the same signature exists in Foo and Bar, then we have some shadowing and the scala compiler will not do the implicit conversion unless we explicitly ask for it with type ascription:

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 )

The output is:

Foo: test
Bar: test

Try it

In your example of split in the scaladocs, there are methods called split on both String and StringOps, however they take different argument types, so I'm not entirely sure why the docs are warning us about having to use type ascription. We do not need to disambiguate anything for the compiler in this situation and type ascription has no effect:

import scala.collection.immutable.StringOps

val stringOps: StringOps = "aaa bbb ccc"

println( stringOps.split("a", 2).mkString )
println( (stringOps: String).split("a", 2).mkString )

The output for these two lines is identical:

aa bbb ccc
aa bbb ccc

Try it

Maybe just an error in the docs.

0
votes

"aaa bbb ccc" is a String object, not a StringOps object, so the comment is not relevant.

This is a corner case where you would probably never create a StringOps object explicitly, only ever relying on implicit conversions, so this Scaladoc comment seems kind of useless.

0
votes

The scaladoc is telling you about non StringOps methods that you can call on a StringOps instance. Invoking this method will trigger an implict conversion from a StringOps to a String.

We hardly ever work with raw StringOps objects so the implicit conversion is unlikely in practice.

Type ascription is explained 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> 
-1
votes

It looks like type ascription is necessary when you have an implicit class that overloads the method with a different signature. This is in conflict with what the other answers above are saying.

Take the apply method of TreeMap, for example. Say I want to overload this with a method that takes a zero-indexed integer position, so I can index into TreeMap as if it was an array:

 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 

You'll note that the last lines fail. What is happening here is that 2 is being converted into a double, and then looked up with TreeMap.apply, and of course it fails. This happens even though we are ascribing it with : Int res1 is ok though, as we are using type ascription on m.

In the example in the answer by Theon, you can't see any difference between StringOps and String call to split, but I think this is because they are meant to produce the same output, even if they are implemented differently.

Why this happens, I don't know. It's pretty confusing if you ask me; but I think it's to discourage people from doing what I showed in the above example.