Использование анонимных методов в Delphi

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

Для упрощения и наглядности рассмотрим операции над числовым массивом, хотя сам подход применим к любым упорядоченным контейнерам (например, TList<T>). Динамический массив не является объектным типом, поэтому для расширения его функциональности используем хэлпер. Тип элементов выберем Double:

uses
  SysUtils, Math;
  
type
  TArrayHelper = record helper for TArray<Double>
  strict private type
    TForEachRef = reference to procedure(X: Double; I: Integer; var Done: Boolean);
    TMapRef = reference to function(X: Double): Double;
    TFilterRef = reference to function(X: Double; I: Integer): Boolean;
    TPredicateRef = reference to function(X: Double): Boolean;
    TReduceRef = reference to function(Accumulator, X: Double): Double;
  public
    function ToString: string;    
    procedure ForEach(Lambda: TForEachRef);
    function Map(Lambda: TMapRef): TArray<Double>;
    function Filter(Lambda: TFilterRef): TArray<Double>;
    function Every(Lambda: TPredicateRef): Boolean;
    function Some(Lambda: TPredicateRef): Boolean;
    function Reduce(Lambda: TReduceRef): Double; overload;
    function Reduce(Init: Double; Lambda: TReduceRef): Double; overload;
    function ReduceRight(Lambda: TReduceRef): Double;
  end;

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

Метод ForEach

Метод ForEach выполняет обход элементов массива и для каждого из них вызывает указанную функцию. Как уже говорилось выше, функция передается методу ForEach в аргументе. При вызове этой функции метод ForEach будет передавать ей значение элемента массива, его индекс, а также булеву переменную Done, присвоение True которой позволит прервать итерации и выйти из метода (аналог инструкции Break для обычного цикла for). Например:

var
  A: TArray<Double>;
begin
  A := [1, 2, 3]; // Использование литералов массивов стало возможным в XE7
  // Умножить все элементы массива на 2
  A.ForEach(procedure(X: Double; I: Integer; var Done: Boolean)
    begin
      A[I] := X * 2;
      if I = 1 then
        Done := True; // Досрочный выход из ForEach
    end);
  WriteLn(A.ToString); // => [2, 4, 3]
end;

Реализация метода ForEach:
procedure TArrayHelper.ForEach(Lambda: TForEachRef);
var
  I: Integer;
  Done: Boolean;
begin
  Done := False;
  for I := 0 to High(Self) do
    begin
      Lambda(Self[I], I, Done);
      if Done then Break;
    end;
end;

// Вспомогательный метод: преобразование массива в строку
function TArrayHelper.ToString: string;
var
  Res: TArray<string>;
begin
  if Length(Self) = 0 then Exit('[]');
  ForEach(procedure(X: Double; I: Integer; var Done: Boolean)
    begin
      Res := Res + [FloatToStr(X)];
    end);
  Result := '[' + string.Join(', ', Res) + ']';
end;


Метод Map

Метод Map передает указанной функции каждый элемент массива, относительно которого он вызван, и возвращает массив значений, возвращаемых этой функцией. Например:

var
  A, R: TArray<Double>;
begin
  A := [1, 2, 3];
  // Вычислить квадраты всех элементов
  R := A.Map(function(X: Double): Double
    begin
      Result := X * X; 
    end);
  WriteLn(R.ToString); // => [1, 4, 9]
end;

Метод Map вызывает функцию точно так же, как и метод ForEach. Однако функция, передаваемая методу Map, должна возвращать значение. Обратите внимание, что Map возвращает новый массив: он не изменяет исходный массив.

Реализация метода Map:
function TArrayHelper.Map(Lambda: TMapRef): TArray<Double>;
var
  X: Double;
begin
  for X in Self do
    Result := Result + [Lambda(X)];
end;

Метод Filter

Метод Filter возвращает массив, содержащий подмножество элементов исходного массива. Передаваемая ему функция должна быть функцией-предикатом, т.к. должна возвращать значение True или False. Метод Filter вызывает функцию точно так же, как методы ForEach и Map. Если возвращается True, переданный функции элемент считается членом подмножества и добавляется в массив, возвращаемый методом. Например:

var
  Data: TArray<Double>;
  MidValues: TArray<Double>;
begin
  Data := [5, 4, 3, 2, 1];
  // Фильтровать элементы, большме 1, но меньшие 5
  MidValues := Data.Filter(function(X: Double; I: Integer): Boolean
    begin
      Result := (1 < X) and (X < 5); 
    end);
  WriteLn(MidValues.ToString); // => [4, 3, 2]

  // Каскад
  Data
    .Map(function(X: Double): Double
      begin
        Result := X + 5; // Увеличить каждый элемент на 5. 
      end)
	.Filter(function(X: Double; I: Integer): Boolean
      begin
        Result := (I mod 2 = 0); // Фильтровать элементы с четными номерами
      end)
	.ForEach(procedure(X: Double; I: Integer; var Done: Boolean)
      begin
        Write(X:2:0) // => 10 8 6
      end);    
end;

Реализация метода Filter:
function TArrayHelper.Filter(Lambda: TFilterRef): TArray<Double>;
var
  I: Integer;
begin
  for I := 0 to High(Self) do
    if Lambda(Self[I], I) then
      Result := Result + [Self[I]];
end;

Методы Every и Some

Методы Every и Some являются предикатами массива: они применяют указанную функцию-предикат к элементам массива и возвращают True или False. Метод Every напоминает математический квантор всеобщности ∀: он возвращает True, только если переданная Вами функция-предикат вернула True для всех элементов массива:

var
  A: TArray<Double>;
  B: Boolean;
begin
  A := [1, 2.7, 3, 4, 5];
  B := A.Every(function(X: Double): Boolean
    begin
      Result := (X < 10);
    end);
  WriteLn(B); // => True: все значения < 10.

  B := A.Every(function(X: Double): Boolean
    begin
      Result := (Frac(X)  = 0);
    end);
  WriteLn(B); // => False: имеются числа с дробной частью.
end;

Метод Some напоминает математический квантор существования ∃: он возвращает True, если в массиве имеется хотя бы один элемент, для которого функция-предикат вернет True, а значение False возвращается методом, только если функция-предикат вернет False для всех элементов массива:

var
  A: TArray<Double>;
  B: Boolean;
begin
  A := [1, 2.7, 3, 4, 5];
  B := A.Some(function(X: Double): Boolean
    begin
      Result := (Frac(X) = 0);
    end);
  WriteLn(B); // => True: имеются числа без дробной части.
end;

Реализация методов Every и Some:
function TArrayHelper.Every(Lambda: TPredicateRef): Boolean;
var
  X: Double;
begin
  Result := True;
  for X in Self do
    if not Lambda(X) then Exit(False);
