This page is no longer maintained — Please continue to the home page at www.scala-lang.org

issue with implicit conversion

8 replies
Raphael Jolly
Joined: 2009-02-27,
User offline. Last seen 1 year 27 weeks ago.

Hello,

I am encountering an issue with implicit conversion in my development of
polynomial arithmetic for a computer algebra project. Here is a
simplified case that shows the problem:

trait Ring[T <: Ring[T]] {
def +(c: T): T
def *(c: T): T
}

class A extends Ring[A] {
def +(c: A) = new A
def *(c: A) = new A
}

class Poly[C <: Ring[C]](val c: C) extends Ring[Poly[C]] {
def +(c: Poly[C]) = new Poly(this.c+c.c)
def *(c: Poly[C]) = new Poly(this.c*c.c)
}

implicit def coef2poly[C <: Ring[C]](c: C) = new Poly(c)

val a = new A
val x = new Poly(new A)

x+a // works
a+x // works

val y = new Poly(new Poly(new A))

x+y*x // works
x*y+x // works
y*x+x // works

x+x*y

error: type mismatch;
[java] found : Poly[Poly[A]]
[java] required: Poly[A]
[java] x+x*y
[java] ^

I would like to know if this is by design, or if not, if there is a
chance that it might be corrected in the future.

Many thanks !
Raphael

Jon Pretty
Joined: 2009-02-02,
User offline. Last seen 42 years 45 weeks ago.
Re: issue with implicit conversion

Hi Raphael,

Raphael Jolly wrote:
> I would like to know if this is by design, or if not, if there is a
> chance that it might be corrected in the future.

Interesting.

So the issue is that whilst

val z = x*y
x + z

is fine,

x + (x*y)

is not.

The reason is that the compiler logic which decides whether an implicit
conversion is applicable or not isn't considering the types of things
given that other implicits might be applied. If you apply either
implicit explicitly, i.e. replace either x with coef2poly(x), it copes fine.

Implicits aren't transitive by design, but this isn't quite
transitivity, and you've clearly provided a case which seems like it
ought to be permitted, though I say that without having considered the
complete ramifications of such a change...

Maybe someone else has an opinion?

Jon

Raphael Jolly
Joined: 2009-02-27,
User offline. Last seen 1 year 27 weeks ago.
Re: issue with implicit conversion

Hello,

Thanks for all the responses,

Jon Pretty wrote:
> Hi Raphael,
>
> Raphael Jolly wrote:
>> I would like to know if this is by design, or if not, if there is a
>> chance that it might be corrected in the future.
>
> Interesting.
>
> So the issue is that whilst
>
> val z = x*y
> x + z
>
> is fine,
>
> x + (x*y)
>
> is not.
>
> The reason is that the compiler logic which decides whether an implicit
> conversion is applicable or not isn't considering the types of things
> given that other implicits might be applied.

Then how comes this simpler example is working:

class A

class B {
def +(that: B) = new B
}

implicit def a2b(a: A) = new B

val a = new A

a+a // works

?

> If you apply either
> implicit explicitly, i.e. replace either x with coef2poly(x), it copes fine.

yes, but coef2poly(x) + (x*y) is not a satisfying mathematical
expression. I am seeking something similar to how the computer algebra
system Sage is working.

> Implicits aren't transitive by design, but this isn't quite
> transitivity, and you've clearly provided a case which seems like it
> ought to be permitted, though I say that without having considered the
> complete ramifications of such a change...

This would be fantastic.

Raphael

Jon Pretty
Joined: 2009-02-02,
User offline. Last seen 42 years 45 weeks ago.
Re: Re: issue with implicit conversion

Hi Raphael,

Raphael Jolly wrote:
> Then how comes this simpler example is working:
>
> [...]

I'd guess because the '+' defined isn't overloaded by the existence of
'+' defined on the object, but I'm only speculating...

> yes, but coef2poly(x) + (x*y) is not a satisfying mathematical
> expression. I am seeking something similar to how the computer algebra
> system Sage is working.

Oh, I wasn't seriously suggesting you did that... ;-)

> This would be fantastic.

If nobody else has anything else to say about it in the near future,
maybe post it as a bug report on the Scala bug tracker, and then it will
either get fixed or rejected.

Cheers,
Jon

