Вавр с дженериками выдает несовместимые типы - PullRequest
0 голосов
/ 26 октября 2018

Может ли кто-нибудь объяснить, почему этот код:

interface Lol {
  default Try<Seq<? extends Number>> lol() {
    return Try.of(List::empty);
  }
}

class LolImpl implements Lol {
  @Override
  public Try<Seq<? extends Number>> lol() {
    return Try
      .of(() -> List.of(1, 2, 3))
      //.onFailure(Object::hashCode)
      ;
  }
}

не скомпилируется, если я раскомментирую onFailure оператор?Понятия не имею, что здесь происходит.Как это улучшить?

Ответы [ 3 ]

0 голосов
/ 26 октября 2018

Похоже, что компилятор Java не может определить правильный тип для вас, в этом случае вам необходимо предоставить дополнительную информацию о типе, необходимую для продолжения, например:

class LolImpl implements Lol {

    @Override
    public Try<Seq<? extends Number>> lol() {
        Try<Seq<? extends Number>> res = Try.of(() -> List.of(1, 2, 3));
        return res.onFailure(Object::hashCode);
    }
}
0 голосов
/ 07 ноября 2018

TL; DR

Ответ на ваш вопрос связан с выводом типа Java в сочетании с дисперсией типа (в нашем случае ковариация).В частности, это не имеет ничего общего с Vavr.

  1. Try<List<Integer>> является подтипом Try<? extends Seq<? extends Number>>.
  2. Но Try<List<Integer>> не является подтипом Try<Seq<? extends Number>>.

Измените тип возврата метода (ов) lol() на Try<? extends Seq<? extends Number>>, и все будет хорошо скомпилировано.


Давайте подробно рассмотрим.

public Try<Seq<? extends Number>> lol() {  // line 1
    return Try.of(() -> List.of(1, 2, 3))  // line 2
        //.onFailure(Object::hashCode)     // line 3
    ;
}

Метод lol() возвращает значение типа Try<Seq<? extends Number>> (см. Строку 1).

Оператор return в строке 2 возвращает экземпляр Try, созданный с использованиемзаводской метод Try.of(...).В Vavr 0.9.x он определяется следующим образом:

static <T> Try<T> of(CheckedFunction0<? extends T> supplier) {
    // implementation omitted
}

Компилятор выводит:

// type T = Seq<? extends Number>
Try.of(() -> List.of(1, 2, 3))

, поскольку он должен соответствовать обоим, тип возвращаемого значения метода lol() и CheckedFunction0 подпись фабричного метода Try.of.

Это прекрасно компилируется, потому что функция supplier возвращает значение типа ? extends T, то есть ? extends Seq<? extends Number>, которое совместимо сфактический тип возвращаемого значения List<Integer> (см. раздел TL; DR выше).

Если мы теперь раскомментируем часть .onFailure (строка 3), то аргумент универсального типа T фабричного метода Try.of больше не имеет типа возврата lol().Компилятор выводит T как List<Integer>, потому что он всегда пытается найти наиболее подходящий тип.

.onFailure возвращает значение типа List<Integer>, потому что он возвращает точно такой же типесли его экземпляр.Но Try<List<Integer>> не является подтипом Try<Seq<? extends Number>> (см. Раздел TL; DR выше), поэтому код больше не компилируется.

Делает метод lol() ковариантным в егоВозвращаемый тип будет соответствовать компилятору:

// before: Try<Seq<? extends Number>>
Try<? extends Seq<? extends Number>> lol() { // line 1
    return Try.of(() -> List.of(1, 2, 3))    // line 2
        .onFailure(Object::hashCode);        // line 3
}

Кстати, чтобы определить правильную общую дисперсию во всей иерархии типов Vavr, особенно для коллекций, было одной из сложных частей при создании Vavr.Система типов Java не идеальна, есть еще несколько вещей, которые мы не можем выразить с помощью обобщений Java.См. Также мое сообщение в блоге "Декларация сайта-отклонения в будущей Java"

Отказ от ответственности: я создатель Vavr (ранее Javaslang)

0 голосов
/ 26 октября 2018

Вы можете вызвать Try.of() с явным универсальным типом, возвращаемым для удовлетворения проверок компилятора.Что-то вроде:

Try.<Seq<? extends Number>of(() -> List.of(1,2,3))

Try.of() возвращает тип Try<T>, где T - это тип, возвращаемый поставщиком.А поскольку List.of(T t...) возвращает List<T>, то последний тип, видимый компилятором, равен Try<List<Integer>, что не является тем, что метод возвращает тип, определенный.Обобщения Java с определенным типом являются инвариантными и не поддерживают ковариантные или контравариантные замены, поэтому List<Integer> != List<Number>.

Рабочий пример:

import io.vavr.collection.List;
import io.vavr.collection.Seq;
import io.vavr.control.Try;

interface Lol {
    default Try<Seq<? extends Number>> lol() {
        return Try.of(List::empty);
    }
}

class LolImpl implements Lol {
    @Override
    public Try<Seq<? extends Number>> lol() {
        return Try
                .<Seq<? extends Number>>of(() -> List.of(1, 2, 3))
                .onFailure(t -> System.out.println(t.getMessage()));

    }

    public static void main(String[] args) {
        System.out.println(new LolImpl().lol());
    }
}

Вывод:

Success(List(1, 2, 3))

Типовая проблема вывода типа

Дальнейшие исследования показали, что это, скорее всего, общая проблема компилятора.Взгляните на следующий простой пример Java:

import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;

interface Some<T> {
    static <T> Some<T> of(Supplier<T> supplier) {
        return new SomeImpl<>(supplier.get());
    }

    default Some<T> shout() {
        System.out.println(this);
        return this;
    }

    class SomeImpl<T> implements Some<T> {
        private final T value;

        public SomeImpl(T value) {
            this.value = value;
        }
    }

    static void main(String[] args) {
        final Some<List<CharSequence>> strings = Some.of(() -> Arrays.asList("a", "b", "c"));
    }
}

Этот код компилируется без проблем, и компилятор выводит тип, возвращаемый Arrays.asList() из ожидаемого типа с левой стороны:

enter image description here

Теперь, если я вызову этот метод Some<T>.shout(), который ничего не делает и возвращает Some<T>, компилятор выводит тип не из ожидаемого типа переменной, а из последнего возвращенного типа:

enter image description here

Конечно Arrays.asList("a","b","c") возвращает List<String> и this is the type shout () `метод выводит и возвращает:

enter image description here

Указание явного типа Some<T>.of() решает проблему, как в примере Try.of():

enter image description here

Я искал документацию Oracle по выводу типа, и есть такое объяснение:

Компилятор Java использует преимущества типизации цели, чтобы вывести параметры типа при вызове универсального метода.Целевой тип выражения - это тип данных, который ожидает компилятор Java в зависимости от того, где находится выражение.

Источник: https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html#target_types

Это выглядит так "в зависимости от того, где появляется выражение" в этом случае означает предполагаемый тип из ранеевернул точный тип.Это объясняет, почему при пропуске метода shout() компилятор осознает, что мы ожидаем Some<List<CharSequence>>, а когда мы добавляем метод shout(), он начинает возвращать Some<List<String>>, потому что именно этот метод shout() видит из возвращенного типа Some.of() метод.Надеюсь, это поможет.

...