end;

function TArrayHelper.Some(Lambda: TPredicateRef): Boolean;
var
  X: Double;
begin
  Result := False;
  for X in Self do
    if Lambda(X) then Exit(True);  
end;

Обратите внимание, что оба метода, Every и Some, прекращают обход элементов массива, как только результат становится известен. Метод Some возвращает True, как только функция-предикат вернет True, и выполнит обход всех элементов массива, только если функция-предикат всегда возвращает False. Метод Every является полно противоположностью: он возвращает False, как только функция-предикат вернет False, и выполняет обход всех элементов массива, только если функция-предикат всегда возвращает True. Кроме того, отметьте, что в соответствии с правилами математики для пустого массива метод Every возвращает True, а метод Some возвращает False.

Методы Reduce и ReduceRight

Методы Reduce и ReduceRight объединяют элементы массива, используя указанную Вами функцию, и возвращают единственное значение. Это типичная операция в функциональном программировании, где она известна также под названием «свертка». Примеры ниже помогут понять суть этой операции:

var
  A: TArray<Double>;
  Total, Product, Max: Double;
begin
  A := [1, 2, 3, 4, 5];
  // Сумма значений
  Total := A.Reduce(0, function(X, Y: Double): Double
    begin
      Result := X + Y;
    end);
  WriteLn(Total); // => 15.0

  // Произведение значений
  Product := A.Reduce(1, function(X, Y: Double): Double
    begin
      Result := X * Y;
    end);
  WriteLn(Product); // => 120.0

  // Наибольшее значение (используется альтернативная реализация Reduce)
  Max := A.Reduce(function(X, Y: Double): Double
    begin
      if X > Y then Exit(X) else Exit(Y);
    end);
  WriteLn(Max); // => 5.0
end;

Метод Reduce принимает два аргумента. Во втором передается функция, которая выполняет операцию свертки. Задача этой функции – объединить некоторым способом или свернуть два значения в одно вернуть свернутое значение. В примерах выше функции выполняют объединение двух значений, складывая их, умножая и выбирая наибольшее. В первом аргументе передается начальное значение для функции.

Функции, передаваемые методу Reduce, отличаются от функций, передаваемых методам ForEach и Map. Значение элемента массива передается им во втором аргументе, а в первом аргументе передается накопленный результат свертки. При первом вызове в первом аргументе функции передается начальное значение, переданное методу Reduce в первом аргументе. Во всех последующих вызовах передается значение, полученное в результате предыдущего вызова функции. В первом примере, из приведенных выше, функция свертки сначала будет вызвана с аргументами 0 и 1. Она сложит эти числа и вернет 1. Затем она будет вызвана с аргументами 1 и 2 и вернет 3. Затем она вычислит 3 + 3 = 6, затем 6 + 4 = 10 и, наконец, 10 + 5 = 15. Это последнее значение 15 будет возвращено методом Reduce.

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

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

Реализация методов Reduce:
function TArrayHelper.Reduce(Init: Double; Lambda: TReduceRef): Double;
var
  I: Integer;
begin
  Result := Init;
  if Length(Self) = 0 then Exit;
  for I := 0 to High(Self) do
    Result := Lambda(Result, Self[I]);
end;

// Альтернативная реализация Reduce – с одним аргументом
function TArrayHelper.Reduce(Lambda: TReduceRef): Double;
var
  I: Integer;
begin
  Result := Self[0];
  if Length(Self) = 1 then Exit;
  for I := 1 to High(Self) do
    Result := Lambda(Result, Self[I]);
end;

Метод ReduceRight действует точно так же, как и метод Reduce, за исключением того, что массив обрабатывается в обратном порядке, от больших индексов к меньшим (справа налево). Это может потребоваться, если операция свертки имеет ассоциативность справа налево, например:

var
  A: TArray<Double>;
  Big: Double;
begin
  A := [2, 3, 4];
  // Вычислить 2^(3^4). 
  // Операция возведения в степень имеет ассоциативность справа налево
  Big := A.ReduceRight(function(Accumulator, Value: Double): Double
    begin
      Result := Math.Power(Value, Accumulator); 
    end);
  Writeln(Big); // => 2.41785163922926E+0024
end;

Реализация метода ReduceRight:
function TArrayHelper.ReduceRight(Lambda: TReduceRef): Double;
var
  I: Integer;
begin
  Result := Self[High(Self)];
  if Length(Self) = 1 then Exit;
  for I := High(Self) - 1 downto 0 do
    Result := Lambda(Result, Self[I]);
end;

Следует отметить, что методы Every и Some, описанные выше, являются своеобразной разновидностью операции свертки массива. Однако они отличаются тем, что стремятся завершить обход массива как можно раньше и не всегда проверяют значения всех его элементов.

Вместо заключения

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

// Вспомогательная функция: вычисление суммы аргументов.
// Свободную функцию (как и метод экземпляра) можно использовать
// в качестве параметра для метода, принимающего reference-тип
function Sum(X, Y: Double): Double;
begin
  Result := X + Y;
end;

// Вычисление среднего значения (Mean) и СКО (StdDev).
procedure MeanAndStdDev;
var
  Data: TArray<Double>;
  Mean, StdDev: Double;
begin 
  Data := [1, 1, 3, 5, 5];    
  Mean := Data.Reduce(Sum) / Length(Data);
  StdDev := Sqrt(Data
    .Map(function(V: Double): Double
      begin
        Result := Sqr(V - Mean); // Квадраты разностей
      end)    
    .Reduce(Sum) / Pred(Length(Data))); 
    WriteLn('Mean: ', Mean, ' StdDev: ', StdDev); // => Mean: 3.0 StdDev: 2.0  
end;

Исходники

Статья была улучшена, благодаря Вашим комментариям.

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

