Почему Scala здесь не использует неявное преобразование?

Я пытаюсь вызвать этот set метод, задокументированный здесь, в библиотека Java jOOQ с подписью:

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

Эта строка Scala представляет собой проблему:

 .set(table.MODIFIED_BY, userId)  

MODIFIED_BY - это Field<Integer>, представляющий столбец таблицы. userId равно Int. Predef имеет неявное преобразование из Int в Integer, так почему же он его не использует? Я получаю это:

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)

Обновление - о примере Винисиуса

Вместо того, чтобы пытаться объяснить это в комментариях, вот демонстрация того, что неявное преобразование не вызывается, когда вы используете тип с ковариантным параметром, например List[+T]. Допустим, я помещаю этот код в файл, компилирую и запускаю ...

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")
}

Вы увидите, что он вызывает test, но никогда не вызывает неявное преобразование String "abc" в Foo. Вместо этого он выбирает T для test[T], который является общим базовым классом между String и Foo. Когда вы используете Int и Integer, он выбирает Any, но это сбивает с толку, потому что время выполнения для Int в списке равно Integer. Похоже, что использовалось неявное преобразование, но этого не произошло. Вы можете проверить, открыв приглашение Scala ...

scala> :type StackOver1.test(List(new java.lang.Integer(1)), 2)
List[Any]

person Rob N    schedule 23.09.2013    source источник


Ответы (1)


Я ничего не знаю о jOOQ, но думаю, проблема в том, что Scala не очень хорошо понимает java-дженерики. Пытаться:

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)

Звучит знакомо??

И чтобы исправить это, просто сообщите типу T о вызове метода: test[Integer](a,b) работает нормально.

РЕДАКТИРОВАТЬ:

Здесь задействовано несколько вещей:

  1. Стирание -> При компиляции тип универсального исчезнет стиранием. Компилятор будет использовать Object, который Scala будет рассматривать как Any. Однако ArrayList [Integer] не является ArrayList [Any], даже если Integer имеет любое значение. Точно так же, как TableField [gen.tables.records.DocRecord, Integer] не является полем [Any].

  2. Механизм вывода типов -> он определит, каким должен быть тип T, и для этого будет использовать доминатор пересечения переданных типов (в нашем случае первый общий предок). Страница 36 Scala Language Spec, которая в наших примерах выше приведет к использованию Any.

  3. Неявное преобразование -> это последний шаг, и он будет вызываться, если есть какой-то тип, который нужно преобразовать в другой, но поскольку тип аргументов был определен как первый общий предок, нет необходимости преобразовывать, и мы будем никогда не будет неявного преобразования, если мы не форсируем тип T.

Пример, показывающий, как общий предок используется для определения 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

Подводя итог, неявный метод не вызывается, потому что механизм вывода типа определяет типы до получения аргумента, и, поскольку он использует общего предка, нет необходимости в неявном преобразовании.

Ваш код вызывает ошибку из-за механизма стирания, который исчезает вместе с информацией о типе, которая была бы важна для определения правильного типа аргумента.

@RobN, спасибо за вопрос к моему ответу, я многому научился в процессе.

person Vinicius Miana    schedule 23.09.2013
comment
Хорошо спасибо. Я понимаю разницу в дисперсии между List и ArrayList, но, к сожалению, добавление [Integer] в этом случае нецелесообразно. Это могло бы случиться слишком часто и сделать код некрасивым. Я хотел бы знать, почему неявное не используется. - person Rob N; 23.09.2013
comment
Я приму этот ответ, если смогу найти что-то в документации или спецификации языка, в котором говорится, почему он не учитывает неявное. Возможно, все параметры типа определены до рассмотрения имплицитов. Если бы это было не так, он мог бы использовать ArrayList[Integer] вместо ArrayList[Any]. Ваш пример не совсем правильный ... он не вызывает неявное преобразование. В данном случае это сбивает с толку, потому что scala использует Integer во время выполнения для Int, но ваша тестовая функция просто получает List[Any]. Функция неявного преобразования не вызывается. - person Rob N; 24.09.2013
comment
@RobN, пожалуйста, помогите мне улучшить свой ответ. Посмотрим, смогу ли я прояснить это. В первом примере воспроизводится ваша ошибка. Компилятор не знает, как сопоставить (javageneric, Int) с (javageneric ‹T›, T). Второй в Scala показывает, что происходит неявное преобразование, обратите внимание, что b преобразуется в java.lang.Integer. Я не знаю, где это находится в документации, я просто показал вам эксперимент. Если у вас есть пример, который может доказать, что я неправ, пожалуйста. - person Vinicius Miana; 24.09.2013
comment
Я думаю, вы ошиблись в этом примере кода. Вы передаете объект типа Class в test в качестве аргумента b. Преобразования не происходит. У вас есть Список [Любой]. Но даже если вы пройдете 12, конверсии все равно не будет. Я дополню свой вопрос примером кода через несколько минут. - person Rob N; 25.09.2013
comment
Обновил вопрос, объясняющий, что я имею в виду. - person Rob N; 25.09.2013
comment
У нас есть разные определения неявно преобразованного. Я говорю о том, когда компилятор вставляет вызов неявной функции. Похоже, SO предупреждает о расширенном обсуждении в комментариях. В любом случае мне этого достаточно. Удачи. - person Rob N; 25.09.2013
comment
@RobN, я обновил свой ответ, надеюсь, теперь он для вас приемлем. Если нет, я хотел бы услышать от вас больше. Я многому учусь в процессе. - person Vinicius Miana; 25.09.2013