Avoiding implicit def ambiguity in Scala
Asked Answered
I

6

15

I am trying to create an implicit conversion from any type (say, Int) to a String...

An implicit conversion to String means RichString methods (like reverse) are not available.

implicit def intToString(i: Int) = String.valueOf(i)
100.toCharArray  // => Array[Char] = Array(1, 0, 0)
100.reverse // => error: value reverse is not a member of Int
100.length // => 3

An implicit conversion to RichString means String methods (like toCharArray) are not available

implicit def intToRichString(i: Int) = new RichString(String.valueOf(i))
100.reverse // => "001"
100.toCharArray  // => error: value toCharArray is not a member of Int
100.length // => 3

Using both implicit conversions means duplicated methods (like length) are ambiguous.

implicit def intToString(i: Int) = String.valueOf(i)
implicit def intToRichString(i: Int) = new RichString(String.valueOf(i))
100.toCharArray  // => Array[Char] = Array(1, 0, 0)
100.reverse // => "001"
100.length // => both method intToString in object $iw of type 
   // (Int)java.lang.String and method intToRichString in object
   // $iw of type (Int)scala.runtime.RichString are possible 
   // conversion functions from Int to ?{val length: ?}

So, is it possible to implicitly convert to String and still support all String and RichString methods?

Insulting answered 27/8, 2009 at 6:15 Comment(0)
S
2

Either make a huge proxy class, or suck it up and require the client to disambiguate it:

100.asInstanceOf[String].length

Sentence answered 27/8, 2009 at 8:57 Comment(3)
It seems that 'suck it up' is the best option. API design in Scala is significantly more difficult than being an API consumer. I wanted to create a datatype that wraps a value and allows the API user to treat the datatype as if it were the type of the interned value. E.g. if it wraps an Int, then treat the object like an int. Turns out this is too difficult. I'm considering giving my datatype helper methods: type T val internedValue: T def s = internedValue.asInstanceOf[String] def i = internedValue.asInstanceOf[Int] ... etcInsulting
Prefer (100: String).length, as this is type-safe, whereas asInstanceOf isn't. Besides, it's prettier and shorter.Jentoft
Not only that, but asInstanceOf will never work here, as it only ever does downcasting (which is bound to fail here) and won't kick off any implicit conversion. In other words 100.asInstanceOf[String].length will always fail with a ClassCastExceptionActinolite
C
5

I don't have a solution, but will comment that the reason RichString methods are not available after your intToString implicit is that Scala does not chain implicit calls (see 21.2 "Rules for implicits" in Programming in Scala).

If you introduce an intermediate String, Scala will make the implict converstion to a RichString (that implicit is defined in Predef.scala).

E.g.,

$ scala
Welcome to Scala version 2.7.5.final [...].
Type in expressions to have them evaluated.
Type :help for more information.

scala> implicit def intToString(i: Int) = String.valueOf(i)
intToString: (Int)java.lang.String

scala> val i = 100
i: Int = 100

scala> val s: String = i
s: String = 100

scala> s.reverse
res1: scala.runtime.RichString = 001
Cavafy answered 27/8, 2009 at 9:7 Comment(1)
Is this "only" a drawback of allowing circular implicit convertible relations, or are there other reasons? Since even if you have a circle, you should statically be able to find the "nearest" implementation. Exploring a graph without visiting graphs twice is not that hard, although it might take a while.Theran
G
5

As of Scala 2.8, this has been improved. As per this paperAvoiding Ambiguities) :

Previously, the most specific overloaded method or implicit conversion would be chosen based solely on the method’s argument types. There was an additional clause which said that the most specific method could not be defined in a proper superclass of any of the other alternatives. This scheme has been replaced in Scala 2.8 by the following, more liberal one: When comparing two different applicable alternatives of an overloaded method or of an implicit, each method gets one point for having more specific arguments, and another point for being defined in a proper subclass. An alternative “wins” over another if it gets a greater number of points in these two comparisons. This means in particular that if alternatives have identical argument types, the one which is defined in a subclass wins.

See that other paper (§6.5) for an example.

Glomerulonephritis answered 16/1, 2011 at 12:53 Comment(1)
Your link to the first paper is broken.Umbilication
A
2

The only option I see is to create a new String Wrapper class MyString and let that call whatever method you want to be called in the ambiguous case. Then you could define implicit conversions to MyString and two implicit conversions from MyString to String and RichString, just in case you need to pass it to a library function.

Application answered 27/8, 2009 at 6:37 Comment(3)
This sounds like a fair approach, but a lot of work. Plus I suspect it is not future proof, as other implicit conversions are introduced.Insulting
Sure it is a lot of work, but not a lot of thinking, just typing. ;) And why wouldn't it be future proof? Nobody forces you to use any implicit conversions that might be introduced in the future, not even those in the preload.Application
@Kim Stebel: Sure it is a lot of work, but not a lot of thinking, just typing programmers are lazy thinkers, not hard-working typists.Balaam
S
2

Either make a huge proxy class, or suck it up and require the client to disambiguate it:

100.asInstanceOf[String].length

Sentence answered 27/8, 2009 at 8:57 Comment(3)
It seems that 'suck it up' is the best option. API design in Scala is significantly more difficult than being an API consumer. I wanted to create a datatype that wraps a value and allows the API user to treat the datatype as if it were the type of the interned value. E.g. if it wraps an Int, then treat the object like an int. Turns out this is too difficult. I'm considering giving my datatype helper methods: type T val internedValue: T def s = internedValue.asInstanceOf[String] def i = internedValue.asInstanceOf[Int] ... etcInsulting
Prefer (100: String).length, as this is type-safe, whereas asInstanceOf isn't. Besides, it's prettier and shorter.Jentoft
Not only that, but asInstanceOf will never work here, as it only ever does downcasting (which is bound to fail here) and won't kick off any implicit conversion. In other words 100.asInstanceOf[String].length will always fail with a ClassCastExceptionActinolite
B
2

The accepted solution (posted by Mitch Blevins) will never work: downcasting Int to String using asInstanceOf will always fail.

One solution to your problem is to add a conversion from any String-convertible type to RichString (or rather, to StringOps as it is now named):

implicit def stringLikeToRichString[T](x: T)(implicit conv: T => String) = new collection.immutable.StringOps(conv(x))

Then define your conversion(s) to string as before:

scala> implicit def intToString(i: Int) = String.valueOf(i)
warning: there was one feature warning; re-run with -feature for details
intToString: (i: Int)String

scala> 100.toCharArray
res0: Array[Char] = Array(1, 0, 0)

scala> 100.reverse
res1: String = 001

scala> 100.length
res2: Int = 3
Brooklime answered 6/5, 2015 at 8:18 Comment(0)
E
1

I'm confused: can't you use .toString on any type anyway thus avoiding the need for implicit conversions?

Escalera answered 27/8, 2009 at 7:56 Comment(1)
I don't think that would change matters, except to perhaps introduce null pointer exceptions. To be clear, you are proposing changing String.valueOf(i) to i.toString ?Insulting

© 2022 - 2024 — McMap. All rights reserved.