Подробнее
Реклама
Комментарии 109
  • 0
    Это всё, конечно, весело, но почему функции только для double? Или дельфи обобщённую версию не потянет?

    for I := 0 to Pred(Length(Self)) do
    for I := Length(Self) - 2 downto 0 do

    Дельфи с индексами от нуля забавно выглядит. :)

    Frac(X) = 0

    Сравнивать double через равенство — это нормально?
    • +1
      Сравнивать double через равенство — это нормально?

      Даже работать будет :) Правда не всегда…
      • 0
        К сожалению в Delphi массив это не класс и добавить к нему методы можно только через т.н. helper, а helper тупой, не умеет работать с обобщенным типом.
        • НЛО прилетело и опубликовало эту надпись здесь
          • –4
            В паскале естественная индексация — с единицы, поэтому цикл for идеально приспособлен под неё: for i := 1 to N или for i := N downto 1. В сях естественная индексация с нуля, а циклу for вообще пофиг на индексы: хоть for (i = 0; i < N; i++), хоть for (i = 1; i <= N; i++). Когда же в дельфи индексация с нуля, получается глупость: язык не приспособлен для этого, и появляются странные и неестественные вычитания единиц.
            • +3
              Позвольте, но это в паскале или в бейсике, а в Delphi для массивов индексация идет с нуля.
              С единицы индексируются только строки, если, конечно, не включена директива {$ZEROBASEDSTRINGS ON}
              • 0
                Э… Хотите сказать, всегда так было? В том дельфи, который я помню, индексы задавались явно… или это был паскаль… Если что, я к коду на дельфи уже много лет не прикасался, и даже в те тёмные времена в основном билдер мучал. Но меня всё равно удивляет несоответствие между языком и индексами.
                • +3
                  Ага — в дельфях (32 битных) так всегда и было :)
                • +3
                  Позвольте, но это в паскале или в бейсике, а в Delphi для массивов индексация идет с нуля.
                  А вы какие массивы имеете в виду — статические или динамические? Статические индексируются как пользователь задаст. Динамические да, с нуля.
                  • 0
                    Статические так же, но их принято с нуля задавать :)
                    • 0
                      А чтобы не задумываться, как он задан, есть удобные функции low() и high().
                • +1
                  Когда же в дельфи индексация с нуля, получается глупость: язык не приспособлен для этого, и появляются странные и неестественные вычитания единиц.

                  Здесь опять-же сказывается отсутствие знаний в предметной области, и не понимание самого принципа работы Delphi компилятора, обрабатывающего такие конструкции.:)
                  Не обижайтесь — но вы действительно сказали не правильную мысль :)
                  • 0
                    Ну раскройте уж мысль. Мне жутко интересно, при чём тут вообще компилятор. Я считаю, что необходимость использовать при обходе массива функции pred или вычитания — это архитектурный недостаток, ухудшающий читаемоть кода (не то чтобы си здесь блистал, где легко спутать < и <=, но всё же). Я не прав?
                    • +2
                      Используйте Low и High.
                      Архитектурный недостаток… возможно. Но при чем здесь заточенность на массива с индексацией от 1?
                      Я с паскаля начинал свой путь программиста… и всегда считал нормой индексацию с 0.
                      Да и сами структуры паскаля, типа того же динамического массива, требуют именно с 0 начинать работу.
                      • +2
                        Потому что естественным для компилятора — вычислять адрес элемента по его индексу. В случае с индексацией с нуля адрес вычисляется как:
                        ElementPointer = ArrayPointer + Index*ElementSize
                        а при индексации с 1 компилятору нужно будет делать дополнительные приседания с вычитанием единицы:
                        ElementPointer = ArrayPointer + (Index-1)*ElementSize
                        • 0
                          Не бывает «естественно для компилятора». То, что вы описали — «естественно для машинного кода» (и то с натяжкой). А вот компилятор все эти индексы как раз должен скрывать, преобразовывая из удобного для программиста в удобное для компьютера. Весь смысл компилятора в этом.

                          Ну и таки в цикле for на уровне машинного кода обычно никаких «ArrayPointer + Index * ElementSize» нет, потому что это тормозное выражение ни для компилятора, ни для машинного кода естественным не является.
                          • 0
                            Вы сейчас про какие преобразования, рантайм или компайлтайм?
                            Далеко не все преобразования возможно провести во время компиляции. Например из хеш функции прилетает некоторый BucketIndex. Как ни крути — это будут либо накладные расходы на преобразование, либо ArrayPointer будет смещен на -BaseIndex*ElementSize, и тогда чтобы просто взять указатель на первый элемент массива его нужно будет опять считать.
                            Так и так накладные расходы, и чтобы минимизировать накладные расходы и быть ближе к железу — индексация с нуля.

                            Ну и таки в цикле for на уровне машинного кода обычно никаких «ArrayPointer + Index * ElementSize» нет, потому что это тормозное выражение ни для компилятора, ни для машинного кода естественным не является.
                            Мы сейчас про индексацию? Или конкретно вот этот цикл? Вы утверждаете, что индексация массива с нуля — архитектурный недостаток? То есть проблема не в том, что for использует только <= для сравнения, а именно в индексации, да?
                            • 0
                              Далеко не все преобразования возможно провести во время компиляции.

                              Поэтому я написал «обычно».

                              Вы утверждаете, что индексация массива с нуля — архитектурный недостаток?

                              Проблема в for, который синтаксически оптимизирован под явные границы индексов, а при индексации с нуля эти границы нужно вычислять в исходном коде (или low и high, или вычитание единицы). Непривычно видеть одну из самых банальных конструкций — обход массива — обёрнутой в синтаксический мусор.

                              Что индексация с нуля более оптимальна для компьютера — это и так понятно, с этим я не спорю.
                              • +1
                                Так и так накладные расходы, и чтобы минимизировать накладные расходы и быть ближе к железу — индексация с нуля.
                                На современных процессорах под современные ОС это экономия на спичках. А если учесть, что поправку достаточно вычислить единожды при входе в цикл — так и вовсе.
                                Гораздо продуктивнее в большинстве случаев будет оптимизация алгоритма, а не охота за минус одним.
                                • 0
                                  Ну для начала — делфи создавался не сегодня. Да и зачем выворачивать все шиворот на выворот? Мне лично индексация с нуля удобна, вам нет?
                              • 0
                                Его там и нет, в обоих случаях. К примеру, вот обращение к элементам массива начинающихся с нуля:

                                var
                                  I: Integer;
                                  Buff: array [0..5] of Byte;
                                begin
                                  for I := 0 to 5 do
                                    Buff[I] := I;
                                

                                асм листинг:

                                Unit1.pas.43: for I := 0 to 5 do
                                005DAA67 33D2             xor edx,edx
                                005DAA69 8BC4             mov eax,esp
                                Unit1.pas.44: Buff[I] := I;
                                005DAA6B 8810             mov [eax],dl
                                005DAA6D 42               inc edx
                                005DAA6E 40               inc eax
                                Unit1.pas.43: for I := 0 to 5 do
                                005DAA6F 83FA06           cmp edx,$06
                                005DAA72 75F7             jnz $005daa6b
                                

                                вот то-же самое, но для массива индексирующегося с 10:

                                var
                                  I: Integer;
                                  Buff: array [10..15] of Byte;
                                begin
                                  for I := 10 to 15 do
                                    Buff[I] := I;
                                

                                асм листинг:

                                Unit1.pas.34: for I := 10 to 15 do
                                005DAA4F BA0A000000       mov edx,$0000000a
                                005DAA54 8BC4             mov eax,esp
                                Unit1.pas.35: Buff[I] := I;
                                005DAA56 8810             mov [eax],dl
                                005DAA58 42               inc edx
                                005DAA59 40               inc eax
                                Unit1.pas.34: for I := 10 to 15 do
                                005DAA5A 83FA10           cmp edx,$10
                                005DAA5D 75F7             jnz $005daa56
                                


                                как можно увидеть никаких вычитаний типа Pred и прочего тут попросту нет.
                                • 0
                                  Я о том и говорю: компилятор любой цикл for спокойно оптимизирует: хоть с нулём, хоть с единицей, хоть я пятёркой; хоть с константами, хоть с low и high, хоть с вычитанием единицы. А вот в исходном коде гораздо приятнее иметь чистый цикл.
                        • НЛО прилетело и опубликовало эту надпись здесь
                          • +2
                            Чтобы не натыкаться на грабли, надо всегда использовать Low и High. Как для строк, так и для массивов.

                            for i := Low(X) to High(X)
                            
                            for i := High(X) downto Low(X)
                            


                            и проблем не будет.

                            Вообще, тенденция такова, что скоро строки будут 0-based, а прямое изменение строк — вообще запрещено. Т.е. нельзя будет сделать s[i] :=…
                            • 0
                              Вообще оно уже с 7-й делфи давно запрещено а S[i] реализуется через обертку-костыль и не всегда правильно работает.
                              • 0
                                Это как не всегда? Можно пример?
                                • 0
                                  Да, что там за обертки?
                                  У нас весь код основан на S[i] :=
                                  и все работает прекрасно (текстовый редактор со спец. возможностями)
                                  • +1
                                    в хелпе к делфи черным по белому написано что конструкция S[i] небезопасна и оставлена только для обратной совместимости, а нужно и правильно использовать конструкции типа copy(S,i,1) которые оптимизируются компилятором где это возможно.

                                    при помощи S[i] можно запросто вылезти в чужую память и не заметить этого, либо получить AV если не повезёт.
                                    • 0
                                      Ну во-первых, у нас всегда включены RangeChecks=ON, не уверен на 100%, влияют ли они на строки, но за границу дин. массива точно не вылезешь.
                                      А во-вторых, давно ли Delphi стал супер-безопасным языком. Я могу использовать Pointer, Absolute, Move и еще кучу способов испортить память. Но я же понимаю, что так делать нужно только там, где это требуется.
                                      И мне удобно так писать, не вижу тут ничего опасного:
                                      Скрытый текст
                                      function ConvertBadChars(const aStr: String): String;
                                      var
                                        i: Integer;
                                      begin
                                        Result := aStr;
                                        for i := 1 to Length(Result) do
                                          case Result[i] of
                                            '«', '»': Result[i] := '"';
                                            '„', '“', '”': Result[i] := '"';
                                            '–', '—': Result[i] := '-'; // тире на знак "минус"
                                            '‹': Result[i] := '<';
                                            '›': Result[i] := '>';
                                            '`', '‘', '’': Result[i] := ''''; // Апострофы разного вида
                                          end;
                                      end;
                                      

                                      • 0
                                        Даже в релизе данная настройка включена?
                                        • 0
                                          Да, в релизе как и в дебаге включено всё по макусимуму Range Checks, Overflow Checks, Assertions. Что бы мы без этого делали, мы бы задолбались искать ошибки. Влияния данных опций на скорость работы не заметили.
                                          • 0
                                            На скорость, конечно не повлияет (ну тут как — скажем, это будет незаметно для пользователя), а вот делать конечного пользователя бетатестером — это тот еще юмор :)
                                            Мы все комплексные тесты проводим со всеми включенными отладочными галками, включая ассерты, но в релиз все уходит в отключенном виде.
                                            А для поиска ошибок (в релизе) мы используем EurekaLog.
                                            • +1
                                              а вот делать конечного пользователя бетатестером

                                              Ну да, конечно лучше скрыть от него ошибки неправильно работающей программы, чтобы казалось, что оно работает, когда на самом деле не работает. Или чего лучше — бороться с гейзенбагом, когда то глючит, то нет.
                                              Пусть уж лучше сразу свалится — мы сразу и исправим.
                                              • 0
                                                Не-не, я не про то говорю :)
                                                Вот возьмем ситуацию с Overflow — и присвоим инту значение выше диапазопа $7FFFFFF — разве это повлияет на работоспособность ПО, тем более что в криптографии это вообще штатная и допустимая сущность?
                                                Вообще, все ошибки связанные с переполнение должны быть покрыты еще на этапе альфатеста посредством юниттестов.
                                                В моей практике еще ниразу не встретилось что-то что повлияло на работоспособность нашего ПО, после юнит-тестирования со всеми галками.
                                                Поэтому и считаю данный шаг не только избыточным, но и вредным — лучше расширять нагрузочное тестирование, чем выдавать свои ляпы пользователю.
                                                В клайнем случае — та-же EurekaLog спасет и покажет место.
                                        • 0
                                          Испортить то можно разными способами, но зачем лишний раз искушать судьбу?

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

                                          То что у вас не «тормозит» означает только одно — работа со строками не является узким местом в программе.
                                          Да, опции проверки диапазонов рассчитаны на работу только в режиме отладки — чтобы выявить откровенные косяки, а в остальном программа не должна ни при каких обстоятельствах допускать выход за границы диапазона — проверка границ это просто «колокольчик» оповещающий о проблемах в программе коих не должно быть по определению.
                                          • 0
                                            Ага, действительно, как только перешли на Delphi, с паскаля, так все и поменялось :)
                                            Кстати, а зачем вы так удачно обошли механизм реаллока памяти, упомянув про конкатенацию? Это действительно так работает только в Дельфи? :)
                                            Но впрочем я пожалуй смогу утверждать, что даже после кокатенации строк, что в сях, что в дельфи — это будет именно единый и непрерывный массив.
                                            Хотите поспорить? :)
                                            • +1
                                              программа не должна ни при каких обстоятельствах допускать выход за границы диапазона

                                              Программа не должна это, программа не должна то… Какая ересь. Программы пишут люди и постоянно допускают ошибки. Нет программ без ошибок, ну нету. Никакие синтетические тесты не выявят ошибок, происходящих на бесконечном множестве входных данных, которые возникают у пользователей.
                                              Если бы тесты работали, не было бы так много часто выпускаемых обновлений винды, сервис паков Microsoft SQL Server и т.п., а уж у них, поверьте, автоматическое тестирование настроено как надо и работает гораздо круче чем, у простых смертных. Чтобы быстро выявлять любые ошибки мы при написании кода используем концепцию самоверифицируемого кода, т.е. кроме всех возможных автоматических проверок компилятора код постоянно наряду с рабочими действиями выполняет дополнительный код чисто по верификации данных (типа сложных Assert'ов). Ну конечно не весь код, а критичные участки, в которых ошибки ну вот прямо совсем недопустимы, т.к. например, они могут ввести систему в несогласованное состояние. Примером может быть какой-то участок криптоалгоритма, когда после выполнения ряда вычислений мы можем сразу провести обратные вычисления и сравнить с исходными данными, пока они «еще есть у нас в открытом виде».
                                              • –1
                                                Выход за пределы массива допущенный программистом — это только одна из тысяч проблем, и это та проблема которая довольно хорошо ловится тестами и соблюдением элементарных правил при написании кода.
                                                Все те заплатки и дыры это преднамеренное нарушение нормального хода программы, человеческое раздолбайство и небольшая доля аппаратных особенностей.
                                                Нельзя защитится от всех напастей, но хотя бы очевидные ошибки не стоит допускать. Программа может быть спроектирована так что возникающая ошибка не приводит к фатальной проблеме но писать такие программы повидимому долго и нудно. Проще накидать кучу кода и ловить все баги в продакшене.
                                                Так что я считаю что программы МОГУТ быть написаны без багов только это долго и нудно, а люди ленивые — поэтому имеем что имеем.
                                                • +1
                                                  программы МОГУТ быть написаны без багов только это долго и нудно, а люди ленивые — поэтому имеем что имеем

                                                  А я считаю, что программы могут быть написаны быстро и не нудно и содержать при этом некоторое количество ошибок. Самое главное, чтобы код был написан таким образом, чтобы ошибка легко выявлялась и не приводила ни к каким плачевным последствиям (т.е. чтобы были реализованы надежные откаты, транзакции и т.п.). В этом случае ошибки будут оперативно выявлены и иправлены. У нас, извините, сроки и план. Мы не можем позволить себе не вносить «очень много правок» и не можем позволить себе долго пытаться отладить эти правки на всех мыслимых и немыслимых ситуациях.
                                                  При разработке придерживаемся принципов "экстремального программирования".
                                          • +3
                                            в хелпе к делфи черным по белому написано что конструкция S[i] небезопасна и оставлена только для обратной совместимости
                                            Не могли бы вы привести цитату про небезопасность и обратную совместимость?
                                            • –2
                                              Что-то не могу найти то самое место… давно дело было. Запомнилось и теперь вот.
                              • 0
                                Кстати анонимные методы очень понравилось использовать в обработчиках. Объявляю их как референсы и вперед. Хочешь в обработчик присваивай обычный метод, а хочешь анонимный. У какой-нибудь кнопки, где всего одна строка кода при нажатии — идеальный вариант. Количества кода в GUI сократилось оч. прилично.
                                • 0
                                  Если потребуется прервать итерации раньше, внутри функции можно возбуждать исключение, а вызов ForEach помещать в блок try.

                                  Это не слишком уж сурово? Выход через исключение — тот еще юмор.
                                  • 0
                                    Можно сделать, чтобы анонимная функция имела var-параметр типа aBreak: Boolean = False, который можно задать в True, когда нужно прерваться
                                    • 0
                                      Можно и так, но гораздо проще сделать TForEachRef не процедурой, а функцией, и ориентироваться на результат, что собственно и показано в нижних вариантах кода :)
                                      • 0
                                        гораздо проще

                                        Надеюсь, это был просто речевой оборот :) Потому что оба варианты по простоте тривиальны до неприличия. А вариант с процедурой удобнее тем, что если параметр не out, а var, то его умолчальное значение будет задано в вызывающем коде (т.е. когда я не хочу прерываться, мне вообще ничего не нужно делать, все сработает само), а в функции результат придется всегда присваивать, что неудобно, а можно например попросту вообще забыть это сделать.
                                        • 0
                                          Просты — да, но задачи решают разные. Особенно при использовании функции с var параметром :)
                                          • 0
                                            Я говорил не про функцию, а про процедуру. Процедура удобнее, чем функция, т.к. когда «брейкать» не нужно, я избавлен от необходимости присваивать этому параметру значение, т.к. оно будет присваиваться в вызывающем коде. А функции я обязан буду присваивать значение результата каждый раз в своем коде.
                                            • 0
                                              Опять не согласен :)
                                              1. Вызов функции приводит к обязательной инициализации параметра Result.
                                              2. Однако — вызов процедуры с Handled флагом (как var параметр) приводит к обязательной инициализации VAR параметра.
                                              Вопрос — так где выгода, не те-же ли яйца, только в профиль? :)
                                              • 0
                                                Не до конца понял, что вы подразумевали под «инициализацией». То, что под результат функции будет выделяться память — это да. Но если результату явно не присвоить значение, то даже компилятор вам скажет «Return value of function might be undefined», т.е. функция вернет случайное «нечто». Я хотел избежать только этого (т.е. чисто архитектурно-проектное соображение при проектировании того класса, т.к. так меньше способов накосячить, забыв присвоить результат).
                                                Про EAX'ы и оверхеды я понял, но в моих проектах обычно тормоза совсем в других местах, далеко не в процессоре.
                                                • 0
                                                  т.е. функция вернет случайное «нечто». Я хотел избежать только этого (т.е. чисто архитектурно-проектное соображение при проектировании того класса, т.к. так меньше способов накосячить, забыв присвоить результат).
                                                  Так а сейчас вам что мешает избежать этого? dcc32 -W^NO_RETVAL и шанса на ошибку не останется.
                                                  • 0
                                                    Я не понял, вы мне это предупреждение включить или выключить рекомендуете? Если я его выключу, то шанс на ошибку только увеличится. А если включить, так он включен у меня, но иногда из-за нескольких сотен ворнингов и хинтов в сторонних компонентах в проекте на полтора миллиона строк кода я могу просмотреть этот ворнинг в моем модуле. Вот если бы вы мне посоветовали, как в Delphi настроить, чтобы он не показывал ворнинги в сторонних компонентах (по папке или как-то фильтровать), было бы супер.
                                                    • 0
                                                      Как-то вот так:
                                                      {$WARNINGS OFF} «кривой» код {$WARNINGS ON}
                                                      • 0
                                                        Очень «удобно», особенно когда я буду потом обновлять версию этих компонентов, перевносить эту правку в кучу модулей.
                                                        • 0
                                                          Тогда надо избавляться от этих модулей, разработчики которых не устраняют все предупреждения. Модули становятся частью вашего приложения, все неустраненные предупреждения когда-то могут стать причиной ошибок, независимо от того в каких модулях находятся своих или сторонних.
                                                          Не знаю, сработает ли, но директивой можно обернуть импорт этих модулей который будет явно под вашим контролем и не изменятся при обновлении сторонних компонент.
                                                          • 0
                                                            директивой можно обернуть импорт этих модулей

                                                            К сожалению, Delphi так не умеет.

                                                            надо избавляться от этих модулей, разработчики которых не устраняют все предупреждения

                                                            Увы, учитывая слабое Delphi Community хороших (работающий) компонентов далеко не так много, чтобы можно было просто «взять и отказаться». По поводу «разработчики которых не устраняют», говорим спасибо, если исходники этих компонентов хотя бы вообще комплируются в новых версиях Delphi без допиливания напильником и если последний коммит разработчиков в эти компоненты был хотя бы менее года назад. Мы понимаем, что это плохо, но пока считаем нецелесообразным написание и дальнейшее сопровождение собственных самописных компонент криптографии, архивации, доступа к различным SQL серверам, работы с файлами в графических форматах, сложных визуальных контролов.
                                                      • 0
                                                        При -W^NO_RETVAL предупреждение «Return value of function '%s' might be undefined» будет приводить к ошибке компиляции. Того же можно достичь через настройки проекта.
                                                        Вернее, если быть точным, -W^^NO_RETVAL т.к. "^" в командной строке трактуется как escape-символ.
                                                        Как минимум в Delphi 2009 такая возможность уже была.
                                                        • 0
                                                          Понял, спасибо! Но к сожалению в IDE я что-то такой настройки не нашел :(
                                                          • 0
                                                            Пункт меню Project ⇒ Options, в диалоге настроек закладка Delphi Compiler ⇒ Hints and Warnings.
                                                            Дочерними узлами пункта «Output warnings» перечислены warnings и любому из них помимо «True» и «False» можно задать значение «Error».
                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                              • 0
                                                                В Delphi 2009 и XE2 это есть, возможно зависит от редакции (Starter/Professional/Architect).
                                                                • 0
                                                                  В XE3 есть. Редакция Architect.
                                                                • 0
                                                                  Не знал про эту возможность! Включил, наслаждаюсь! :) Спасибо!
                                                    • 0
                                                      Ну и последний момент: результат выполнения функции идет через регистр EAX, а BY REF параметр в виде VAR переменной — по сути ссылка (не важно даже куда она ведет, на область в куче, или собственную память, выделенную посредством VirtualAlloc), что, в итоге, дает дополнительный оверхед при ее использвании.
                                                      Этот момент желательно учитывать.
                                                • 0
                                                  Может и проще, но у ForEach концепция не-функции, и поступать так я бы не стал :)
                                                  • 0
                                                    А я бы не согласился :)
                                                    ForEach — как любая итеративная функция имеет право на досрочный выход.
                                                    За базу возьмем, к примеру, операцию Cut-Paste файлов из папки в папку, посредством ForEach обертки.
                                                    К примеру путь одного из копируемых файлов выходит за диапазон MAX_PATH, что делать?
                                                    В ОС принято отображать диалог, в котором пользователь имеет полное право отменить столь сомнительную операцию :)
                                                • 0
                                                  Это потребует дополнительного параметра в заголовке и дополнительной проверки if при проходе списка элементов, но идея, конечно, годная. Более красивое решение, чем try\except :)
                                                • 0
                                                  Вообще в Делфи принято активно использовать исключения. И если мы в какой-то функции вызываем неизвестный обработчик — мы должны быть готовы, что он может выкинуть исключение.
                                                  Но строить логику на исключениях это конечно не самая хорошая идея.
                                                • –1
                                                  ЗЫ: и кстати, просто как совет — включайте в состав статьи ссылку на исходный код демопримеров. Не удобно все это копировать ручками и перепроверять, гораздо проще работать с уже готовыми исходниками :)
                                                  • +1
                                                    Автор как-то не до конца прочуствовал мощь анонимных методов. Для меня их основное применение — в многопоточных приложениях, чтобы легко передавать значения переменных между потоками (например, значения локальных переменных метода основного потока передать в какой-то другой поток). Все переменные, на которые есть ссылки из анонимного метода, будут «захвачены» компилятором и размещены не на стеке, а в куче, за счет чего обратиться к их значениям из другого потока можно и после того, как поток управления основного потока выйдет из области видимости того метода, в котором эти переменные были объявлены как локальные.
                                                    Если кто пользовался библиотекой OmniThreadLibrary, она вся на анонимных методах работает.
                                                    • 0
                                                      Все переменные, на которые есть ссылки из анонимного метода, будут «захвачены» компилятором и размещены не на стеке, а в куче

                                                      Кстати — отлично подмечено, причем тут даже не обязательно многопоточность, достаточно это исполнить рекурсивно и изучить, где в памяти будет расположена каждая из таких переменных.
                                                      • 0
                                                        будут «захвачены» компилятором и размещены не на стеке, а в куче, за счет чего обратиться к их значениям из другого потока
                                                        А это безопасно?
                                                        • 0
                                                          Странный вопрос :) Это безопасно, например, если к этим данных обращаться только на чтение из других потоков, а если на запись, то чтобы в момент записи их мог писать только один поток и никто не мог их в это время читать. Это то же самое, как если бы эти данные разместить в глобальных переменных, только сами глобальные переменные при этом заводить не требуется, удобство только в этом :)
                                                          • 0
                                                            Т.е. неявные глобальные переменные, получается. Удобство сомнительное, скорее потенциальный источник ошибок.
                                                      • +1
                                                        C Делфи завязал после семерки, некоторые вещи в новинку. Я правильно понимаю, reference to это просто другой способ записи указателя на функцию? Также без контроля ссылочной целостности?
                                                        • 0
                                                          Во-во, закончил на D2007, смотрю код и в шоке. Анонимные функции, куча новых keywords, хелперы и прочее — круто продвинулись. Единственное, что анонимные функции с классическим begin/end выглядят страшно после шарповых брэкетов, к примеру.
                                                          • +4
                                                            reference to — это новая подкапотная магия компилятора. Там все несколько сложнее. Указатель на функцию под капотом оборачивается в интерфейс. Объект, реализующий этот интерфейс тянет с собой весь scope, с которым будет работать функция.

                                                            То есть вот у нес есть запись:
                                                            var a: Integer;
                                                            begin
                                                              DoSome();
                                                              a := 2;
                                                              DoSomeWithCallBack( function : Integer;
                                                                begin
                                                                  Result := a;
                                                                  WriteLn(a);
                                                                end;
                                                              );
                                                            end;

                                                            Код выведет число 2.
                                                            Под капотом функция превратится в статическую функцию, а так же будет создан объект типа:
                                                            IRefToProc = interface
                                                              function Execute: Integer;
                                                            end;
                                                            TNewObject = class(TInterfacedObject, IRefToProc)
                                                              a : Integer;
                                                              FStaticProcPointer: Pointer;
                                                              function Execute: Integer;
                                                            end;
                                                            
                                                            у которого есть заполненное поле a = 2
                                                            Это и есть грубо говоря захваченный scope
                                                            Автоматически оно начинает работать для методов класса, т.к. этим же механизмом мы можем захватить переменную self.
                                                            Так что когда вы объявляете reference to — вы просите компилятор вот так вот обернуть в интерфейсный враппер ваш метод.
                                                            Это принципиальное отличие от of object; методов, которые из себя представляют структуру TMethod с self и указателем на метод.
                                                          • –7
                                                            Все эта делфи сдохнуть не может…
                                                            • +2
                                                              С чего бы ей подыхать?
                                                              • –4
                                                                Да кому это уродство нужно? даже в том, в чем делфи сильна — формошлепстве, уже есть более адекватные вещи.
                                                                • –4
                                                                  Да и инструментарий плохой, вызывает рвотные позывы. Приходится поддерживать старый код на делфи иногда.
                                                                  • +2
                                                                    Плохому танцору понятно что мешает…
                                                                    • –3
                                                                      А с чего вы решили, что я плохой танцор? Делфи — уродство, какое еще нужно поискать, а если вы не смогли освоить что-то по-новее, ну это ваши проблемы…
                                                                    • +1
                                                                      Инструментарий XE версий последних лет вполне себе хорош. В чем то конечно не дотягивает до Visual Studio, в чем-то наоборот удобнее. А кроме Vusial Studio и Delphi нормальных альтернатив для разработки чисто под Винду как бы и нет. Если знаете, назовите.
                                                                      • +1
                                                                        Qt ничего так.
                                                                        • +1
                                                                          Да, наверное соглашусь и когда нужно создать кроссплатформенное GUI приложение, у Qt практически монополия. Но когда кроссплатформенность не требуется, а вот Винду нужно поддерживать более «нэйтивно», мне кажется Qt — это вставление палок самому себе в колеса (хотя это чисто ИМХО). Я сам не разрабатывал на Qt, у него есть дизайнер форм а-ля как в Visual Studio или Delphi? И как у него со сложными GUI-компонентами типа красивых древовидных списков, всяких сложных гридов? Есть они?
                                                                          • –2
                                                                            да полно там всего.
                                                                            Еще зыбыли MFC — достойная вещь, под винду, отлично просто.
                                                                            • +2
                                                                              MFC для Rich GUI Applications по-моему это ад. ) Я имею в виду когда в приложении действительно много разноплановых окон с большим количеством меню, таблиц, списков (ну а-ля какая-нибудь КИС).
                                                                            • 0
                                                                              Много лет писал на Delphi, потом на Qt перешел. Ну что сказать про GUI. В Qt Designer'е конечно можно сделать почти все что угодно, правда подход на основе лэйаутов после Делфи кажется слишком мудрёным. И форма в дизайнере может выглядеть совсем не так как в ран-тайме.
                                                                              Что касается «красивых контрольчиков» — особо не встречал. Предполагается что лучше делать просто и нативно для ОС.

                                                                              По-поводу Qt вообще (и в частности после Delphi) — очень достойный фреймворк, богатый возможностями. Минусы конечно тоже есть. Гораздо больше возможностей в принципе C++ по сравнению с Delphi имеет. Я прям первый год наиграться не мог :)

                                                                              Но конечно это ни в коем случае не повод Delphi записывать в «отстой». Хороший, мощный в умелых руках инструмент.
                                                                              • 0
                                                                                Лэйауты Qt даже после WPF «слишком мудреными» кажутся.:)
                                                                        • +3
                                                                          А мы игру пишем на Delphi и нам плевать на формошлепство и кроссплатформенность. Никаких рвотных позывов, теплота и ламповость.
                                                                      • +3
                                                                        Единственный минус Delphi, реально мешающий развиваться языку, — отсутствие бесплатных версий.

                                                                        Даже Visual Studio (давно выпуская Express) выпустила Community-версию.
                                                                        • 0
                                                                          Visual studio делайтся мелкософтом и им как владельцам ОС выгодно, чтобы под их ОС писали софт. А вот Эмбаркадеро зарабатывает на продаже лицензий к среде разработки.
                                                                          У них, кстати, есть крайне бюджетная starter. Не бесплатно, согласен, но и не 1000$ уж никак.
                                                                          • +2
                                                                            Ошибка Embarcadero в том, что они не привлекают новых разработчиков в свою среду.

                                                                            Новый, начинающий, смотрит и думает: зачем мне покупать Delphi, когда я могу бесплатно учиться в VS?
                                                                            Не взращивают новое поколение тех, кто будет покупать их продукт.

                                                                            И продают только в основном старым дельфистам.

                                                                            Embarcadero должно быть выгодно, чтобы больше писали на Delphi. Поэтому надо делать бесплатную версию для некоммерческого использования и платную для коммерческого.
                                                                            • 0
                                                                              У них, кстати, есть крайне бюджетная starter. Не бесплатно, согласен, но и не 1000$ уж никак.

                                                                              Ага. Без компиляторов для командной строки, навигации по коду, рефакторинга, нормальной отладки, баз данных, сорцов VCL, но с запретом на доход больше штуки. Это не смешно даже на фоне VS Express, что уж говорить про VS Community. Для написания мелких нативных штуковин — ну да, сойдёт. Но новые программисты туда не сунутся.
                                                                              • 0
                                                                                Так хоть бы для обучения (хотя бы для студентов) бесплатную сделали, как JetBrains.

                                                                                Раньше во времена 2006 Дельфи вроде были Турбо версии, но чего-то потом пропали.
                                                                              • 0
                                                                                Еще появился appmethod. Правда убогенький такой. И в бесплатной версии может под андроид компилировать( мы же говорим про любые бесплатные версии?).
                                                                                Вот вопрос: а можно как-нибудь для кнопочки сделать анонимный метод, что-то типа btnTest.OnClick = procedure(Sender: TObject) begin...end;?
                                                                                • 0
                                                                                  Только при условии что в декларации обработчика будет стоять не TNotiFyEvent, а reference to procedure. Но в этом случае можно будет туда и не анонимные тоже присваивать.
                                                                                  • 0
                                                                                    Учитывая, что в reference to procedure можно подсовывать как анонимный, так и обычный метод, мне не понятно, почему Embarcadero давно уже не поменяет свои legacy TNotifyEvent на reference to procedure, видимо, им тупо лень
                                                                                    • 0
                                                                                      Накладные расходы, которые в узком месте могут стать существенными. Смотрите мой комментарий: habrahabr.ru/post/243721/#comment_8137843
                                                                                      • 0
                                                                                        Ну я так понимаю, во-первых, когда в reference to подсовывают обычный метод (не анонимный), то никакой магии не происходит и все работает по-старому, а во-вторых, уж если вы подсунули туда анонимный метод, значит а) «понимаете что делаете» и б) вряд ли обработчик OnClick это узкое место. OnDraw конечно другое дело.
                                                                                        • 0
                                                                                          Нет, не работает все по старому. Просто захватывается self. Можно убедится посмотрев в ассемблер. Если вы имели ввиду VCL — то да, там евенты вряд ли узкое место.
                                                                                          Я подумал что вы предлагает вообще отказаться от механизма of object.
                                                                                          • 0
                                                                                            Просто захватывается self

                                                                                            Ну ОК, пусть и так, спасибо за разъяснение. Да, я имел в виду только в VCL, просто потому что удобно.
                                                                                            Сейчас провел тест, померил разницу в скорости вызова:
                                                                                            Скрытый текст
                                                                                            type
                                                                                              TSimple = procedure of Object;
                                                                                              TAnonymous = reference to procedure;
                                                                                            
                                                                                            type
                                                                                              TMyObj = class
                                                                                              public
                                                                                                procedure Simple;
                                                                                              end;
                                                                                            
                                                                                            function CallMeSimple(const aCallback: TSimple): Integer;
                                                                                            var
                                                                                              i: Integer;
                                                                                              t: Cardinal;
                                                                                            begin
                                                                                              t := GetTickCount;
                                                                                              Result := 1;
                                                                                              while GetTickCount-t < 1000 do
                                                                                              begin
                                                                                                aCallback;
                                                                                                inc(Result);
                                                                                              end;
                                                                                            end;
                                                                                            
                                                                                            function CallMeAnonymous(const aCallback: TAnonymous): Integer;
                                                                                            var
                                                                                              i: Integer;
                                                                                              t: Cardinal;
                                                                                            begin
                                                                                              t := GetTickCount;
                                                                                              Result := 1;
                                                                                              while GetTickCount-t < 1000 do
                                                                                              begin
                                                                                                aCallback;
                                                                                                inc(Result);
                                                                                              end;
                                                                                            end;
                                                                                            
                                                                                            procedure TForm1.Button1Click(Sender: TObject);
                                                                                            var
                                                                                              vObj: TMyObj;
                                                                                            begin
                                                                                              vObj := TMyObj.Create;
                                                                                              ShowMessage(IntToStr(CallMeSimple(vObj.Simple)));
                                                                                              ShowMessage(IntToStr(CallMeAnonymous(procedure
                                                                                              var
                                                                                                x: Integer;
                                                                                              begin
                                                                                                x := Random(1000);
                                                                                                x := x + 1;
                                                                                              end)));
                                                                                            end;
                                                                                            
                                                                                            { TMyObj }
                                                                                            
                                                                                            procedure TMyObj.Simple;
                                                                                            var
                                                                                              x: Integer;
                                                                                            begin
                                                                                              x := Random(1000);
                                                                                              x := x + 1;
                                                                                            end;
                                                                                            
                                                                                            

                                                                                            Скрытый текст
                                                                                            Итог: обычный метод успевает за секунду вызваться около 142 миллиона раз, а анонимный — около 127 миллионов раз (усреднил по нескольким запускам). Т.е. разница в среднем в 15 миллионов, что составляет чуть более 10%.
                                                                                            Я для себя делаю вывод: разницу в скорости имеет смысл учитывать, только если реально вызов срабатывает сотни миллионов раз в секунду, во всех же остальных случаях 10% это вообще ничто (по сравнению с какой-нибудь дисковой или сетевой операцией, например).
                                                                                • –1
                                                                                  Да ради одного ReSharper'a можно выбрать Visual Studio, пусть не бесплатного! А делать рефакторинг в Delphi — это просто ад. Так же как и генирить однотипный контент, шаблоны какого — то кода.
                                                                                  • 0
                                                                                    Необходимость писать однотипный код намекает на не самую удачную архитектуру.
                                                                              • +1
                                                                                Вообще забавно. Автор статьи для демонстрации силы лямбда(анонимных методов) использует стандартные функции для обработки коллекций. Собственно так делают во многих языках(C#, Java,C++). Вот только для дельфи выходит фейл. Вот как выглядит MeanAndStdDev в классическом стиле:

                                                                                procedure MeanAndStdDev;
                                                                                var
                                                                                  Data: TArray<Double>;
                                                                                  V, Sum, Mean, StdDev: Double;
                                                                                begin 
                                                                                  Data := [1, 1, 3, 5, 5];    
                                                                                
                                                                                  Sum := 0;
                                                                                  for V in Data do Sum := Sum + V;
                                                                                  Mean := Sum / Length(Data);
                                                                                
                                                                                  Sum := 0;
                                                                                  for V in Data do Sum := Sqr(V - Mean);
                                                                                  StdDev := Sqrt(Sum /  Pred(Length(Data)));
                                                                                
                                                                                  WriteLn('Mean: ', Mean, ' StdDev: ', StdDev); // => Mean: 3.0 StdDev: 2.0  
                                                                                end;
                                                                                

                                                                                ИМХО гораздо читабельнее, чем в статье. Вообще не видно, зачем тут нужны лямбды.
                                                                                А причина в чрезмерно раздутом синтаксисе. Предположим, что в дельфи был бы C#овских синтаксис лямбд:

                                                                                Mean = Data.Reduce((a, b) => a + b); // даже не нужно описывать функцию Sum
                                                                                StdDev = Sqrt(Data.Map(v => Sqrt(v - Mean)) / Pred(Length(Data)));
                                                                                


                                                                                Получились однострочники. Вот тут видно, что лямбды могут сократить код, может имеет смысл что это за зверь.
                                                                                • 0
                                                                                  опечатки
                                                                                  было: for V in Data do Sum := Sqr(V — Mean);
                                                                                  стало: for V in Data do Sum := Sum + Sqr(V — Mean);

                                                                                  было: StdDev = Sqrt(Data.Map(v => Sqrt(v — Mean)) / Pred(Length(Data)));
                                                                                  стало: StdDev = Sqrt(Data.Map(v => Sqrt(v — Mean)).Reduce((a, b) => a + b) / Pred(Length(Data)));
                                                                                  • +1
                                                                                    Вычисление Mean & StdDev приводятся для примера, а именно: использование внешней свободной функции в качестве аргумента Reduce; связь контекста вызывающего кода MeanAndStdDev и вызываемого анонимного метода Map; каскадной цепочки вызовов «Data..Map..Reduce».
                                                                                    Разумеется, всего лаконичнее смотрится вариант использования библиотечной функции Math.MeanAndStdDev ;)
                                                                                    Языки C#, JavaScript, Java имеют свой синтаксис и возможности, однако статья про Delphi, где нет подобных «стандартных функций для обработки коллекций».

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