Это застало меня врасплох. После изучения языка Kotlin, чтобы узнать, как наилучшим образом использовать этот интересный новый язык для jOOQ , я наткнулся на эту головоломку. Как вы думаете, следующая программа будет печатать?
|
1
2
3
4
5
6
7
8
9
|
fun main(args: Array) { (1..5).forEach { if (it == 3) return print(it) } print("done")} |
Ну … Вы могли догадаться неправильно. Выше будет напечатано:
|
1
|
1 2 |
Он НЕ напечатает то, что может ожидать большинство людей:
|
1
|
1245done |
Примечание для тех из вас, кто не удивлен:
1245done характерно для тех, кто привык работать с Java 8, где следующий код действительно 1245done :
|
01
02
03
04
05
06
07
08
09
10
|
public static void main(String[] args) { IntStream.rangeClosed(1, 5).forEach(it -> { if (it == 3) return; System.out.print(it); }); System.out.print("done");} |
Синтаксическая причина объясняется в этом разделе руководства Kotlin: https://kotlinlang.org/docs/reference/returns.html
В лямбда-выражениях / замыканиях оператор return будет (не обязательно) возвращаться из лямбда / замыкания, но из непосредственной области охвата лямбда / замыкания. Обоснование было любезно предоставлено мне Дмитрием Джемеровым из JetBrains в двух твиттах:
Как ни странно, язык Kotlin удалил основанную на языке поддержку таких конструкций Java, как try-with-resources или synchronized оператор. Это очень разумно, потому что эти языковые конструкции не обязательно принадлежат языку ( как мы ранее заявляли в другом посте в блоге ), но вместо этого могут быть перемещены в библиотеки. Например:
|
1
2
3
4
5
|
// try-with-resources is emulated using an// extension function "use"OutputStreamWriter(r.getOutputStream()).use { it.write('a')} |
- ( критика здесь )
Или же:
|
1
2
|
// Synchronized is a function!val x = synchronized (lock, { computation() }) |
В конце концов, даже в Java функция языка работает только потому, что язык зависит от типов библиотеки, таких как Iterable ( foreach ), AutoCloseable ( try-with-resources ) или функции JVM (отслеживайте каждую ссылку для synchronized ).
Итак, в чем же дело с возвращением?
В соответствии с приведенным выше обоснованием, когда разработчики языка хотят избежать языковых конструкций для вещей, которые могут быть реализованы с помощью библиотек, но все же хотят, чтобы вы чувствовали, что это языковые конструкции, тогда единственное разумное значение return внутри такой «конструкции» -ish »лямбда / замыкание — это возврат из внешней области видимости. Итак, когда вы пишете что-то вроде:
|
01
02
03
04
05
06
07
08
09
10
11
|
fun main(args : Array) { val lock = Object() val x = synchronized(lock, { if (1 == 1) return "1" }) print(x)} |
Реальное намерение состоит в том, чтобы это было эквивалентно следующему коду Java:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
public static void main(String[] args) { Object lock = new Object(); String x; synchronized (lock) { if (1 == 1) return; x = "1"; } System.out.println(x);} |
В случае Java, очевидно, оператор return выходит из метода main() , потому что нет другого разумного стекового фрейма для возврата. В отличие от Kotlin, где можно утверждать, что лямбда / замыкание создаст свой собственный кадр стека.
Но это действительно не так. Причиной этого является inline модификатор synchronized функции:
|
1
2
3
4
5
6
7
8
9
|
public inline fun <R> synchronized(lock: Any, block: () -> R): R { monitorEnter(lock) try { return block() } finally { monitorExit(lock) }} |
- Смотрите также: https://kotlinlang.org/docs/reference/inline-functions.html
Это означает, что закрытие block передаваемое в качестве аргумента, на самом деле является не просто лямбда-выражением, а просто синтаксическим сахаром, встроенным в область вызова.
Weird. Хитрость. Умная. Но немного неожиданно.
Это хорошая идея? Или дизайнеры языка будут сожалеть об этом позже? Являются ли все лямбды / замыкания потенциально «языковой конструкцией», где такой оператор возврата должен покинуть внешнюю область? Или есть явные случаи, когда это inline поведение имеет смысл?
Посмотрим. В любом случае, для языка очень интересно выбрать этот путь.
| Ссылка: | Очень своеобразная, но, возможно, хитрая особенность языка Kotlin от нашего партнера по JCG Лукаса Эдера в блоге JAVA, SQL и JOOQ . |