Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why won't Scala use implicit conversion here?

I'm trying to call this set method documented here, in the Java library jOOQ, with signature:

<T> ... set(Field<T> field, T value)

This Scala line is a problem:

 .set(table.MODIFIED_BY, userId)  

MODIFIED_BY is a Field<Integer> representing the table column. userId is Int. Predef has an implicit conversion from Int to Integer, so why doesn't it use it? I get this:

type mismatch; found: org.jooq.TableField[gen.tables.records.DocRecord,Integer]    
            required: org.jooq.Field[Any] 
Note: Integer <: Any 
(and org.jooq.TableField[gen.tables.records.DocRecord,Integer] <: 
     org.jooq.Field[Integer]), but Java-defined trait Field is invariant in type T. 
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)

Update - About Vinicius's Example

Rather than try to explain this in comments, here is a demonstration that there is no implicit conversion being called when you use a type with covariant parameter, like List[+T]. Let's say I put this code in a file, compile, and run it...

case class Foo(str: String)

object StackOver1 extends App {

  implicit def str2Foo(s: String): Foo = { 
    println("In str2Foo.")
    new Foo(s)
  }

  def test[T](xs: List[T], x: T): List[T] = {
    println("test " + x.getClass)
    xs
  }

  val foo1 = new Foo("foo1")
  test(List(foo1), "abc")
}

You'll see that it calls test, but never the implicit conversion from String "abc" to Foo. Instead it's picking a T for test[T] that is a common base class between String and Foo. When you use Int and Integer it picks Any, but it's confusing because the runtime representation of the Int in the list is Integer. So it looks like it used the implicit conversion, but it didn't. You can verify by opening a Scala prompt...

scala> :type StackOver1.test(List(new java.lang.Integer(1)), 2)
List[Any]
like image 988
Rob N Avatar asked Nov 02 '22 14:11

Rob N


1 Answers

I don't know anything aboutjOOQ, but I think the issue is that Scala does not understand java generics very well. Try:

scala> def test[T](a : java.util.ArrayList[T], b: T) = {  println(a,b) }
scala> val a = new java.util.ArrayList[Integer]()
scala> val b = 12
scala> test(a,b)
<console>:11: error: type mismatch;
 found   : java.util.ArrayList[Integer]
 required: java.util.ArrayList[Any]
Note: Integer <: Any, but Java-defined class ArrayList is invariant in type E.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)
              test(a,b)

Sounds familiar??

And to fix, just inform the type T to call the method: test[Integer](a,b) works fine.

EDIT:

There a few things involved here:

  1. Erasure -> When compiled the type of the generic will disappear by erasure. The compiler will use Object which Scala, will treat as Any. However a ArrayList[Integer] is not an ArrayList[Any], even though Integer is any. The same way that TableField[gen.tables.records.DocRecord,Integer] is not a Field[Any].

  2. Type inference mechanism -> it will figure out what type T should be and to do that it will use the intersection dominator of the types passed (in our case the first common ancestor). Page 36 of Scala Language Spec, which in our examples above will lead use to Any.

  3. Implicit conversion -> it is the last step and would be called if there was some type to be converted to another one, but since the type of the arguments were determined to be the first common ancestor, there is no need to convert and we will never have a implicit conversion if we don't force the type T.

A example to show how the common ancestor is used to determine T:

scala> def test[T](a: T, b: T): T = a
scala> class Foo
scala> class Boo extends Foo
scala> test(new Boo,new Foo)
res2: Foo = Boo@139c2a6
scala> test(new Boo,new Boo)
res3: Boo = Boo@141c803
scala> class Coo extends Foo
scala> test(new Boo,new Coo)
res4: Foo = Boo@aafc83
scala> test(new Boo,"qsasad")
res5: Object = Boo@16989d8

Summing up, the implicit method does not get called, because the type inference mechanism, determines the types before getting the argument and since it uses the common ancestor, there is no need for a implicit conversion.

Your code produces an error due to erasure mechanism which disappear with the type information that would be important to determine the correct type of the argument.

@RobN, thanks for questioning my answer, I learned a lot with the process.

like image 144
Vinicius Miana Avatar answered Nov 15 '22 07:11

Vinicius Miana