Это застало меня врасплох. После изучения языка 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 . |