disabling implicit conversions

1,218 views
Skip to first unread message

Grzegorz Balcerek

unread,
Feb 16, 2011, 4:25:40 PM2/16/11
to scala...@googlegroups.com
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

unread,
Feb 16, 2011, 4:28:17 PM2/16/11
to scala...@googlegroups.com
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

unread,
Feb 16, 2011, 5:27:29 PM2/16/11
to Grzegorz Balcerek, scala...@googlegroups.com
2011/2/16 Grzegorz Balcerek <gbal...@echostar.pl>

What about scalac -Yno-imports?
 
 
Regards,
Grzegorz Balcerek
 

Paul Phillips

unread,
Feb 16, 2011, 9:16:43 PM2/16/11
to Grzegorz Balcerek, scala...@googlegroups.com
On 2/16/11 1:25 PM, Grzegorz Balcerek wrote:
> 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

unread,
Feb 16, 2011, 11:46:03 PM2/16/11
to Grzegorz Balcerek, scala...@googlegroups.com
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

unread,
Feb 16, 2011, 11:58:25 PM2/16/11
to scala...@googlegroups.com
From: "Paul Phillips" <pa...@improving.org>
> 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

unread,
Feb 17, 2011, 12:06:50 AM2/17/11
to Grzegorz Balcerek, scala...@googlegroups.com
On 2/16/11 8:58 PM, Grzegorz Balcerek wrote:
> 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

unread,
Feb 17, 2011, 12:08:28 AM2/17/11
to Grzegorz Balcerek, scala...@googlegroups.com
On 2/16/11 9:06 PM, Paul Phillips wrote:
> 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

unread,
Feb 17, 2011, 12:14:56 AM2/17/11
to Grzegorz Balcerek, scala...@googlegroups.com
On 2/16/11 9:08 PM, Paul Phillips wrote:
> ...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

unread,
Feb 17, 2011, 12:30:25 AM2/17/11
to scala...@googlegroups.com
From: "Paul Phillips" <pa...@improving.org>

> 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 => _, _ }


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

unread,
Feb 17, 2011, 12:40:40 AM2/17/11
to Grzegorz Balcerek, scala...@googlegroups.com
On 2/16/11 9:30 PM, Grzegorz Balcerek wrote:
> 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

unread,
Feb 17, 2011, 12:51:49 AM2/17/11
to scala...@googlegroups.com
----- Original Message -----
From: "Paul Phillips" <pa...@improving.org>
> As discussed in the ticket, you can only mask predef imports from the
> first line.

Thanks for explaining!
Grzegorz Balcerek

Josh Suereth

unread,
Feb 19, 2011, 6:29:41 AM2/19/11
to Grzegorz Balcerek, scala...@googlegroups.com

In your scope write:

Object Donotuseme

implicit def intWrapper(x : Int) = Donotuseme

That will shadow the implicit and hide it behind a useless one.

Jim Balter

unread,
Feb 19, 2011, 7:16:40 AM2/19/11
to Josh Suereth, Grzegorz Balcerek, scala...@googlegroups.com
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

Josh Suereth

unread,
Feb 19, 2011, 7:30:03 AM2/19/11
to Jim Balter, Grzegorz Balcerek, scala...@googlegroups.com

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" <J...@balter.name> 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




On Sat, Feb 19, 2011 at 3:29 AM, Josh Suereth <joshua....@gmail.com> wrote:
>

> In your scope...


Jim Balter

unread,
Feb 19, 2011, 4:18:29 PM2/19/11
to Josh Suereth, Grzegorz Balcerek, scala...@googlegroups.com
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

Josh Suereth

unread,
Feb 19, 2011, 9:50:05 PM2/19/11
to Jim Balter, Grzegorz Balcerek, scala...@googlegroups.com
Thanks!  Those made some great examples for the ticket (#4271)
Reply all
Reply to author
Forward
0 new messages