Raphael Jolly
Joined: 2009-02-27,
User offline. Last seen 1 year 27 weeks ago.
Re: issue with implicit conversion

Jon Pretty wrote:
> Hi Raphael,
>
> Raphael Jolly wrote:
>> Then how comes this simpler example is working:
>>
>> class A
>>
>> class B {
>> def +(that: B) = new B
>> }
>>
>> implicit def a2b(a: A) = new B
>>
>> val a = new A
>>
>> a+a // works
>
> I'd guess because the '+' defined isn't overloaded by the existence of
> '+' defined on the object, but I'm only speculating...

This is right, as this modified example shows:

class A

class B {
def +(that: B) = new B
}

implicit def a2b(a: A) = new B
implicit def int2b(n: Int) = new B

val a = new A

a+1 // works

1+a

:16: error: overloaded method value + with alternatives
(Double)Double (Float)Float (Long)Long (Int)Int
(Char)Int (Short)Int (Byte)Int
(java.lang.String)java.lang.String cannot be applied to (A)
ret(0) = 1+a
^

So it seems that the compiler is only looking for an implicit conversion
when there is no method at all, not when there is one with the wrong
arguments. I would be surprised this one is not on purpose. This is
sometimes annoying, but not as bad as my original problem as far as my
project is concerned. Although these two problems may be related, of
course, in which case perhaps x+x*y is bound to fail. This would be too
bad really.

Raphael

Jesper Nordenberg
Joined: 2008-12-27,
User offline. Last seen 42 years 45 weeks ago.
Re: issue with implicit conversion

Raphael Jolly wrote:
> So it seems that the compiler is only looking for an implicit conversion
> when there is no method at all, not when there is one with the wrong
> arguments.

Yes, AFAIK method parameter types are not taken into account when
resolving implicit conversions, which is quite unfortunate as it limits
the utility of them.

/Jesper Nordenberg

Alex Boisvert
Joined: 2008-12-16,
User offline. Last seen 42 years 45 weeks ago.
Re: Re: issue with implicit conversion
On Tue, Mar 3, 2009 at 2:09 AM, Raphael Jolly <raphael.jolly@free.fr> wrote:
Jon Pretty wrote:
> Hi Raphael,
>
> Raphael Jolly wrote:
>> Then how comes this simpler example is working:
>>
>> class A
>>
>> class B {
>>   def +(that: B) = new B
>> }
>>
>> implicit def a2b(a: A) = new B
>>
>> val a = new A
>>
>> a+a // works
>
> I'd guess because the '+' defined isn't overloaded by the existence of
> '+' defined on the object, but I'm only speculating...

This is right, as this modified example shows:

class A

class B {
 def +(that: B) = new B
}

implicit def a2b(a: A) = new B
implicit def int2b(n: Int) = new B

val a = new A

a+1 // works

1+a

<console>:16: error: overloaded method value + with alternatives
(Double)Double <and> (Float)Float <and> (Long)Long <and> (Int)Int <and>
(Char)Int <and> (Short)Int <and> (Byte)Int <and>
(java.lang.String)java.lang.String cannot be applied to (A)
      ret(0) = 1+a
                ^

So it seems that the compiler is only looking for an implicit conversion
when there is no method at all, not when there is one with the wrong
arguments. I would be surprised this one is not on purpose. This is
sometimes annoying, but not as bad as my original problem as far as my
project is concerned. Although these two problems may be related, of
course, in which case perhaps x+x*y is bound to fail. This would be too
 bad really.

Not quite.   The issue here is conflicting implicits because Scala defines the "+" operator in Predef.anyToString.

Your example works if you use a different operator (e.g. ++)

scala> class A
defined class A

scala> class B { def ++(that: B) = new B }
defined class B

scala> implicit def a2b(a: A) = new B
a2b: (A)B

scala> implicit def int2b(n: Int) = new B
int2b: (Int)B

scala> val a = new A
a: A = A@10b755d

scala> a ++ 1
res1: B = B@171b246

scala> 1 ++ a
res2: B = B@933cba

alex

Raphael Jolly
Joined: 2009-02-27,
User offline. Last seen 1 year 27 weeks ago.
Re: issue with implicit conversion

