Discussion:
disabling implicit conversions
Grzegorz Balcerek
2011-02-16 21:25:40 UTC
Permalink
Hello,
Is there a way to disable an implicit conversion?
I know I can change the way an implicit conversion works. For example:

scala> 200.toHexString
res0: String = c8

scala> implicit def intWrapper(x:Int) = new runtime.RichInt(255)
intWrapper: (x: Int)scala.runtime.RichInt

scala> 200.toHexString
res1: String = ff

Is there a way to disable the conversion?
I know can do the following as well:

scala> implicit def intWrapper(x:Int):runtime.RichInt = throw new Exception
intWrapper: (x: Int)scala.runtime.RichInt

scala> 200.toHexString
java.lang.Exception
at .intWrapper(<console>:6)
at .<init>(<console>:9)
(...)

Is there another way? Is it possible to make scala give a compile error instead of an exception?

Regards,
Grzegorz Balcerek
mepcotterell
2011-02-16 21:28:17 UTC
Permalink
I haven't tested this, but if you define another implicit conversion such
that the two are ambiguous, would that give a compile error?
Nils Kilden-Pedersen
2011-02-16 22:27:29 UTC
Permalink
Post by Grzegorz Balcerek
Hello,
Is there a way to disable an implicit conversion?
scala> 200.toHexString
res0: String = c8
scala> implicit def intWrapper(x:Int) = new runtime.RichInt(255)
intWrapper: (x: Int)scala.runtime.RichInt
scala> 200.toHexString
res1: String = ff
Is there a way to disable the conversion?
scala> implicit def intWrapper(x:Int):runtime.RichInt = throw new Exception
intWrapper: (x: Int)scala.runtime.RichInt
scala> 200.toHexString
java.lang.Exception
at .intWrapper(<console>:6)
at .<init>(<console>:9)
(...)
Is there another way? Is it possible to make scala give a compile error
instead of an exception?
What about scalac -Yno-imports?
Post by Grzegorz Balcerek
Regards,
Grzegorz Balcerek
Paul Phillips
2011-02-17 02:16:43 UTC
Permalink
Post by Grzegorz Balcerek
Is there another way? Is it possible to make scala give a compile error
instead of an exception?
The mechanism is less than entirely satisfactory, but here you go.

// this is a source file, don't imagine it'll work in the repl
import Predef.{ intWrapper => _ }

object Test {
def main(args: Array[String]): Unit = {
200.toHexString
}
}

See some comments at

https://lampsvn.epfl.ch/trac/scala/ticket/1931
Lex
2011-02-17 04:46:03 UTC
Permalink
Not sure what exactly you are looking for, but I use an erasure trick to
disable implicit conversions on function arguments.

The trick is to force an implicit conversion you can control. This requires
the target type that is a subclass of your type. However you probably dont
want to extend your class or convert your objects just to disable an
implicit conversion. The solution is phantom types, they can be created
without physically extending your class.

Below is an example with an integer and double vectors. An integer vector
can be implicitly converted to a double vector (this is useful when adding
vectors), however we do not want conversion to take place when a method
modifies the argument. So instead we create a phantom type outVec2 and
define a conversion from Vec2 to outVec2. Notice that outVec2 behaves like a
subclass Vec2 in all respects. Moreover the extra trait Implicits[Off] is
erased during compilation leaving Vec2 in the compiled code. Because the
forced implicit conversion simply returns the argument, it will be entirely
optimised away by jit, leaving zero performance penalty.

The main method calls two set methods that modify the arguments:
- The setToZero method takes a double vectors. When called with an integer
vector, an implicit conversion takes place and the original integer vector
remains unchanged.
- The setToOne method takes a outVec2 phantom type. When called, a forced
implicit conversion must takes place, preventing a second conversion from an
integer vector. So when you try to invoke setToOne with an integer vector,
you get a compile time error.

{{{
object conversions {
implicit def i2d(u: Vec2i) :Vec2 = new Vec2(u.x, u.y)

type outVec2 = Vec2 with Implicits[Off]
implicit def d2out(u: Vec2) :outVec2 = u.asInstanceOf[outVec2]
}

trait On
trait Off extends On
trait Implicits[+T]

case class Vec2i(var x: Int, var y: Int)
case class Vec2(var x: Double, var y: Double) // extends Implicits[On]


object Main {
import conversions._

def setToZero(u: Vec2) {
u.x = 0
u.y = 0
}
def setToOne(u: outVec2) {
// uncomment extends above to make this true.
println(u.isInstanceOf[outVec2])
u.x = 1
u.y = 1
}

def main(args: Array[String]) {
val d = new Vec2(2, 2)
setToZero(d); println(d)
setToOne(d); println(d)

val i = new Vec2i(2, 2)
setToZero(i); println(i)
setToOne(i); println(i) // compile-time error
}
}
}}}

