Telegram Group & Telegram Channel
Продолжаем тренировать #насмотренность с inline-классами. В предыдущем посте я обещал рассказать про boxing inline-классов. Но сначала немного поговорим про примитивы.

Один из моих любимых вопросов на собеседовании — есть ли в Kotlin примитивы? Вот в Java есть примитив int и объект Integer, а в Kotlin только Int. На самом деле при компиляции под JVM компилятор сам решает что использовать. Когда возможно, использует примитив, но если тип используется в дженерике или объявлен как nullable, нужен объект и происходит boxing — примитив оборачивается в объект Integer.

Теперь вернёмся к inline-классам. Подобно примитивам, при работе с inline-классами Kotlin старается избежать лишних обёрток. Чтобы убедиться, что в конкретном случае не происходит boxing, можно посмотреть как код выглядит для JVM через Show Kotlin Bytecode > Decompile. Случаи, когда происходит boxing, подробно описаны в KEEP. Рекомендую прочитать полностью, а пока сосредоточимся на самых интересных моментах.

0️⃣ Нуллабельность

Если нужна нуллабельность, но хочется обойтись без boxing'а, можно создать специальное значение, которое будет заменять null. Помните Color.Unspecified в Compose? Это как раз оно. Кстати, недавно подобные специальные значения добавили и inline-классам входящим в состав TextStyle. В сообщении к коммиту есть бенчмарки показывающие сколько аллокаций удалось на этом сэкономить.

1️⃣ Создание подтипов

Когда в Kotlin появились sealed-интерфейсы, я подумал что они будут неплохо комбинироваться с inline-классами. Была мысль завести абстракцию чтобы одинаково работать со строками и текстовыми ресурсами:

sealed interface TextValue {
fun get(resources: Respurces): String

@JvmInline
value class Plain(val value: String) : TextValue {
fun get(resources: Resources) = value
}

@JvmInline
value class Res(@StringRes val resId: Int) : TextValue {
fun get(resources: Resources) = resources.getString(resId)
}
}


Проблема в том, что тут от inline-классов нет никакого толка. Мы будем использовать интерфейс, а не конкретный inline-класс, так что boxing будет происходить всегда. Компилятор не знает какую именно реализацию интерфейса мы подложим, а значит не может заменить интерфейс на внутренний тип.

Вариантов минимизации boxing'а в подобных случаях как минимум два:

☝️ Если тип внутреннего значения у разных "подтипов" разный, можно хранить общий супертип. Так сделано в Result, внутри лежит Any?, а чтобы отличать контент от ошибки, создан вспомогательный тип Failure, в который оборачиваются ошибки.
✌️ Можно комбинировать тип и значение. Это хорошо работает с примитивами. Например, в Compose TextUnit может иметь размерность Sp и Em, но это всё один inline-класс. Информация о типе хранится в младших битах Long'а, а значение в следующих за ними битах. Другой пример такого подхода — Duration из stdlib.

2️⃣ Дженерики

С использованием inline-класса на позиции дженерика всё понятно — в этом случае без boxing'а не обойтись. Но дженерики указанные у самого inline-класса не влияют на boxing.

Классный пример применения inline-классов с дженериками — безопасные ID. Представим, что у нас есть несколько разных сущностей со строковыми ID и мы хотим чтобы на уровне контракта нельзя было по ID пользователя запросить товар и наоборот. В 2019 году Jake Wharton предложил создавать inline-классы для строгой типизации ID. С тех пор появилась возможность указывать дженерики у inline-классов и теперь можно не плодить отдельные классы-обёртки на каждый тип сущности, достаточно создать один inline-класс с дженериком:
@JvmInline
value class Id<out T>(val value: String)

data class User(val id: Id<User>)


Всем inline-классы в код!

UPD: @senk0n подсказывает, что Romain Guy недавно написал пример как можно в inline-класс запихнуть целую сетку 8х8, каждая ячейка которой может иметь значение 1 или 0.



group-telegram.com/rareilly/164
Create:
Last Update:

Продолжаем тренировать #насмотренность с inline-классами. В предыдущем посте я обещал рассказать про boxing inline-классов. Но сначала немного поговорим про примитивы.

Один из моих любимых вопросов на собеседовании — есть ли в Kotlin примитивы? Вот в Java есть примитив int и объект Integer, а в Kotlin только Int. На самом деле при компиляции под JVM компилятор сам решает что использовать. Когда возможно, использует примитив, но если тип используется в дженерике или объявлен как nullable, нужен объект и происходит boxing — примитив оборачивается в объект Integer.

