Warning: mkdir(): No space left on device in /var/www/group-telegram/post.php on line 37

Warning: file_put_contents(aCache/aDaily/post/rareilly/--): Failed to open stream: No such file or directory in /var/www/group-telegram/post.php on line 50
Ra'Reilly - Заметки про Android и не только | Telegram Webview: rareilly/164 -
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: |

In 2018, Russia banned Telegram although it reversed the prohibition two years later. Asked about its stance on disinformation, Telegram spokesperson Remi Vaughn told AFP: "As noted by our CEO, the sheer volume of information being shared on channels makes it extremely difficult to verify, so it's important that users double-check what they read." Perpetrators of such fraud use various marketing techniques to attract subscribers on their social media channels. But Kliuchnikov, the Ukranian now in France, said he will use Signal or WhatsApp for sensitive conversations, but questions around privacy on Telegram do not give him pause when it comes to sharing information about the war. The perpetrators use various names to carry out the investment scams. They may also impersonate or clone licensed capital market intermediaries by using the names, logos, credentials, websites and other details of the legitimate entities to promote the illegal schemes.
from ar


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