Groovy Closures: искусство компактности

В языке Groovy помимо стандартных полей и методов существуют т.н. Closures. Средство это очень нетипичное для классического программирования, но чрезвычайно эффективное. Рассмотрим простой пример: нужно превратить «плохой» html-код вида

Строка 1<br>
Строка 2<br>
Строка 3<br>
Строка 4<br>


в «хороший»

<p>Строка 1</p>
<p>Строка 2</p>
<p>Строка 3</p>
<p>Строка 4</p>

Вот как это делается (результат окажется в переменной goodHtml):

goodHtml = ''
(badHtml =~ /.*?<br>/).each{
 goodHtml += "<p>${it.substring(0, it.lastIndexOf('<')).trim()}</p>"
}

В данном примере closure — это то, что заключено в фигурные скобки. В круглых скобках происходит разбивка строки на фрагменты, соответствующие регулярному выражению .*?<br>. Для каждого такого фрагмента и выполняется алгоритм в фигурных скобках. Об этом свидетельствует оператор each.

Регулярное выражение заключено в слэши, которые применяются вместо кавычек, чтобы избежать экранирования, т.е. вместо /.*?<br>/ можно было бы записать ".*?<br>". Но о регекспах в Groovy можно написать целую поэму, так что оставим это на следующий раз. А сейчас вернемся к closures.

Closure — это нечто среднее между методами и операторами циклов. Строго говоря, это, конечно же, методы (а может даже анонимные классы, если копнуть глубже). Однако с точки зрения синтаксиса пишутся так же легко, как циклы. Closures можно писать «по месту» (как было сделано в вышеприведенном примере) или объявлять и именовать, как методы. Например, некоторое время назад я писал о копировании потока в поток средствами Java. Вот как то же самое звучит в Groovy с помощью closures:

import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;

def stream2stream = {is, os ->
 src  = Channels.newChannel(is);
 dest = Channels.newChannel(os);
 buffer = ByteBuffer.allocateDirect(16 * 1024);
 while (src.read(buffer) != -1) {
  buffer.flip();
  dest.write(buffer);
  buffer.compact();
 }
 buffer.flip();
 while (buffer.hasRemaining())dest.write(buffer);
}


Вот как с помощью этого скопировать один файл в другой:

stream2stream(new FileInputStream(file1), new FileOutputStream(file2))

По сути очень похоже на стандартное объявление метода и его вызов, только «букаф меньше». Во-первых, пропущен тип возвращаемого значения. Во-вторых, аргументы, передаваемые методу, перечислены до знака -> (входной поток is и выходной os), что выглядит изящнее. Если для closure аргументы не перечислены, но внутри его идет какой-то перебор в цикле, то к перечисляемым элементам можно обращаться с помощью универсальной переменной it (см. первый пример).

Может этот экскурс в мир closures и слишком краток, но по крайней мере примеры кода позволяют оценить эффективность этого средства Groovy: Java-листинг первого фрагмента (замена строк с помощью регулярных выражений) был бы раз в 5 длиннее.
  • +8
  • 21 июня 2010, 21:47
  • yababay

Комментарии (5)

RSS свернуть / развернуть
+
0
Это что-то вроде html+php когда код вперемешку со строками но реализовано красиво
avatar

Gangsta

  • 22 июня 2010, 10:15
+
0
Верное замечание. Только пропорция другая. В php, как правило, преобладает html, в который встроены фрагменты исполняемого на лету кода. В Groovy главное — код, но встраивать строки на всевозможных декларативных синтаксисах (html, xml, sql, regexp) гораздо удобнее, чем в Java.
avatar

yababay

  • 22 июня 2010, 10:48
+
0
ну почему… Если пользоваться моделью MVC — то всё раздельно, да и удобней
avatar

Mihael

  • 22 июня 2010, 18:12
+
0
В более-менее крупных программах — согласен, нужны модели и т.п. Но в таких разовых задачах лучше иметь возможность включать сложные строки прямо в текст программы.
avatar

yababay

  • 22 июня 2010, 19:15
+
0
Красиво !
avatar

Markony

  • 22 июня 2010, 16:56

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.