Блоки — различия между версиями

Материал из Deeptown Manual
Перейти к: навигация, поиск
(atacand impotenz ace hemmer http://www.flickr.com/people/74669795@N05/ atacand mecanismo de accion xeloda more drug uses view topic http://dailybooth.com/siotremse/22556295 buy cheap online xeloda h)
 
(не показана 20 051 промежуточная версия 4 участников)
Строка 1: Строка 1:
atacand impotenz ace hemmer http://www.flickr.com/people/74669795@N05/ atacand mecanismo de accion
+
__TOC__
xeloda more drug uses view topic http://dailybooth.com/siotremse/22556295 buy cheap online xeloda
+
 
http://www.faniq.com/user/alocolin/bio xeloda smpc capecitabine xeloda price nausea vomiting
+
Основной идеей при написании как процедурных, так и объектно-ориентированных программ является локализация (заключение) функционала в некоторой области: в функции, либо в методе. Таким образом получается, что программа состоит из множества функционально независимых частей, каждая из которых решает конкретную, достаточно узкую задачу. Такой подход позволяет скрыть детали реализации алгоритмов внутри этих частей, а "наружу" предоставить только интерфейс — совокупность способов взаимодействия данного функционального элемента с другими. В случае функции, под интерфейсом понимается совокупность ее параметров и возвращаемого значения; в случае классов — наборы методов и свойств.
http://www.formspring.me/trucpaso/q/284251903510976890 xeloda report xeloda 150 efectos secundarios
+
 
metoprolol genrx atacand plus http://www.flickr.com/people/74669877@N05/ absetzen von atacand
+
Случается, что в работе некоторого алгоритма возникают четко определенные функциональные подсистемы, которые, однако, слишком зависимы от самого алгоритма, либо слишком просты для того, чтобы выделять их в отдельную функцию. В случае с C++ это все же приходится делать, даже если эта "минифункция" применяется всего в одном методе, но достаточное количество раз, чтобы программист не захотел копировать эти строки.
http://www.formspring.me/trucpaso/q/284252094498617304 xeloda in and rash xeloda chemotherapy
+
 
xeloda auswirkungen http://www.last.fm/user/xeloda-r6zb xeloda online purchase
+
В других случаях, может возникнуть желание вынести некоторый функционал за пределы текущей функции, или даже предоставить программисту-пользователю возможность самому определить его.
definition xeloda http://dailybooth.com/siotremse/22556339 definition xeloda
+
 
http://www.formspring.me/trucpaso/q/284252311914548066 xeloda drug interactions effectiveness of xeloda
+
И в том и в другом случае на помощь приходят ''замыкания'', ''анонимные функции'' или ''лямбда функции'', как их еще называют. Сущность такой функции в том, что она с одной стороны как бы является переменной, а с другой стороны — функцией, которую можно вызывать. В языке К++, такие функции носят название блоков. Сущность переменной, блоки проявляют в том что их можно создавать как локальные переменные, присваивать друг другу и даже передавать другим функциям в качестве параметра. Приняв такой параметр, функция может обратиться к нему, как к любой другой функции: вызывать с некоторыми параметрами и использовать его возвращаемое значение:
http://www.formspring.me/bunkblocno/q/284252376116771170 xeloda mammella di generic buy xeloda
+
<source lang="kpp">
cimetidine colorectal cancer xeloda http://www.last.fm/user/xeloda-930i buying xeloda online
+
function Caller(x, y, block b) {
xeloda for gastric cancer patients http://www.flickr.com/people/74669985@N05/ xeloda clinical trials
+
    return b(x, y);
http://www.formspring.me/trucpaso/q/284252594459650709 xeloda neuropathy colon xeloda liver cancer 5 fluorouracil
+
}
http://www.formspring.me/bunkblocno/q/284252601803869362 comprar de xeloda xeloda skin cancer
+
</source>
xeloda gemzar metastatic pancreatic cancer http://www.formspring.me/trucpaso/q/284252594459650709 order xeloda no prescription
+
 
xeloda krefeld http://www.formspring.me/bunkblocno/q/284252863280987184 xeloda navelbine liver metastases
+
Приведем простой пример объявления блоков и их передачи в вышеописанную функцию:
http://www.formspring.me/trucpaso/q/284253044361674033 breast buy xeloda breast buy xeloda
+
<source lang="kpp">
http://www.formspring.me/bunkblocno/q/284252863280987184 xeloda hair loss chemo xeloda ovarian cancer 2008
+
var sum = { |x, y| return x + y; };
cheapest xeloda http://www.formspring.me/trucpaso/q/284253295399153269 can i buy xeloda over the counter
+
var mul = { |x, y| return x * y; };
avastin xeloda breast cancer http://www.formspring.me/bunkblocno/q/284253320451719217 sutent xeloda study
+
var s = Caller(2, 3, sum); // s = 2 + 3 = 5
http://www.formspring.me/trucpaso/q/284253477901697243 xeloda scheda tecnica xeloda oxaliplatino 5 fluorouracil
+
var m = Caller(2, 5, mul); // m = 2 * 5 = 10
http://www.last.fm/user/xeloda-a6l chemotherapie xeloda probleme von xeloda
+
</source>
emea xeloda adjuvant http://www.formspring.me/bunkblocno/q/284253550760964030 her2 negative xeloda
+
 
buy online prescription xeloda http://www.faniq.com/user/crawtoudo/bio xeloda bevacizumab per
+
Первый блок принимает два параметра и возвращает их сумму; второй блок так же принимает два параметра, но результатом его выполнения будет уже произведение.
taxol and xeloda http://www.last.fm/user/xeloda-6i9h xeloda retail discount
+
 
emea xeloda colon retto http://www.formspring.me/trucpaso/q/284253688661287015 xeloda wechselwirkung
+
== Применение блоков ==
http://www.formspring.me/bunkblocno/q/284253771586871511 xeloda pantostin no prescription xeloda
+
 
http://www.flickr.com/people/74670199@N05/ xeloda pill looks like xeloda blog
+
Наиболее подходящая задача, иллюстрирующая идеологию и способы использования блоков — это сортировка элементов массива. Предположим, что программисту требуется реализовать для класса <tt>array</tt> механизм сортировки элементов. Под сортировкой, в данном случае понимается упорядочение набора элементов на основании некоторого критерия, по которому можно судить об их очередности в списке для каждой пары элементов.
xeloda generic price http://www.formspring.me/trucpaso/q/284253962486428114 cheapest xeloda generic
+
 
xeloda fatigue breast cancer http://www.formspring.me/bunkblocno/q/284254118527120019 xeloda compresse acido folinico
+
Проблема заключается в том, что на этапе написания реализации алгоритма, мы не имеем представления о том, какие же элементы будут храниться в массиве. Как мы отсортируем массив, который может состоять из чего угодно: строк, чисел, произвольных объектов и даже самих массивов? Пришлось бы либо писать обширную реализацию некоторого метода сравнения двух элементов, который бы учитывал все возможные варианты типов элементов, либо реализовывать свою функцию сортировки на каждый из типов данных элементов:
http://www.formspring.me/trucpaso/q/284254168238011079 xeloda diarrea 5 fluorouracilo xeloda wikipedia arzneimittel
+
<source lang="kpp">
xeloda after effects stomach cancer http://www.last.fm/user/xeloda-39h post xeloda medication
+
extend array {
xeloda avastin hand foot syndrome http://www.formspring.me/bunkblocno/q/284254347947152599 name brand xeloda online
+
    public function array SortAsNumbers();
xeloda et alimentation http://www.formspring.me/trucpaso/q/284254375583425466 xeloda for liver cancer 5 fluorouracil
+
    public function array SortAsStrings();
http://dailybooth.com/sirace155/22556540 price of xeloda prescription drugs online xeloda medicinale
+
    public function array SortAsArrays();
http://www.formspring.me/trucpaso/q/284254598066087115 generic xeloda sales opinion xeloda
+
    public function array SortAsCollections();
http://www.last.fm/user/xeloda-i90 buy cheapest xeloda xeloda dosing schedule
+
}
 +
</source>
 +
 
 +
И тот и другой способ жестко ограничивает функциональность нашего класса только теми типами элементов, которые были изначально предусмотрены. А если программист-пользователь захочет использовать свой тип данных, или еще проще, захочет сортировать элементы не по возрастанию, а по убыванию? Вероятно, мало кто захочет переписывать все эти функции только для того, чтобы изменить порядок сортировки.
 +
 
 +
Давайте взглянем на проблему шире. В сущности, все вышеописанные методы делают ровно одно и то же: некоторым из алгоритмов сортировки, они упорядочивают элементы в массиве. При этом, единственная различающаяся в них часть — это сама операция сравнения двух элементов. Решение напрашивается само собой — необходимо вынести эту часть из функции сортировки и предоставить пользователю самому обеспечить операцию сравнения. Таким образом, мы получим ОДИН метод сортировки, который будет работать для ЛЮБЫХ типов элементов — программист-пользователь сам укажет необходимый критерий сортировки, который уже будет использован внутри метода.
 +
 
 +
Эту задачу можно красиво решить с помощью блоков:
 +
<source lang="kpp">
 +
extend array {
 +
    public const function array sort(block cmp) {
 +
        return this if size < 2;
 +
        var array left, array right;
 +
        for (var i = 1; i < size; ++i)
 +
            cmp(this[i], this[0]) ? left.push(this[i]) : right.push(this[i]);
 +
        return left.sort(cmp) + [ this[0] ] + right.sort(cmp);
 +
    }
 +
}
 +
</source>
 +
 
 +
В качестве параметра, метод <tt>sort()</tt> принимает один аргумент — блок, который должен производить сравнение двух элементов и возвращать истину, если элементы расположены в правильном порядке и ложь, если порядок неверный, то есть необхдимо их переставить.
 +
 
 +
'''Примечание:''' Приведенный здесь код является реализацией алгоритма [http://ru.wikipedia.org/wiki/Быстрая_сортировка быстрой сортировки], однако он является не очень практичным, поскольку на каждом из этапов он оперирует с копиями соответствующих частей массива; в книге же был использован из соображений краткости кода.
 +
 
 +
Теперь, любой массив в программе, использующей текущий модуль, может быть отсортирован на основании некоторого критерия. Результатом выполнения будет другой, уже отсортированный массив:
 +
 
 +
<source lang="kpp">
 +
var array sorted, ary = [2, 7, 3, 0, 1, 5, 8, 9, 4, 6];
 +
sorted = ary.sort() { |x, y| x < y; }; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 +
sorted = ary.sort() { |x, y| x > y; }; // [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
 +
 
 +
ary = ['q', 'e', 'b', 'z', 'a', 'x', 't'];
 +
sorted = ary.sort() { |x, y| x < y; }; // ['a', 'b', 'e', 'q', 't', 'x', 'z']
 +
sorted = ary.sort() { |x, y| x > y; }; // ['z', 'x', 't', 'q', 'e', 'b', 'a']
 +
</source>
 +
 
 +
Мы объявляем две переменных-массива ''ary'' и ''sorted''. Переменная ''ary'' инициализируется массивом произвольных чисел, либо строк во втором случае. Затем выполняются вызовы метода <tt>sort()</tt>, результат выполнения которых сохраняется в переменной ''sorted''.
 +
 
 +
'''Примечание:''' Для сокращения записи, в вышеописанном коде были применены т. н. ''встроенные блоки'' (inline). К++ поддерживает специальный синтаксис для передачи встроенных блоков в функции. Подробнее об этом будет написано чуть ниже.
 +
 
 +
== Отличие от функций ==
 +
 
 +
В целом, блоки очень похожи на обычные функции, однако существует несколько отличий, которые следует иметь в виду. Первое, наиболее очевидное отличие заключается в способе описания формальных параметров, которые, в отличие от функций, записываются в самом начале тела блока, между двумя вертикальными чертами. Друг от друга параметры отделяются запятой.  
 +
 
 +
Главное отличие блоков заключается в том, что они в основном ориентированы на динамический код. Скажем, возвращаемое значение всегда является [[Переменные#Нетипированные (динамические) переменные|динамической переменной]]. В отдельных случаях допускается типизация аргументов блока, однако она происходит внутри самого блока и не влияет на фактически передаваемые параметры (см. ниже). Таким образом, с точки зрения вызывающего кода, блоки всегда представляют собой динамический код.
 +
 
 +
Блоки не могут быть экспортированы из текущего модуля и должны применяться только в нем самом. Разумеется, экспортировать функции, принимающие блоки в качестве параметров можно.
 +
 
 +
== Типизация параметров ==
 +
 
 +
В некоторых случаях, для повышения эффективности кода блока можно типировать формальные параметры, с тем чтобы впоследствии компилятор, на основании этой информации, мог бы генерировать статический код. Типирование аргументов осуществляется точно так же, как и в обычных функциях: непосредственно перед идентификатором аргумента, указывается его тип.
 +
<source lang="kpp">
 +
var myblock = { | int x, int y | /* код блока */ };
 +
</source>
 +
Теперь, в теле блока, переменные ''x'' и ''y'' будут считаться статическими.
 +
 
 +
'''Примечание 1:''' Поскольку на момент компиляции, у компилятора нет возможности проверить правильность типов передаваемых в блок параметров, ответственность за это ложится на самого программиста. Если в блок будет передан объект неприводимого типа, то это приведет к ошибке времени исполнения ([[Идеология языка#Понятие исключения|исключению]]).
 +
 
 +
'''Примечание 2:''' Указание типов в параметрах может быть удобно, поскольку компилятор, располагая информацией о типе параметров, может выполнять некоторую работу автоматически. Например, приводить переменные к нужному типу при вызове других функций. Допустим, мы хотим описать некоторый блок, который по логике работы принимает число (переменную типа <tt>int</tt>), но внутри себя вызывает другие функции, скажем <tt>puts()</tt>:
 +
 
 +
<source lang="kpp" line="1">
 +
var myblock = { |x|
 +
    /* код блока */  
 +
    puts(x as string);
 +
};
 +
</source>
 +
 
 +
В данном случае, в строке 3, при вызове <tt>puts()</tt>, нам пришлось привести тип переменной ''x'' к строке явным образом. Если бы этого не было сделано, то возникла бы ошибка времени исполнения, поскольку функция <tt>puts()</tt> ожидает строку. При небольших размерах кода это не составляет проблем, однако приводить тип переменной пришлось бы в каждом вызове. Для решения этой проблемы как раз рекомендуется применять типирование аргументов:
 +
 
 +
<source lang="kpp" line="1">
 +
var myblock = { | int x |
 +
    /* код блока */  
 +
    puts(x); //теперь, приведение происходит автоматически
 +
};
 +
</source>
 +
 
 +
== Встроенные блоки ==
 +
 
 +
Чаще всего, блоки применяются для записи небольших кусочков кода, которые, как в примерах выше, передаются в функции, для уточнения некоторых моментов. Для того чтобы облегчить работу программиста и сделать код более читаемым, был введен специальный синтаксис для передачи блоков в функции с одновременным их объявлением "на месте"; при этом, блоки записываются в сокращенной форме сразу за кодом вызова функции, после блока фактических параметров.
 +
 
 +
Покажем использование встроенных блоков на примере нахождения суммы чисел от 1 до 10, а так же получения списка четных чисел в этом же интервале.
 +
<source lang="kpp">
 +
var s = 0;
 +
var numbers = [];
 +
(1..10).each() { |x| s += x; numbers.push(x); };
 +
var evens = numbers.collect() { |x| x % 2 == 0 ? x : null; }.compact();
 +
</source>
 +
 
 +
Несмотря на свою простоту и надуманность, данный пример показывает две особенности встроенных блоков, по сравнению с обычными блоками, объявляемыми как переменные. Первая особенность заключается в том, что встроенные блоки имеют доступ к локальным переменным в контексте вызова (то есть, замыкаются на него — отсюда и название "замыкания"). Например, данный блок использует внешние по отношению к нему переменные: ''s'' для накопления суммы, а ''numbers'' для добавления текущего числа в массив.
 +
 
 +
Вторая особенность реализуется в коде второго встроенного блока. Она заключается в том, что встроенные блоки могут возвращать значения без явного указания оператора <tt>'''return'''</tt>. Однако, это вожможно только для простых выражений, без применения операторов ветвления. Для получения массива целых чисел, используются два метода, объявленные в стандартной библиотеке языка K++: методы <tt>collect()</tt> и <tt>compact()</tt>.
 +
 
 +
Метод <tt>collect()</tt> принимает в качестве аргумента блок. Далее, он проходит вдоль всего массива, вызывая блок с параметром текущего элемента. Результат выполнения блока помещается в другой массив, который возвращается вызывающему коду. Таким образом, этот метод может применяться для обработки элементов некоторого массива с получением другого массива из обработанных элементов. Метод <tt>compact()</tt> используется для "уплотнения" массива, путем удаления из него пустых элементов, равных <tt>'''null'''</tt>.
 +
 
 +
В вышеприведенном коде, мы используем оба этих метода для того, чтобы сначала выбрать из исходного массива все четные элементы (вместо нечетных вставляется <tt>'''null'''</tt>), а затем уплотнить полученный массив.
 +
 
 +
Реализованы эти методы могут быть примерно так:
 +
<source lang="kpp">
 +
extend array {
 +
    public const function array collect(block b) {
 +
        var array result;
 +
        this.each() { |x| result.push(b(x)); };
 +
        return result;
 +
    }
 +
 
 +
    public const function array compact() {
 +
        var array result;
 +
        this.each() { |x| result.push(b(x)) if x; };
 +
        return result;
 +
    }
 +
}
 +
</source>
 +
 
 +
'''Примечание 1:''' при работе со встроенными блоками внутри обрабатывающих функций, следует быть очень осторожным. Поскольку, встроенные блоки оперируют с перменными контекста своего объявления, то они должны существовать не дольше чем существует сам блок, в котором они были объявлены и в котором был произведен вызов функции-обработчика.  
 +
 
 +
Сама функция, должна работать с ними "как есть"; ни в коем случае нельзя их сохранять во внешних переменных (где они могут просуществовать дольше положенного времени). Например, такой код '''является недопустимым''':
 +
<source lang="kpp">
 +
var block_array = [];
 +
function AddBlock(block b) { block_array.push(b); }
 +
function f() {
 +
    const c = 5;
 +
    AddBlock() { |x| x * c; }; // так делать нельзя!
 +
}
 +
</source>
 +
В коде блока мы видим использование внешнего объекта — константы ''c''. Проблема заключается в том, что встроенные блоки не порождают собственного контекста и заимствуют набор локальных переменных из контекста вызова. Соответственно, если блок был где то сохранен, то может случиться, что контект его локальных переменных был уже удален.  
 +
 
 +
В то же время, такой код вполне правомерен:
 +
<source lang="kpp">
 +
function f() {
 +
    var b = { |x| return x + 1; }; // а так можно
 +
    AddBlock(b);
 +
 
 +
    //так тоже можно, но не рекомендуется:
 +
    AddBlock({|x| return x * 5;});
 +
}
 +
</source>
 +
 
 +
'''Примечание 2:''' хотя использование связки <tt>collect(){...}.compact()</tt> не возбраняется, следует иметь в виду, что происходит двойное копирование массивов, что в конечном счете сказывается на производительности. Для задачи выбора (а не обработки) элементов из массива, более приемлемым вариантом является использование метода <tt>select()</tt>, который отбирает из исходного массива все элементы, удовлетворяющие условию, записанному в блоке:
 +
 
 +
<source lang="kpp">
 +
var ary = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
 +
var selected = ary.select() { |x| x > 6; }; // [7, 8, 9, 10]
 +
</source>
 +
 
 +
При использовании метода <tt>select()</tt> промежуточного копирования не происходит, так что операция будет выполняться более эффективно.
 +
 
 +
----
 +
 
 +
 
 +
Механизм встроенных блоков — это очень мощный инструмент, который при грамотном использовании позволяет добиться потрясающих результатов при написании программ. Программы получаются очень лаконичными, легко читаемыми и иногда даже более эффективными, чем при традиционном подходе.  
 +
 
 +
К примеру, вот пример кода, который обращается на FTP сервер и выбирает из некоторой директории все файлы, имеющие расширение '.conf':
 +
<source lang="kpp">
 +
var configs = Directory.open('ftp://example.com/media/etc')
 +
    .browse().select() { |file| file.name =~ `\.conf$`; };
 +
</source>
 +
 
 +
На традиционных языках, такая операция могла бы занять с десяток строк кода, тогда как здесь мы обходимся всего одной (строка разбита две части для того, чтобы уместить в ширину страницы).
 +
 
 +
Учитывая принципы полудинамической типизации, некоторые конструкции могут быть более лаконичными даже по сравнению с подобными языками. Например, следующий код на C# 2.0 позволяет увеличить все элементы массива на 2:
 +
<source lang="csharp">
 +
int[] ary = { 1, 2, 3 };
 +
int x = 2;
 +
Array.ConvertAll<int, int>(ary, delegate(int elem) { return elem * x; }); // { 2, 4, 6 }
 +
</source>
 +
 
 +
На K++ эта же конструкция будет выглядеть так:
 +
<source lang="kpp">
 +
var ary = [1, 2, 3];
 +
var x = 2;
 +
ary.convert() { |e| e * x; }; // [2, 4, 6]
 +
</source>
 +
 
 +
Конечно, можно привести массу доводов в защиту того или иного языка, укзазать преимущества однго и недостатки другого, чем с упоением и занимаются многочисленные разжигатели религиозных войн в среде IT. Мы не ставим себе целью доказать преимущества K++, скорее показать преимущества всех языков, использующих альтернативный подход к программированию в целом. К чести того же C# можно сказать, что в третей версии были введены замыкания, по краткости не уступающие K++. В целом, у каждого языка есть свои преимущества и недостатки и в общем случае сравнивать их нельзя. Можно, разве что, судить о степени удобства языка, в сочетании с его производительностью и о том, насколько эффективно он позволяет решать задачи, на которые был рассчитан и исходя из которых проектировался.

Текущая версия на 13:40, 13 июля 2013

Содержание


Основной идеей при написании как процедурных, так и объектно-ориентированных программ является локализация (заключение) функционала в некоторой области: в функции, либо в методе. Таким образом получается, что программа состоит из множества функционально независимых частей, каждая из которых решает конкретную, достаточно узкую задачу. Такой подход позволяет скрыть детали реализации алгоритмов внутри этих частей, а "наружу" предоставить только интерфейс — совокупность способов взаимодействия данного функционального элемента с другими. В случае функции, под интерфейсом понимается совокупность ее параметров и возвращаемого значения; в случае классов — наборы методов и свойств.

Случается, что в работе некоторого алгоритма возникают четко определенные функциональные подсистемы, которые, однако, слишком зависимы от самого алгоритма, либо слишком просты для того, чтобы выделять их в отдельную функцию. В случае с C++ это все же приходится делать, даже если эта "минифункция" применяется всего в одном методе, но достаточное количество раз, чтобы программист не захотел копировать эти строки.

В других случаях, может возникнуть желание вынести некоторый функционал за пределы текущей функции, или даже предоставить программисту-пользователю возможность самому определить его.

И в том и в другом случае на помощь приходят замыкания, анонимные функции или лямбда функции, как их еще называют. Сущность такой функции в том, что она с одной стороны как бы является переменной, а с другой стороны — функцией, которую можно вызывать. В языке К++, такие функции носят название блоков. Сущность переменной, блоки проявляют в том что их можно создавать как локальные переменные, присваивать друг другу и даже передавать другим функциям в качестве параметра. Приняв такой параметр, функция может обратиться к нему, как к любой другой функции: вызывать с некоторыми параметрами и использовать его возвращаемое значение: <source lang="kpp"> function Caller(x, y, block b) {

   return b(x, y);

} </source>

Приведем простой пример объявления блоков и их передачи в вышеописанную функцию: <source lang="kpp"> var sum = { |x, y| return x + y; }; var mul = { |x, y| return x * y; }; var s = Caller(2, 3, sum); // s = 2 + 3 = 5 var m = Caller(2, 5, mul); // m = 2 * 5 = 10 </source>

Первый блок принимает два параметра и возвращает их сумму; второй блок так же принимает два параметра, но результатом его выполнения будет уже произведение.

[править] Применение блоков

Наиболее подходящая задача, иллюстрирующая идеологию и способы использования блоков — это сортировка элементов массива. Предположим, что программисту требуется реализовать для класса array механизм сортировки элементов. Под сортировкой, в данном случае понимается упорядочение набора элементов на основании некоторого критерия, по которому можно судить об их очередности в списке для каждой пары элементов.

Проблема заключается в том, что на этапе написания реализации алгоритма, мы не имеем представления о том, какие же элементы будут храниться в массиве. Как мы отсортируем массив, который может состоять из чего угодно: строк, чисел, произвольных объектов и даже самих массивов? Пришлось бы либо писать обширную реализацию некоторого метода сравнения двух элементов, который бы учитывал все возможные варианты типов элементов, либо реализовывать свою функцию сортировки на каждый из типов данных элементов: <source lang="kpp"> extend array {

   public function array SortAsNumbers();
   public function array SortAsStrings();
   public function array SortAsArrays();
   public function array SortAsCollections();

} </source>

И тот и другой способ жестко ограничивает функциональность нашего класса только теми типами элементов, которые были изначально предусмотрены. А если программист-пользователь захочет использовать свой тип данных, или еще проще, захочет сортировать элементы не по возрастанию, а по убыванию? Вероятно, мало кто захочет переписывать все эти функции только для того, чтобы изменить порядок сортировки.

Давайте взглянем на проблему шире. В сущности, все вышеописанные методы делают ровно одно и то же: некоторым из алгоритмов сортировки, они упорядочивают элементы в массиве. При этом, единственная различающаяся в них часть — это сама операция сравнения двух элементов. Решение напрашивается само собой — необходимо вынести эту часть из функции сортировки и предоставить пользователю самому обеспечить операцию сравнения. Таким образом, мы получим ОДИН метод сортировки, который будет работать для ЛЮБЫХ типов элементов — программист-пользователь сам укажет необходимый критерий сортировки, который уже будет использован внутри метода.

Эту задачу можно красиво решить с помощью блоков: <source lang="kpp"> extend array {

   public const function array sort(block cmp) {
       return this if size < 2;
       var array left, array right;
       for (var i = 1; i < size; ++i)
           cmp(this[i], this[0]) ? left.push(this[i]) : right.push(this[i]);
       return left.sort(cmp) + [ this[0] ] + right.sort(cmp);
   }

} </source>

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

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

Теперь, любой массив в программе, использующей текущий модуль, может быть отсортирован на основании некоторого критерия. Результатом выполнения будет другой, уже отсортированный массив:

<source lang="kpp"> var array sorted, ary = [2, 7, 3, 0, 1, 5, 8, 9, 4, 6]; sorted = ary.sort() { |x, y| x < y; }; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] sorted = ary.sort() { |x, y| x > y; }; // [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

ary = ['q', 'e', 'b', 'z', 'a', 'x', 't']; sorted = ary.sort() { |x, y| x < y; }; // ['a', 'b', 'e', 'q', 't', 'x', 'z'] sorted = ary.sort() { |x, y| x > y; }; // ['z', 'x', 't', 'q', 'e', 'b', 'a'] </source>

Мы объявляем две переменных-массива ary и sorted. Переменная ary инициализируется массивом произвольных чисел, либо строк во втором случае. Затем выполняются вызовы метода sort(), результат выполнения которых сохраняется в переменной sorted.

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

[править] Отличие от функций

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

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

Блоки не могут быть экспортированы из текущего модуля и должны применяться только в нем самом. Разумеется, экспортировать функции, принимающие блоки в качестве параметров можно.

[править] Типизация параметров

В некоторых случаях, для повышения эффективности кода блока можно типировать формальные параметры, с тем чтобы впоследствии компилятор, на основании этой информации, мог бы генерировать статический код. Типирование аргументов осуществляется точно так же, как и в обычных функциях: непосредственно перед идентификатором аргумента, указывается его тип. <source lang="kpp"> var myblock = { | int x, int y | /* код блока */ }; </source> Теперь, в теле блока, переменные x и y будут считаться статическими.

Примечание 1: Поскольку на момент компиляции, у компилятора нет возможности проверить правильность типов передаваемых в блок параметров, ответственность за это ложится на самого программиста. Если в блок будет передан объект неприводимого типа, то это приведет к ошибке времени исполнения (исключению).

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

<source lang="kpp" line="1"> var myblock = { |x|

   /* код блока */ 
   puts(x as string); 

}; </source>

В данном случае, в строке 3, при вызове puts(), нам пришлось привести тип переменной x к строке явным образом. Если бы этого не было сделано, то возникла бы ошибка времени исполнения, поскольку функция puts() ожидает строку. При небольших размерах кода это не составляет проблем, однако приводить тип переменной пришлось бы в каждом вызове. Для решения этой проблемы как раз рекомендуется применять типирование аргументов:

<source lang="kpp" line="1"> var myblock = { | int x |

   /* код блока */ 
   puts(x); //теперь, приведение происходит автоматически

}; </source>

[править] Встроенные блоки

Чаще всего, блоки применяются для записи небольших кусочков кода, которые, как в примерах выше, передаются в функции, для уточнения некоторых моментов. Для того чтобы облегчить работу программиста и сделать код более читаемым, был введен специальный синтаксис для передачи блоков в функции с одновременным их объявлением "на месте"; при этом, блоки записываются в сокращенной форме сразу за кодом вызова функции, после блока фактических параметров.

Покажем использование встроенных блоков на примере нахождения суммы чисел от 1 до 10, а так же получения списка четных чисел в этом же интервале. <source lang="kpp"> var s = 0; var numbers = []; (1..10).each() { |x| s += x; numbers.push(x); }; var evens = numbers.collect() { |x| x % 2 == 0 ? x : null; }.compact(); </source>

Несмотря на свою простоту и надуманность, данный пример показывает две особенности встроенных блоков, по сравнению с обычными блоками, объявляемыми как переменные. Первая особенность заключается в том, что встроенные блоки имеют доступ к локальным переменным в контексте вызова (то есть, замыкаются на него — отсюда и название "замыкания"). Например, данный блок использует внешние по отношению к нему переменные: s для накопления суммы, а numbers для добавления текущего числа в массив.

Вторая особенность реализуется в коде второго встроенного блока. Она заключается в том, что встроенные блоки могут возвращать значения без явного указания оператора return. Однако, это вожможно только для простых выражений, без применения операторов ветвления. Для получения массива целых чисел, используются два метода, объявленные в стандартной библиотеке языка K++: методы collect() и compact().

Метод collect() принимает в качестве аргумента блок. Далее, он проходит вдоль всего массива, вызывая блок с параметром текущего элемента. Результат выполнения блока помещается в другой массив, который возвращается вызывающему коду. Таким образом, этот метод может применяться для обработки элементов некоторого массива с получением другого массива из обработанных элементов. Метод compact() используется для "уплотнения" массива, путем удаления из него пустых элементов, равных null.

В вышеприведенном коде, мы используем оба этих метода для того, чтобы сначала выбрать из исходного массива все четные элементы (вместо нечетных вставляется null), а затем уплотнить полученный массив.

Реализованы эти методы могут быть примерно так: <source lang="kpp"> extend array {

   public const function array collect(block b) {
       var array result;
       this.each() { |x| result.push(b(x)); };
       return result;
   }
   public const function array compact() {
       var array result;
       this.each() { |x| result.push(b(x)) if x; };
       return result;
   }

} </source>

Примечание 1: при работе со встроенными блоками внутри обрабатывающих функций, следует быть очень осторожным. Поскольку, встроенные блоки оперируют с перменными контекста своего объявления, то они должны существовать не дольше чем существует сам блок, в котором они были объявлены и в котором был произведен вызов функции-обработчика.

Сама функция, должна работать с ними "как есть"; ни в коем случае нельзя их сохранять во внешних переменных (где они могут просуществовать дольше положенного времени). Например, такой код является недопустимым: <source lang="kpp"> var block_array = []; function AddBlock(block b) { block_array.push(b); } function f() {

   const c = 5;
   AddBlock() { |x| x * c; }; // так делать нельзя!

} </source> В коде блока мы видим использование внешнего объекта — константы c. Проблема заключается в том, что встроенные блоки не порождают собственного контекста и заимствуют набор локальных переменных из контекста вызова. Соответственно, если блок был где то сохранен, то может случиться, что контект его локальных переменных был уже удален.

В то же время, такой код вполне правомерен: <source lang="kpp"> function f() {

   var b = { |x| return x + 1; }; // а так можно
   AddBlock(b); 
   //так тоже можно, но не рекомендуется:
   AddBlock({|x| return x * 5;}); 

} </source>

Примечание 2: хотя использование связки collect(){...}.compact() не возбраняется, следует иметь в виду, что происходит двойное копирование массивов, что в конечном счете сказывается на производительности. Для задачи выбора (а не обработки) элементов из массива, более приемлемым вариантом является использование метода select(), который отбирает из исходного массива все элементы, удовлетворяющие условию, записанному в блоке:

<source lang="kpp"> var ary = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; var selected = ary.select() { |x| x > 6; }; // [7, 8, 9, 10] </source>

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



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

К примеру, вот пример кода, который обращается на FTP сервер и выбирает из некоторой директории все файлы, имеющие расширение '.conf': <source lang="kpp"> var configs = Directory.open('ftp://example.com/media/etc')

   .browse().select() { |file| file.name =~ `\.conf$`; };

</source>

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

Учитывая принципы полудинамической типизации, некоторые конструкции могут быть более лаконичными даже по сравнению с подобными языками. Например, следующий код на C# 2.0 позволяет увеличить все элементы массива на 2: <source lang="csharp"> int[] ary = { 1, 2, 3 }; int x = 2; Array.ConvertAll<int, int>(ary, delegate(int elem) { return elem * x; }); // { 2, 4, 6 } </source>

На K++ эта же конструкция будет выглядеть так: <source lang="kpp"> var ary = [1, 2, 3]; var x = 2; ary.convert() { |e| e * x; }; // [2, 4, 6] </source>

Конечно, можно привести массу доводов в защиту того или иного языка, укзазать преимущества однго и недостатки другого, чем с упоением и занимаются многочисленные разжигатели религиозных войн в среде IT. Мы не ставим себе целью доказать преимущества K++, скорее показать преимущества всех языков, использующих альтернативный подход к программированию в целом. К чести того же C# можно сказать, что в третей версии были введены замыкания, по краткости не уступающие K++. В целом, у каждого языка есть свои преимущества и недостатки и в общем случае сравнивать их нельзя. Можно, разве что, судить о степени удобства языка, в сочетании с его производительностью и о том, насколько эффективно он позволяет решать задачи, на которые был рассчитан и исходя из которых проектировался.

Персональные инструменты
Пространства имён

Варианты
Действия
Навигация
информация
документация
Инструменты