Статьи

Сравнение замыканий в Java, Groovy и Scala

По возвращении Пола из JavaOne в этом году мы поговорили о выступлении Нила Гэфтера в кулинарной книге Closures. Из того, что я понял, это был взгляд на предложение замыканий BGGA , и он содержал пример, который давил на некоторые из более сложных проблем замыкания для Java. Я подумал, что было бы интересно взглянуть на пример Java из выступления и скрыть его в Scala и Groovy .

Почему эти языки? Потому что это три языка JVM, которые меня больше всего интересуют. Полагаю, я мог бы также сравнить поддержку замыканий в Jython, JRuby или … ну, есть из чего выбирать, но этого блога будет много достаточно долго только с тремя.

Давайте начнем с приведенного примера Java, помня, что это предложенный синтаксис, который может или не может быть сделан в Java 7 или более поздней версии. Как я понял в примере, это было так: представьте, что вы хотите добавить возможность синхронизировать блок кода, и вы хотите сделать это так, чтобы это выглядело почти как новое ключевое слово, добавленное к языку; и вы хотели передать параметр, чтобы указать, на что вы рассчитывали; и блок, который вы синхронизируете, возвращает результат или может вызвать исключение. Итак, довольно сложный случай.

BGGA Предложенный синтаксис

Вот как выглядит текущее предложение Java:

// Here's a method that uses the time call:

int f() throws MyException {
time("opName", {=>
// some statements that can throw MyException
});
time("opName", {=>
return ...compute result...;
});
}

Итак, мы рассчитываем пару операций, и мы делаем это внутри метода f , который возвращает целое число. Реализация будет ….

interface Block<R, throws X> {
R execute() throws X;
}

public <R, throws X> R time(
String opName, Block<R, X> block) throws X {
long startTime = System.nanoTime();
boolean success = true;
try {
return block.execute();
} catch (final Throwable ex) {
success = false;
throw ex;
} finally {
recordTiming(
"opName", System.nanoTime() - startTime, success);
}
}

Как вы можете видеть, время берет произвольную текстовую метку и блок кода, запускает блок и сообщает вам, сколько времени потребовалось блоку для выполнения и успешно ли он выполнен или нет.

Это пример, который был дан на JavaOne. Теперь за то же самое в Groovy …

Тот же пример в Groovy

Чтобы сделать исполняемый код для Groovy (и для Scala), мне пришлось кое-что решить. Поэтому я синхронизирую блок кода, который случайным образом генерирует исключение или возвращает что-то. И затем время блока кода, который просто возвращает число. В Groovy это будет:

def time(opname, block)
{
long start_time = System.nanoTime()
boolean success = true
try {
return block()
} catch (Throwable ex) {
success = false;
throw ex
} finally {
diff = System.nanoTime() - start_time
println "$opname $diff $success"
}
}

int f() throws Exception {
time("a") {
Random r = new Random()
if (r.nextInt(100) > 50)
throw new IOException("Boom")
else
return 42
}

time("b") {
return 7
}
}

println f()

Пример выполнения кода, показывающий один запуск, где не было выброшено исключение, и операции «a» и «b», где оба успешно и заняли определенное количество времени ….

$ groovy time.groovy 
a 37116000 true
b 69000 true
7

… и пример, где было выброшено исключение при выполнении операции синхронизации «a»:

$ groovy time.groovy 
a 39998000 false
Caught: java.io.IOException: Boom
at time$_f_closure1.doCall(time.groovy:21)
at time$_f_closure1.doCall(time.groovy)
at time.time(time.groovy:6)
at time.f(time.groovy:18)
at time.run(time.groovy:31)
at time.main(time.groovy)

Обратите внимание, что пример Java типизирован тем, что он использует универсальный тип, R , для возвращаемого значения, которое дает вам некоторые проверки во время компиляции. То есть, когда вы запускаете time () и используете результат, компилятор принудительно устанавливает, что ваше объявление результата имеет тот же тип, что и тип возврата блока, который вы синхронизируете.

Хотя Groovy поддерживает обобщенные типы , я не использовал их здесь, и в результате пример Groovy не имеет такой безопасности типов. Я думаю, что так обычно пишут Groovy-код.

Тот же пример в Scala

Теперь посмотрим на тот же код в Scala:

def time[R](opname: String)(block: => R) = {
val start_time = System.nanoTime()
var success = true
try {
block
} catch {
case ex: Throwable => {
success = false;
throw ex
}
} finally {
val diff = System.nanoTime() - start_time
println(opname + " " + diff + " "+success)
}
}

def f():Integer = {

val answer = time("a") {
val r = new Random()
if (r.nextInt(100) > 50)
throw new java.io.IOException("Boom")
else
"42"
}

println ("the answer, "+answer+" is of type "+
answer.getClass())

val seven:Integer = time("b") {
7
}

println ("seven is of type "+seven.getClass())

return seven
}

f()

Я до сих пор не использую Scala изо дня в день, так что это может быть немного неловко: спасибо Лондонской группе пользователей Scala за помощь в очистке моего синтаксиса, но все ошибки мои.

Этот код имеет те же свойства, что и код Java (безопасность типов через универсальный тип R ), но кажется немного короче и аккуратнее. Кроме того, что мне нравится в коде Scala (и коде Groovy), так это то, что языки возвращают значение последнего оператора в блоке и что синтаксис допускает формат чистого времени («вещь») {…} ,

Одно наблюдение: я использовал класс Integer , который устарел, чтобы иметь возможность распечатать класс возвращаемых типов в функции f () . Без : целое числообъявление я получаю странные ошибки компиляции. Как я уже сказал, мое понимание Scala и вывода типов еще не достигнуто.

Выход из запуска кода:

a 913000 true
the answer, 42 is of type class java.lang.String
b 13000 true
seven is of type class java.lang.Integer

… и пример, где метод вызвал исключение:

a 936000 false
java.io.IOException: Boom
 at Main$$anonfun$1.apply((virtual file):26)
 at Main$$anonfun$1.apply((virtual file):23)
 at Main$.time$1((virtual file):8)
 at Main$.f$1((virtual file):23)
 at Main$.main((virtual file):44)
 at Main.main((virtual file))
// rest of stack trace removed
 

Выводы

Здесь нет никаких выводов. Это всего лишь упражнение по сравнению кода замыканий на трех разных языках. Возможно, я пропустил некоторые нюансы примера Java, но эй … это отправная точка.

Оригинальное сообщение в блоге: http://blog.spiralarm.com/richard/2008/06/comparing-closures-in-java-groovy-and.html