Итак, я читал о стандартных функциях run, let, also, apply
, поскольку видел, как они используются в разных случаях в моей кодовой базе, и обнаружил две вещи:
Все четыре из этих методов развертывают необязательные параметры, но они также имеют разные типы возвращаемых данных и разные контексты, так сказать.
Читая в Документацию Kotlin по нулевой безопасности , кажется, что рекомендуемый способ развернуть дополнительные опции в Kotlin - использовать let
.
Немного предыстории относительно этих четырех методов:
val string = ""
val thing: String = string.apply { length } + string.also { it.length }
val other: Int = string.run { length } + string.let { it.length }
Этот код компилируется из-за типов возврата четырех методов. Для получения дополнительной информации об этих методах, вы можете взглянуть на это сообщение в блоге
Однако, поскольку все четыре метода развернуты необязательно, некоторые разработчики в моей команде предпочли бы написать:
textView?.run {
someLongMethod(
text = text,
blah = blah,
blah2 = blah2
)
}
над
textView?.let {
someLongMethod{
text = it.text,
blah = it.blah,
blah2 = it.blah2
)
}
Вышеупомянутое предпочтение является стилистически разумным, а также непротиворечивым, поскольку в обоих случаях мы не используем тип возврата блоков let
или run
. Кроме того, использование run
разворачивает необязательный textView
, также удаляя лишнюю it.
от необходимости перезаписывать в моем вызове метода.
Вся предпосылка моего аргумента основана на том факте, что приемлемо, стилистически и условно, делать вышеуказанное.
Если это так, то в данном конкретном контексте замена let
на also
или run
на apply
не приведет к изменениям ни внутри их соответствующих блоков, ни побочных эффектов.
Поэтому я чувствую, что использование любого из этих четырех методов для развертывания необязательных компонентов допускает дикие несоответствия между разработчиками в командах, и в этом случае нет никаких аргументов в пользу использования одного над другим, за исключением личного выбора.
Поэтому я предложил следующие два расширения (хотя второе может использовать более хорошее имя):
@UseExperimental(ExperimentalContracts::class)
inline fun <T, R> T.unwrap(block: (T) -> R) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
}
@UseExperimental(ExperimentalContracts::class)
inline fun <T, R> T.unwrapInScope(block: T.() -> R) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
Так что блок кода, написанный ранее, становится:
textView?.unwrapInScope {
someLongMethod(
text = text,
blah = blah,
blah2 = blah2
)
}
И поскольку мы не используем тип возвращаемого значения, в этом случае предпочтительнее использовать расширение. Другое расширение unwrap
будет использоваться тогда, когда let
предпочтительнее, чем run
.
Это, конечно, только мой аргумент. Однако мне интересно, есть ли что-то, что я пропускаю? Разве мы не должны использовать run
для развёртывания опций, для начала? Или Kotlin
должен был встроить различные методы, чтобы устранить это несоответствие?