Raphael Jolly wrote:
> Hello,
>
> I am encountering an issue with implicit conversion in my development of
> polynomial arithmetic for a computer algebra project. Here is a
> simplified case that shows the problem:
>
> trait Ring[T <: Ring[T]] {
> def +(that: T): T
> def *(that: T): T
> }
>
> class A extends Ring[A] {
> def +(that: A) = new A
> def *(that: A) = new A
> }
>
> class Poly[C <: Ring[C]](val c: C) extends Ring[Poly[C]] {
> def +(that: Poly[C]) = new Poly(this.c+that.c)
> def *(that: Poly[C]) = new Poly(this.c*that.c)
> }
>
> implicit def coef2poly[C <: Ring[C]](c: C) = new Poly(c)
>
> val a = new A
> val x = new Poly(new A)
>
> x+a // works
> a+x // works
>
> val y = new Poly(new Poly(new A))
>
> x+y*x // works
> x*y+x // works
> y*x+x // works
>
> x+x*y
>
> error: type mismatch;
> [java] found : Poly[Poly[A]]
> [java] required: Poly[A]
> [java] x+x*y
> [java] ^
>
> I would like to know if this is by design, or if not, if there is a
> chance that it might be corrected in the future.

The problem occurs in a much simpler situation:

class A {
def +(that: A) = new A
def *(that: A) = new A
}

class B {
def +(that: B) = new B
def *(that: B) = new B
}

implicit def a2b(a: A) = new B

val a = new A
val b = new B

a+b*a // works
a*b+a // works
b*a+a // works

a+a*b

:58: error: type mismatch;
found : B
required: A
ret(0) = a+a*b
^

By the way this is in version 2.7.3.final
Raphael

Raphael Jolly
Joined: 2009-02-27,
User offline. Last seen 1 year 27 weeks ago.
Re: issue with implicit conversion

Jesper Nordenberg wrote:
> Raphael Jolly wrote:
>> So it seems that the compiler is only looking for an implicit conversion
>> when there is no method at all, not when there is one with the wrong
>> arguments.
>
> Yes, AFAIK method parameter types are not taken into account when
> resolving implicit conversions, which is quite unfortunate as it limits
> the utility of them.
>
> /Jesper Nordenberg

Here is what the manual says (ScalaReference.pdf , p101):

"Views are applied in two situations.

1. If an expression e is of type T , and T does not conform to the
expression's expected type pt. In this case an implicit v is searched
which is applicable to e and whose result type conforms to pt. The
search proceeds as in the case of implicit parameters, where the
implicit scope is the one of T => pt. If such a view is found, the
expression e is converted to v (e).

2. In a selection e.m with e of type T, if the selector m does not
denote a member of T. In this case, a view v is searched which is
applicable to e and whose result contains a member named m. The search
proceeds as in the case of implicit parameters, where the implicit scope
is the one of T. If such a view is found, the selection e.m is converted
to v(e).m."

I understand it as saying that argument types are not relevant and that
the name only is searched for. But this is in contradiction with the facts:

class A {
def +(that: A) = new A
}

class B {
def +(that: B) = new B
}

implicit def a2b(a: A) = new B

val a = new A
val b = new B

b+a // works, this one is normal according to 1. above

a+b // works too !!!

Having found the + method of A, the compiler should have stopped
querying a replacement type for a, and not have applied the required
conversion a2b(a)+b.

This fails however when A and B need to be converted to a third type C
for the operation to be possible:

class A {
def +(that: A) = new A
}

class B {
def +(that: B) = new B
}

class C {
def +(that: C) = new C
}

implicit def a2c(a: A) = new C
implicit def b2c(b: B) = new C

val a = new A
val b = new B

a+b // fails

If I remove the + method in A, then it works and yields a2c(a)+b2c(b).
So the + in A is what stops the search process.

Anyway, playing with these combinations I have found a workaround for my
original problem:

class A {
def +(that: Nothing) = () // I'm just adding this overloaded method...
def +(that: A) = new A
def *(that: A) = new A
}

class B {
def +(that: B) = new B
def *(that: B) = new B
}

implicit def a2b(a: A) = new B

val a = new A
val b = new B

a+b*a // works
a*b+a // works
b*a+a // works

a+a*b // ... and then it works.

This is very strange and a bug is all the more likely.

Raphael

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland