views:

260

answers:

5

This simple test, of course, works as expected:

scala> var b = 2
b: Int = 2

scala> b += 1   

scala> b
res3: Int = 3

Now I bring this into scope:

class A(var x: Int) { def +=(y:Int) { this.x += y } }
implicit def int2A(i:Int) : A = new A(i)             

I'm defining a new class and a += operation on it, and a convenient implicit conversion for those times when I want to add an Int to A's Int value.

I never expected this would affect the way my regular Int operations behave, when the "A" class is not at all part of the expression.

But it does:

scala> var b:Int = 0
b: Int = 0

scala> b += 1

scala> b  
res29: Int = 0

scala> b += 2

scala> b
res31: Int = 0

What seems to be happening here is that the b:Int is implicitly converted to an "A", which is not bound to any variable, and then += is invoked on it, discarding the results.

Scala seems to give high precedence the implicit conversion over the natural += behavior (compiler magic, not an actual method) that is already defined to Ints. Common-sense as well as a C++ background tells me implicits should only be invoked as a last resort, when the compilation would otherwise fail. That leads to several questions...

  • Why? Is this a bug? Is it by design?
  • Is there a work-around (other than not using "+=" for my DSL's "+=" operation)?

Thanks

+3  A: 

I don't think it is a bug. Actually, Int only has a "+" method but doesn't have a "+=" method. b += 1 would transform to b = b + 1 in compile time if there is not a other implicit which has a "+=" method exists.

Eastsun
I think, that even withstanding your explanation it's probably a bug, and it should try the transformation before trying an implicit conversion for +=
Ken Bloom
+7  A: 

From Programming in Scala, Chapter 17:

Whenever you write a += b, and a does not support a method named +=, Scala will try interpreting it as a = a + b.

The class Int does not contain method +=. However class A provides += method. That might be triggering the implicit conversion from Int to A.

missingfaktor
Thanks. I'm starting to get very disappointed at the amount of "magic" that has been implemented in what was supposed to be a very "orthogonal, clean language". They should just implement += in Int and get rid of the compiler magic. I hope there will be a Scala 3.0 :)
Alex R
@Alex How would you implement `+=` on an immutable class?
Ben Lings
@Ben: +1, Good point.
missingfaktor
Daniel
@Ben, good point (I think C++ gets around it by letting you define += as pass-by-reference, which is not supported in the JVM).@Daniel, the += magic is breaking my DSL. I hope there would be a way to turn it off or unimport it.
Alex R
WAlex: You can unimport it: `import {int2A=>_}`.
Ken Bloom
+1  A: 

Even withstanding Eastsun's explanation, it seems like this is a bug, and it should try the b=b+1 transformation before trying an implicit conversion for +=.

Please ask this question to the scala-user email list by emailing [email protected] or by visiting n4.nabble.com/Scala-User-f1934582.html. If it's a bug, that's where it will be noticed and fixed.

Ken Bloom
Thanks... Scala bug #3377
Alex R
+1  A: 

Scala seems to give high precedence the implicit conversion over the natural += that is already defined to Ints.

Which version of Scala are you talking about? I don't know of any version that has a += method on Int. Certainly, none of the still supported versions do, that must be some really ancient version you have there.

And since there is no += on Int, but you are calling a += method on Int, Scala tries to satisfy that type constraint via an implicit conversion.

Jörg W Mittag
Edited my question to clarify Int.+= is compiler magic, not an actual method.
Alex R
+11  A: 

As others have noted, Int cannot have a += "method", because Int is immutable. What happens instead is that x += 1 is treated as a short form for x = x + 1, but only if there is no method called += that is defined on the type. So method resolution takes precedence.

Given that Scala lets you define += methods and also lets you do += on variables, could we have changed the priority of the two? I.e. try expanded += first and only if that fails search for a method named +=?

Theoretically yes, but I argue it would have been worse than the current scheme. Practically, no. There are many types in Scala's collection library that define both a + method for non-destructive addition and a += method for destructive addition. If we had switched the priority around then a call like

  myHashTable += elem

would expand to

  myHashTable = myHashTable + elem

So it would construct a new hashtable and assign this back to the variable, instead of simply updating an element. Not a wise thing to do...

Martin Odersky
But couldn't we try running myHashTable's `+=`, then seeing that it's not found we expand it to `myHashTable = myHashTable + elem`, and failing that fall back to implicit conversions?
Ken Bloom
This would complicate things further. And it would invalidate a class of useful refactorings, where inheritance is replaced by views.
Martin Odersky
@Martin: I agree that trying to find a += method before expanding to "... = ... + ..." makes perfect sense. What I don't agree is forcing an implicit conversion just to find a += in a class which is "unrelated" to the expression at hand. Another approach would be to formally define += in Int so that it gets "found" sooner (before the implicit conversion), but use special syntax to indicate it's "not a real method" and explicitly tell the compiler to fallback to the expansion magic. BTW I'm flattered to get an answer directly from the top :) Thanks
Alex R