The is a slight problem when you call .isInstanceOf[outVec2] on the
arguments of the type outVec2. This call will return false. The same applies
to the match statements. This can be fixed by making Vec2 extend
Implicits[On].

Hope this is useful.

Cheers,
Lex
Grzegorz Balcerek
2011-02-17 04:58:25 UTC
Permalink
Post by Paul Phillips
The mechanism is less than entirely satisfactory, but here you go.
// this is a source file, don't imagine it'll work in the repl
import Predef.{ intWrapper => _ }
It looks like the statement above disables not just the import of the intWrapper method.

$ type Test1.scala
import Predef.{intWrapper => _}
object Test1 { println(200.toHexString) }

$ scalac Test1.scala
Test1.scala:2: error: not found: value println
object Test1 { println(200.toHexString) }
^
one error found

$ type Test2.scala
import Predef.{boolean2Boolean => _}
object Test2 { println(200.toHexString) }

$ scalac Test2.scala
Test2.scala:2: error: not found: value println
object Test2 { println(200.toHexString) }
^
one error found

$ type Test3.scala
import Predef.{boolean2Boolean => _, println}
object Test3 { println(200.toHexString) }

$ scalac Test3.scala
Test3.scala:2: error: value toHexString is not a member of Int
object Test3 { println(200.toHexString) }
^
one error found

$


Actually, there is already a comment in the ticket you pointed to with a similar example. The ticket is about an enhancement, but it looks more like a bug to me than enhancement.

Regards,
Grzegorz Balcerek
Paul Phillips
2011-02-17 05:06:50 UTC
Permalink
Post by Grzegorz Balcerek
It looks like the statement above disables not just the import of the intWrapper method.
Yeah, that's how it's supposed to work. I was just demonstrating that
you can mask it. You still have to import Predef, like this.

import Predef.{ intWrapper => _, _ }
Paul Phillips
2011-02-17 05:08:28 UTC
Permalink
Yeah, that's how it's supposed to work. I was just demonstrating that
you can mask it. You still have to import Predef, like this.
import Predef.{ intWrapper => _, _ }
...but I see that then the masking doesn't work, so yes, it appears to
be bug stricken.
Paul Phillips
2011-02-17 05:14:56 UTC
Permalink
Post by Paul Phillips
...but I see that then the masking doesn't work, so yes, it appears to
be bug stricken.
I take it back. I was fooled because masking intWrapper allowed
longWrapper to pop up and do the conversion. But now we get the
expected behavior:

import Predef.{ intWrapper => _, longWrapper => _, _ }

object Test {
def main(args: Array[String]): Unit = {
200.toHexString
println(5)
}
}


a.scala:6: error: value toHexString is not a member of Int
200.toHexString
^
one error found
Grzegorz Balcerek
2011-02-17 05:30:25 UTC
Permalink
Post by Paul Phillips
I take it back. I was fooled because masking intWrapper allowed
longWrapper to pop up and do the conversion. But now we get the expected
import Predef.{ intWrapper => _, longWrapper => _, _ }
I don't get it. Why is the following program working?

$ type Test1.scala
import Predef._
import Predef.{intWrapper => _,longWrapper => _,_}
object Test1 extends Application { println(200.toHexString) }

$ scalac Test1.scala

$ scala Test1
c8


When I comment out the first line it is not compiling any more.

$ type Test1.scala
//import Predef._
import Predef.{intWrapper => _,longWrapper => _,_}
object Test1 extends Application { println(200.toHexString) }

$ scalac Test1.scala
Test1.scala:3: error: value toHexString is not a member of Int
object Test1 extends Application { println(200.toHexString) }
^
one error found

$

Why does the first line make any difference? Aren't Predef members supposed
to be implicitly imported? Isn't the first line is just redoing what is
implicitly done anyway?

Regards,
Grzegorz Balcerek
Paul Phillips
2011-02-17 05:40:40 UTC
Permalink
Post by Grzegorz Balcerek
I don't get it. Why is the following program working?
As discussed in the ticket, you can only mask predef imports from the
first line.

I did mention it was less than entirely satisfactory.
Grzegorz Balcerek
2011-02-17 05:51:49 UTC
Permalink
----- Original Message -----
Post by Paul Phillips
As discussed in the ticket, you can only mask predef imports from the
first line.
Thanks for explaining!
Grzegorz Balcerek
Josh Suereth
2011-02-19 11:29:41 UTC
Permalink
In your scope write:

Object Donotuseme

implicit def intWrapper(x : Int) = Donotuseme

That will shadow the implicit and hide it behind a useless one.
Post by Paul Phillips
As discussed in the ticket, you can only mask predef imports from the
first line.

Thanks for explaining!
Grzegorz Balcerek
Jim Balter
2011-02-19 12:16:40 UTC
Permalink
By that reasoning there could only be one implicit that provides methods for
a given type, but there can be any number of them -- e.g., Predef.scala
defines quite a few. The compiler looks for an implicit that provides the
invoked method -- toHexString here. Since no such method is defined for
Donotuseme, it isn't relevant to that search:

