tags:

views:

92

answers:

2

I am struggling with a Scala implicit conversion problem. The following code snippet illustrates my problem :

import org.junit.{ Test, Before, After };

class ImplicitsTest {

    implicit def toStringWrapper(str: String) = new StringWrapper(str);

    @Test
    def test(){
        val res1: Predicate = "str" startsWith "other";
    }

}

class StringWrapper(str: String){

    def startsWith(other: String): Predicate = null;

}

trait Predicate

How can I force the String literal "str" to be converted through the implicit conversion toStringWrapper to get startsWith return Predicate instead of Boolean?

The code example doesn't compile. I am aware that String has already a startsWith method, I just want to use a different one, and I thought that using implicit conversions might be a way to do it.

+9  A: 

Scala thankfully doesn't let you sneak replacement methods in without you noticing--if you call a method on a class, and the class has that method, that's the method call you get. To do otherwise would likely cause all sorts of confusion.

That leaves you with two other options: (1) Rename the method (2) Add a specific method to do the conversion.

The second approach works like so: you define a class that has both the method that you want and a uniquely-named method that returns itself, and optionally an implicit conversion from that class back to string if you want to be able to use the custom item like the original string (as if it had extended String):

object ImplicitExample {
  class CustomString(s: String) {
    def original = s
    def custom = this
    def startsWith(other: String): Int = if (s.startsWith(other)) 1 else 0
  }
  implicit def string_to_custom(s: String) = new CustomString(s)
  implicit def custom_to_string(c: CustomString) = c.original

  def test = {
    println("This".custom.startsWith("Thi"))
    println("This".custom.length())
  }
}

scala> ImplicitExample.test
1
4

scala> 
Rex Kerr
@rex-kerr, thanks for the detailed answer.
Timo Westkämper
I like the pattern of providing an alias for `this` to avoid a second temporary object.
retronym
+2  A: 

An implicit conversion is triggered in Scala only if the receiver does not contain the method being invoked or if an expression has a type different than the expected type.

Since the String object above contains the method startsWith, no implicit conversion is triggered. The compiler does, however, check if the type of the right hand expression "str".startsWith("other"); (that is Boolean) can be converted to a Predicate. Since there is no such implicit conversion in scope, it reports an error.

Note, also, that implicit conversions must be unambiguous. For instance, you might try to override Scala string behaviour for some other methods using implicit conversions. Java String objects are not Scala sequences (Seq), meaning that they do not have methods such as sorted, which returns the sorted version of the sequence. If you invoke it on a string:

scala> "string" sorted
res1: String = ginrst

This works because an implicit conversion defined in the Predef object gets triggered. Note that providing your own implicit conversion results in an error:

scala> implicit def wrap(s: String) = new { def sorted = "Hi!" }
wrap: (s: String)java.lang.Object{def sorted: java.lang.String}

scala> "string" sorted
<console>:7: error: type mismatch;
 found   : java.lang.String
 required: ?{val sorted: ?}
 Note that implicit conversions are not applicable because they are ambiguous:
 ...
axel22
@axel22, thanks for your answer.
Timo Westkämper