Pull to refresh

Comments 10

Спасибо, познавательно.

А out нужен только для наследников? Без него можно использовать только конкретный тип, но не наследник?

Если не указывать ни in, ни out, то дженерик является инвариантным.
В таком случае будет между Desk<User> и Desk<Manager> нет никакой иерархии наследования. Экземпляр с наследником создать тоже получится, но без ковариантности не получится его использовать там, где ожидался Desk<User>

> И точно так же выглядит иерархия для классов с generic-параметром Desk за счет ковариантности.

Я думаю здесь помогла бы диаграмка именно для самих generic классов (Desk + sub/superclass), так как ко(нтр)вариантность наиболее явно проявляется как раз для них, а не для параметров типов (User в данном случае).

А выведение типов тут не работает? Нельзя написать типа:

var a = Either.Left(null)

a = Either.Right("string")

Можно, если указать явно тип. По умолчанию компилятор выводит тип в лоб.

В твоем примере a имеет тип Either.Left<Nothing?> - потому что указано значение null и не ясно это Int?, String? или Any?, компилятор сам по себе тип не расширяет, поэтому в данном примере он считает, что это самый минимальный тип и это Nothing?.

Для понятности я заменю строчку с нуллом на var a = Either.Left(1) , тогда тип будет Either.Left<Int> это то же самое, что и Either<Int, Nothing> по определению класса Left.

Чтобы все работало так, как ты хочешь, надо явно указать какие типы ожидаются и вывод будет работать.

var a: Either<Int?, String> = Either.Left(null)
a = Either.Right("string")

Ясно. Было бы круто не указывать тип в месте создания переменной, видимо, единственный способ это сделать - отказаться от дженериков и в объявлении Either указать конкретные типы A и B. Либо абстрактный Either унаследовать в конкретный EitherIntOrString (условно).

Проблема решается заданием интерфейса и дополнительными функциями вокруг Either.

Правильный тип выведется за счет типа интерфейса.

interface MyService {
  fun f(): Either<Error, String>
}

enum class Error {
  NO_DATA
}

class MyImpl : MyService {

  fun getData(): String?
  
  override fun f() = either {
    val data = getData()
    ensure(data != null) {
      NO_DATA
    }
    data
  }
}

Более подробно такой подход к работе с ошибками можно в статье из документации Arrow

Вчера вечером читал что такое Nothing и не понял зачем он может быть нужен, а сегодня читаю вашу статью и приходит осознание. Очень классная статья!!!

А как теперь оттуда данные забрать?
Данные мы положили без проблем, а как теперь считывать?

Дальше можно работать, например, в функциональном стиле, используя:

fun <A, B> Either<A, B>.onLeft(action: (A) -> Unit): Either<A, B>

fun <A, B> Either<A, B>.onRight(action: (B) -> Unit): Either<A, B>

Или использовать функции getOrNull, getOrElse, leftOrNull. В данном случае, get подразумевает получение right, потому что это полезные данные, а это основной сценарий использования.

Sign up to leave a comment.

Articles