и возвращает подмассив массива a элементов T, удовлетворяющих условию pred.
Приведем пример вызова этой функции:
function f(x: integer): boolean;
begin
Result := ;
end;
var a := Seq(1,3,6,5,8);
var b := System.Array.FindAll(a,x -> x mod 2 = 0);
Здесь возвращается массив b, содержащий все четные значения массива a в том же порядке.
Ограничения на параметры обобщенных подпрограмм и классов
По умолчанию с переменными, имеющими тип параметра обобщенного класса или подпрограммы, внутри методов обобщённых классов и обобщенных подпрограмм можно делать лишь ограниченный набор действий: присваивать и сравнивать на равенство (отметим, что в NET сравнение на равенство внутри обобщений запрещено!).
Например, данный код будет работать:
function Eq<T>(a,b: T): boolean;
begin
Result := a = b;
end;
Можно также использовать присваивание переменной, имеющей тип параметра обобщенного класса или подпрограммы, значение по умолчанию, используя конструкцию default(T) - значение по умолчанию для типа T (nil для ссылочных типов и нулевое значение для размерных типов):
procedure Def<T>(var a: T);
begin
a := default(T);
end;
Однако, данный код
function Sum<T>(a,b: T): T;
begin
Result := a + b;
end;
вызовет ошибку компиляции до инстанцирования (создания экземпляра с конкретным типом). Такое поведение в .NET кардинально отличается от шаблонов в C++, где в коде шаблона можно использовать любые операции с шаблонными параметрами, и ошибка может произойти только в момент инстанцирования с конкретным типом.
Чтобы разрешить использование некоторых действий с переменными, имеющими тип параметра обобщенного класса или подпрограммы, используются ограничения на обобщенные параметры, задаваемые в секции where после заголовка подпрограммы или класса:
type
MyPair<T> = class
where T: System.ICloneable;
private
x,y: T;
public
constructor (x,y: T);
begin
Self.x := x;
Self.y := y;
end;
function Clone: MyPair;
begin
Result := new MyPair<T>(x.Clone,y.Clone);
end;
end;
В секции where через запятую перечисляются следующие ограничения:
На 1 месте: слово class или слово record или имя класса-предка.
На 2 месте: список реализуемых интерфейсов через запятую.
На 3 месте: слово constructor, указывающее, что данный тип должен иметь конструктор по умолчанию.
При этом каждое из мест, кроме одного, может быть пустым.
Для каждого типа-параметра может быть своя секция where, каждая секция where завершается точкой с запятой.
Пример. Обобщенная функция поиска минимального элемента в массиве. Элементы должны реализовывать интерфейс IComparable<T>.
function MinElem<T>(a: array of T): T;
where T: IComparable<T>;
begin
var min: T := a[0];
for var i := 1 to a.Length-1 do
if min.CompareTo(a[i])<0 then
min := a[i];
Result := max;
end;
К сожалению, нет возможности использовать операцию <, поскольку операции не входят в интерфейсы.
Элементы функционального программирования
Лямбда-выражения
Лямбда-выражение - это выражение специального вида, которое на этапе компиляции заменяется на имя подпрограммы, соответствующей лямбда-выражению и генерируемой компилятором на лету.
Здесь излагается полный синтаксис лямбда-выражений.
Здесь рассказывается о захвате лямбда-выражением переменных из внешнего контекста.
Лямбда-выражения запрещается использовать при инициализации полей класса или записи, внутри вложенных подпрограмм, в подпрограмме при наличии вложенной подпрограммы, в разделе инициализации модуля.
Синтаксис лямбда-выражений достаточно сложен и в данном пункте иллюстрируется на примерах.
Пример 1.
var f: integer -> integer := x -> x*x;
f(2);
Запись x -> x является лямбда-выражением, представляющем собой функцию с одним параметром x типа integer, возвращающую x*x типа integer. По данной записи компилятор генерирует следующий код:
function #fun1(x: integer): integer;
begin
Result := x*x;
end;
...
var f: integer -> integer := #fun1;
f(2);
Здесь #fun1 - это имя, генерируемое компилятором. Кроме того, код функции #fun1 также генерируется компилятором.
Пример 2. Фильтрация четных
Обычно лямбда-выражение передаётся как параметр подпрограммы. Например, в следующем коде
var a := Seq(3,2,4,8,5,5);
a.Where(x -> x mod 2 = 0).Print;
лямбда-выражение x -> x mod 2 = 0 задаёт условие отбора чётных чисел из массива a.
Пример 3. Сумма квадратов
var a := Seq(1,3,5);
writeln(a.Aggregate(0,(s,x)->s+x*x));
Иногда необходимо явно задавать тип параметров в лямбда-выражении.
Пример 4. Выбор перегруженной версии процедуры с параметром-лямбдой.
procedure p(f: integer -> integer);
begin
write(f(1));
end;
procedure p(f: real -> real);
begin
write(f(2.5));
end;
begin
p((x: real)->x*x);
end.
В данном примере вызов p(x -> x) вызовет ошибку компиляции, потому что компилятор не может выбрать, какую версию процедуры p выбирать. Задание типа параметра лямбды помогает устранить эту неоднозначность.
Пример 5. Лямбда-процедура.
procedure p(a: integer -> ());
begin
a(1)
end;
begin
p(procedure(x) -> write(x));
end.
Захват переменных в лямбда-выражении
Лямбда-выражение может использовать переменные из внешнего контекста. Такие переменные называются захваченными лямбда-выражением.
Пример 1. Захват переменной в запросе Select.
begin
var a := Seq(2,3,4);
var z := 1;
var q := a.Select(x->x+z);
q.Println;
z := 2;
q.Println;
end.
Здесь лямбда-выражение x->x+z захватывает внешнюю переменную z. Важно заметить, что при изменении значения переменной z запрос a.Select(x->x+z), хранящийся в переменной q, выполняется с новым значением z.
Пример 2. Накопление суммы во внешней переменной.
begin
var sum := 0;
var AddToSum: integer -> () := procedure (x) -> begin sum += x; end;
AddToSum(1);
AddToSum(3);
AddToSum(5);
writeln(sum);
end.
Методы последовательностей