scala> object foo {
| object Donotuseme
| implicit def intWrapper(x : Int) = Donotuseme
| val h = 3.toHexString
| }
defined module foo

scala> foo.h
res14: String = 3

-- Jim
Post by Josh Suereth
Object Donotuseme
implicit def intWrapper(x : Int) = Donotuseme
That will shadow the implicit and hide it behind a useless one.
Post by Paul Phillips
As discussed in the ticket, you can only mask predef imports from the
first line.
Thanks for explaining!
Grzegorz Balcerek
Josh Suereth
2011-02-19 12:30:03 UTC
Permalink
You should check the rules again in the sls. If this does not work in all
scenarios it is a violation of the spec. I'll file another bug.

Implicits pulled from the local scope must be accessible by name with no
prefix. We are shadowing the predef._ implicit here and nullifyng it. It
works great for non predef imported implicits. I suggest you try this to
see what the correct behavior should be.

There's a minor bug where you *have* to shadow an implicit with an implicit
to remove it from the potential lookup.

On Feb 19, 2011 7:17 AM, "Jim Balter" <Jim-***@public.gmane.org> wrote:

By that reasoning there could only be one implicit that provides methods for
a given type, but there can be any number of them -- e.g., Predef.scala
defines quite a few. The compiler looks for an implicit that provides the
invoked method -- toHexString here. Since no such method is defined for
Donotuseme, it isn't relevant to that search:

scala> object foo {

| object Donotuseme
| implicit def intWrapper(x : Int) = Dono...
| val h = 3.toHexString
| }
defined module foo

scala> foo.h
res14: String = 3

-- Jim
In your scope...
Jim Balter
2011-02-19 21:18:29 UTC
Permalink
Ah, sorry, good point. Interestingly, it does appear to work for implicits
defined directly in object Predef rather than in LowPrioryImplicits (or
perhaps it's due to some other difference; I haven't explored it further):

scala> object foo {
| object Donotuseme
| implicit def intWrapper(x: Int) = Donotuseme
| implicit def any2ArrowAssoc[A](x: A) = Donotuseme
| 3 to 5
| 3 -> 5
| }
<console>:10: error: value -> is not a member of Int
3 -> 5
^

-- Jim
Post by Josh Suereth
You should check the rules again in the sls. If this does not work in all
scenarios it is a violation of the spec. I'll file another bug.
Implicits pulled from the local scope must be accessible by name with no
prefix. We are shadowing the predef._ implicit here and nullifyng it. It
works great for non predef imported implicits. I suggest you try this to
see what the correct behavior should be.
There's a minor bug where you *have* to shadow an implicit with an implicit
to remove it from the potential lookup.
By that reasoning there could only be one implicit that provides methods
for a given type, but there can be any number of them -- e.g., Predef.scala
defines quite a few. The compiler looks for an implicit that provides the
invoked method -- toHexString here. Since no such method is defined for
scala> object foo {
| object Donotuseme
| implicit def intWrapper(x : Int) = Dono...
| val h = 3.toHexString
| }
defined module foo
scala> foo.h
res14: String = 3
-- Jim
In your scope...
Josh Suereth
2011-02-20 02:50:05 UTC
Permalink
Thanks! Those made some great examples for the ticket
(#4271<https://lampsvn.epfl.ch/trac/scala/ticket/4271>
)
Post by Jim Balter
Ah, sorry, good point. Interestingly, it does appear to work for implicits
defined directly in object Predef rather than in LowPrioryImplicits (or
scala> object foo {
| object Donotuseme
| implicit def intWrapper(x: Int) = Donotuseme
| implicit def any2ArrowAssoc[A](x: A) = Donotuseme
| 3 to 5
| 3 -> 5
| }
<console>:10: error: value -> is not a member of Int
3 -> 5
^
-- Jim
Post by Josh Suereth
You should check the rules again in the sls. If this does not work in all
scenarios it is a violation of the spec. I'll file another bug.
Implicits pulled from the local scope must be accessible by name with no
prefix. We are shadowing the predef._ implicit here and nullifyng it. It
works great for non predef imported implicits. I suggest you try this to
see what the correct behavior should be.
There's a minor bug where you *have* to shadow an implicit with an
implicit to remove it from the potential lookup.
By that reasoning there could only be one implicit that provides methods
for a given type, but there can be any number of them -- e.g., Predef.scala
defines quite a few. The compiler looks for an implicit that provides the
invoked method -- toHexString here. Since no such method is defined for
scala> object foo {
| object Donotuseme
| implicit def intWrapper(x : Int) = Dono...
| val h = 3.toHexString
| }
defined module foo
scala> foo.h
res14: String = 3
-- Jim
In your scope...
Loading...