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, потому что это полезные данные, а это основной сценарий использования.
Где мне это пригодится в жизни или применение Nothing в Kotlin на примере