Теперь вернёмся к inline-классам. Подобно примитивам, при работе с inline-классами Kotlin старается избежать лишних обёрток. Чтобы убедиться, что в конкретном случае не происходит boxing, можно посмотреть как код выглядит для JVM через Show Kotlin Bytecode > Decompile. Случаи, когда происходит boxing, подробно описаны в KEEP. Рекомендую прочитать полностью, а пока сосредоточимся на самых интересных моментах.

0️⃣ Нуллабельность

Если нужна нуллабельность, но хочется обойтись без boxing'а, можно создать специальное значение, которое будет заменять null. Помните Color.Unspecified в Compose? Это как раз оно. Кстати, недавно подобные специальные значения добавили и inline-классам входящим в состав TextStyle. В сообщении к коммиту есть бенчмарки показывающие сколько аллокаций удалось на этом сэкономить.

1️⃣ Создание подтипов

Когда в Kotlin появились sealed-интерфейсы, я подумал что они будут неплохо комбинироваться с inline-классами. Была мысль завести абстракцию чтобы одинаково работать со строками и текстовыми ресурсами:

sealed interface TextValue {
fun get(resources: Respurces): String

@JvmInline
value class Plain(val value: String) : TextValue {
fun get(resources: Resources) = value
}

@JvmInline
value class Res(@StringRes val resId: Int) : TextValue {
fun get(resources: Resources) = resources.getString(resId)
}
}


Проблема в том, что тут от inline-классов нет никакого толка. Мы будем использовать интерфейс, а не конкретный inline-класс, так что boxing будет происходить всегда. Компилятор не знает какую именно реализацию интерфейса мы подложим, а значит не может заменить интерфейс на внутренний тип.

Вариантов минимизации boxing'а в подобных случаях как минимум два:

☝️ Если тип внутреннего значения у разных "подтипов" разный, можно хранить общий супертип. Так сделано в Result, внутри лежит Any?, а чтобы отличать контент от ошибки, создан вспомогательный тип Failure, в который оборачиваются ошибки.
✌️ Можно комбинировать тип и значение. Это хорошо работает с примитивами. Например, в Compose TextUnit может иметь размерность Sp и Em, но это всё один inline-класс. Информация о типе хранится в младших битах Long'а, а значение в следующих за ними битах. Другой пример такого подхода — Duration из stdlib.

2️⃣ Дженерики

С использованием inline-класса на позиции дженерика всё понятно — в этом случае без boxing'а не обойтись. Но дженерики указанные у самого inline-класса не влияют на boxing.

Классный пример применения inline-классов с дженериками — безопасные ID. Представим, что у нас есть несколько разных сущностей со строковыми ID и мы хотим чтобы на уровне контракта нельзя было по ID пользователя запросить товар и наоборот. В 2019 году Jake Wharton предложил создавать inline-классы для строгой типизации ID. С тех пор появилась возможность указывать дженерики у inline-классов и теперь можно не плодить отдельные классы-обёртки на каждый тип сущности, достаточно создать один inline-класс с дженериком:
@JvmInline
value class Id<out T>(val value: String)

data class User(val id: Id<User>)


Всем inline-классы в код!

UPD: @senk0n подсказывает, что Romain Guy недавно написал пример как можно в inline-класс запихнуть целую сетку 8х8, каждая ячейка которой может иметь значение 1 или 0.

BY Ra'Reilly - Заметки про Android и не только


Warning: Undefined variable $i in /var/www/group-telegram/post.php on line 260

Share with your friend now:
group-telegram.com/rareilly/164

View MORE
Open in Telegram


Telegram | DID YOU KNOW?

Date: |

Friday’s performance was part of a larger shift. For the week, the Dow, S&P 500 and Nasdaq fell 2%, 2.9%, and 3.5%, respectively. Given the pro-privacy stance of the platform, it’s taken as a given that it’ll be used for a number of reasons, not all of them good. And Telegram has been attached to a fair few scandals related to terrorism, sexual exploitation and crime. Back in 2015, Vox described Telegram as “ISIS’ app of choice,” saying that the platform’s real use is the ability to use channels to distribute material to large groups at once. Telegram has acted to remove public channels affiliated with terrorism, but Pavel Durov reiterated that he had no business snooping on private conversations. In 2018, Russia banned Telegram although it reversed the prohibition two years later. "There are a lot of things that Telegram could have been doing this whole time. And they know exactly what they are and they've chosen not to do them. That's why I don't trust them," she said. The Security Service of Ukraine said in a tweet that it was able to effectively target Russian convoys near Kyiv because of messages sent to an official Telegram bot account called "STOP Russian War."
from ca


Telegram Ra'Reilly - Заметки про Android и не только
FROM American