В чем разница между лямбда-функцией и обычной функцией Kotlin?

Я пытаюсь изучить некоторые основы Android и использовать этот пользовательский интерфейс для генерации случайного числа при нажатии:

@Composable
fun DiceWithButton(modifier: Modifier = Modifier) {
    var value by remember { mutableStateOf(1) }
    fun rollNew() {
        value = (1..6).random()
    }
    Column(modifier = blabla) {
        Text(text = value.toString())
        Button(onClick = rollNew) {
            Text("Roll")
        }
    }
}

Не удается создать сообщение Type mismatch: inferred type is Unit but () -> Unit was expected

Но когда я переписываю rollNew как лямбда, все работает нормально:

var rollNew = {
    value = (1..6).random()
}

Меня это сбивает с толку, поскольку я работаю на Python/Javascript/Golang, где любое значение функции одинаково можно вызвать с помощью ().

В чем разница в Котлине? Почему rollNew имеет тип Unit, если он определен с помощью fun? Или Котлин пытается вызвать rollNew, когда это функция, поэтому Unit?

Чтобы ссылаться на функцию Kotlin вместо лямбды, вам нужно использовать ::. В вашем примере это будет onClick = ::rollNew.

Leviathan 21.04.2024 12:43

Ах, значит, в Котлине функции не являются первоклассными гражданами, а лямбды являются ими, и :: используется для преобразования функции в лямбду?

Bunyk 21.04.2024 12:58

Нет, функции являются первоклассными, и :: не преобразует функцию в лямбду, а вместо этого создает ссылку на эту функцию (чтобы ее можно было передать в качестве параметра). Дополнительную информацию см. в других разделах на связанной странице.

Leviathan 21.04.2024 13:42

Вдобавок к этому: это потому, что в Котлине упоминание имени функции по умолчанию является признаком того, что вы хотите ее выполнить.  (Круглые скобки не всегда необходимы, например, если единственным параметром является лямбда.)  Поэтому, чтобы использовать его в качестве ссылки, вам нужен некоторый синтаксис, который представляет собой ведущий ::.

gidds 22.04.2024 01:59
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
4
82
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Button(onClick = ::rollNew)

Простое написание имени функции само по себе является недопустимым синтаксисом.

Меня это сбивает с толку, поскольку я работаю на Python/Javascript/Golang, где любое значение функции одинаково можно вызвать с помощью ().

В Котлине rollNew не является «значением функции». Это имя, объявленное в объявлении функции, а не само по себе выражение. ::rollNew — это значение функции, а также лямбда-выражения и объявления анонимных функций (эти два вместе называются функциональными литералами).

В чем разница в Котлине? Почему rollNew имеет тип Unit, если он определен с помощью fun? Или Котлин пытается вызвать rollNew, когда это функция, поэтому Unit?

rollNew сам по себе не является выражением вообще, поэтому говорить о его типе — нонсенс. Как вы догадались, это просто компилятор «догадывается», как вы хотите его вызвать. Помимо сообщения об ошибке несравнимых типов, вы также получаете сообщение об ошибке «Ожидается вызов функции rowNew()».

Ах, значит, в Котлине функции не являются первоклассными гражданами, а лямбды являются ими, и :: используется для преобразования функции в лямбду?

Это объединение синтаксиса и семантики. Лямбда-выражения и вызываемые ссылки являются синтаксическими особенностями языка. Семантически они оба представляют идею «функций» — типом этих выражений, среди прочего, являются типы функций. Возможность передавать «функции» и вызывать их — вот что делает функции первоклассными, независимо от синтаксиса, который вы для этого используете. И, конечно же, :: ничего не преобразует в лямбды. Это полностью отличается от синтаксиса лямбда-выражения.

По аналогии, это похоже на то, что val str = Foo выдает ошибку, но val str = "Foo" компилируется и приходит к выводу, что «строки не являются гражданами первого класса, а строковые литералы».

Тем не менее, обобщенные функции (функции с параметрами свободного типа) действительно не являются первоклассными гражданами, но это уже другая история.

Другие вопросы по теме

Условное выражение создает разные типы между MSVC и GCC/Clang
Может ли ссылка на функцию Фортрана мешать назначению?
Выбросить основные, т.е. не перехваченные результаты исключения в SIGSEGV
Почему типом результата условного выражения является константная ссылка?
Указано ли в стандарте C++ «Побочные эффекты функции упорядочиваются перед ее вычислением»?
Clang не удается создать экземпляр `operator!=()` из `operator==()` с типом возвращаемого значения `auto` для объекта класса, переданного через параметр шаблона, не относящийся к типу
Почему Rust не меняет порядок полей в перечислении для размещения в памяти?
Почему C++23 if consteval не допускает разные типы возврата, как если бы constexpr подходил?
В C++ все подвыражения аргументов вызова функции последовательно упорядочены?
Каковы точные условия, при которых оценивается type_name в sizeof(type_name)? GCC оценивает f() в sizeof(int [(f(), 100)])