diff --git a/05-данные-и-функции-функциональный-стиль/README.md b/05-данные-и-функции-функциональный-стиль/README.md
index aa3e3ac..97ef39c 100644
--- a/05-данные-и-функции-функциональный-стиль/README.md
+++ b/05-данные-и-функции-функциональный-стиль/README.md
@@ -1,38 +1,38 @@
# 5. Данные и функции. Функциональный стиль
- [5.1. Написание и модульное тестирование простой функции](#5-1-написание-и-модульное-тестирование-простой-функции)
-- [5.2. Соглашения о передаче аргументов и классы памяти]()
- - [5.2.1. Параметры и возвращаемые значения, переданные по ссылке (с ключевым словом ref)]()
- - [5.2.2. Входные параметры (с ключевым словом in)]()
- - [5.2.3. Выходные параметры (с ключевым словом out)]()
- - [5.2.4. Ленивые аргументы (с ключевым словом lazy)]()
- - [5.2.5. Статические данные (с ключевым словом static)]()
-- [5.3. Параметры типов]()
-- [5.4. Ограничения сигнатуры]()
-- [5.5. Перегрузка]()
- - [5.5.1. Отношение частичного порядка на множестве функций]()
- - [5.5.2. Кроссмодульная перегрузка]()
-- [5.6. Функции высокого порядка. Функциональные литералы]()
- - [5.6.1. Функциональные литералы против литералов делегатов]()
-- [5.7. Вложенные функции]()
-- [5.8. Замыкания]()
- - [5.8.1. Так, это работает... Стоп, не должно... Нет, все же работает!]()
-- [5.9. Не только массивы. Диапазоны. Псевдочлены]()
- - [5.9.1. Псевдочлены и атрибут @property]()
- - [5.9.2. Свести – но не к абсурду]()
-- [5.10. Функции с переменным числом аргументов]()
- - [5.10.1. Гомогенные функции с переменным числом аргументов]()
- - [5.10.2. Гетерогенные функции с переменным числом аргументов]()
- - [5.10.2.1. Тип без имени]()
- - [5.10.2.2. Тип данных Tuple и функция tuple]()
- - [5.10.3. Гетерогенные функции с переменным числом аргументов. Альтернативный подход]()
- - [5.10.3.1. Функции с переменным числом аргументов в стиле C]()
- - [5.10.3.2. Функции с переменным числом аргументов в стиле D]()
-- [5.11. Атрибуты функций]()
- - [5.11.1. Чистые функции]()
- - [5.11.1.1. «Чист тот, кто чисто поступает»]()
- - [5.11.2. Атрибут nothrow]()
-- [5.12. Вычисления во время компиляции]()
+- [5.2. Соглашения о передаче аргументов и классы памяти](#5-2-соглашения-о-передаче-аргументов-и-классы-памяти)
+ - [5.2.1. Параметры и возвращаемые значения, переданные по ссылке (с ключевым словом ref)](#5-2-1-параметры-и-возвращаемые-значения-переданные-по-ссылке-с-ключевым-словом-ref)
+ - [5.2.2. Входные параметры (с ключевым словом in)](#5-2-2-входные-параметры-с-ключевым-словом-in)
+ - [5.2.3. Выходные параметры (с ключевым словом out)](#5-2-3-выходные-параметры-с-ключевым-словом-out)
+ - [5.2.4. Ленивые аргументы (с ключевым словом lazy)](#5-2-4-ленивые-аргументы-с-ключевым-словом-lazy4)
+ - [5.2.5. Статические данные (с ключевым словом static)](#5-2-5-статические-данные-с-ключевым-словом-static)
+- [5.3. Параметры типов](#5-3-параметры-типов)
+- [5.4. Ограничения сигнатуры](#5-4-ограничения-сигнатуры)
+- [5.5. Перегрузка](#5-5-перегрузка)
+ - [5.5.1. Отношение частичного порядка на множестве функций](#5-5-1-отношение-частичного-порядка-на-множестве-функций)
+ - [5.5.2. Кроссмодульная перегрузка](#5-5-2-кроссмодульная-перегрузка)
+- [5.6. Функции высокого порядка. Функциональные литералы](#5-6-функции-высокого-порядка-функциональные-литералы)
+ - [5.6.1. Функциональные литералы против литералов делегатов](#5-6-1-функциональные-литералы-против-литералов-делегатов)
+- [5.7. Вложенные функции](#5-7-вложенные-функции)
+- [5.8. Замыкания](#5-8-замыкания)
+ - [5.8.1. Так, это работает... Стоп, не должно... Нет, все же работает!](#5-8-1-так-это-работает-стоп-не-должно-нет-все-же-работает)
+- [5.9. Не только массивы. Диапазоны. Псевдочлены](#5-9-не-только-массивы-диапазоны-псевдочлены)
+ - [5.9.1. Псевдочлены и атрибут @property](#5-9-1-псевдочлены-и-атрибут-property)
+ - [5.9.2. Свести – но не к абсурду](#5-9-2-свести-–-но-не-к-абсурду)
+- [5.10. Функции с переменным числом аргументов](#5-10-функции-с-переменным-числом-аргументов)
+ - [5.10.1. Гомогенные функции с переменным числом аргументов](#5-10-1-гомогенные-функции-с-переменным-числом-аргументов)
+ - [5.10.2. Гетерогенные функции с переменным числом аргументов](#5-10-2-гетерогенные-функции-с-переменным-числом-аргументов)
+ - [5.10.2.1. Тип без имени](#5-10-2-1-тип-без-имени)
+ - [5.10.2.2. Тип данных Tuple и функция tuple](#5-10-2-2-тип-данных-tuple-и-функция-tuple)
+ - [5.10.3. Гетерогенные функции с переменным числом аргументов. Альтернативный подход](#5-10-3-гетерогенные-функции-с-переменным-числом-аргументов-альтернативный-подход)
+ - [5.10.3.1. Функции с переменным числом аргументов в стиле C](#5-10-3-1-функции-с-переменным-числом-аргументов-в-стиле-c)
+ - [5.10.3.2. Функции с переменным числом аргументов в стиле D](#5-10-3-2-функции-с-переменным-числом-аргументов-в-стиле-d)
+- [5.11. Атрибуты функций](#5-11-атрибуты-функций)
+ - [5.11.1. Чистые функции](#5-11-1-чистые-функции)
+ - [5.11.1.1. «Чист тот, кто чисто поступает»](#5-11-1-1-«чист-тот-кто-чисто-поступает»)
+ - [5.11.2. Атрибут nothrow](#5-11-2-атрибут-nothrow)
+- [5.12. Вычисления во время компиляции](#5-12-вычисления-во-время-компиляции)
Обсуждать данные и функции сегодня, когда даже разговоры об объектах устарели, – это как вернуться в 1970-е. Но, к сожалению, все еще за горами день, когда говоришь компьютеру, что нужно сделать, и он сам выясняет, как это сделать. А пока этот день не настал, функции – обязательный компонент всех основных направлений программирования. По большому счету, любая программа состоит из вычислений, гоняющих данные туда-сюда; возводимые нами замысловатые строительные леса – типы, объекты, модули, фреймворки, шаблоны проектирования – только придают вычислениям нужные нам свойства, такие как модульность, изоляция ошибок или легкость сопровождения. Правильный язык программирования позволяет своему пользователю держаться золотой середины между кодом «для действия» и кодом «для существования». Идеальное соотношение зависит от множества факторов, из которых самый очевидный – размер программы: основная задача короткого скрипта – действовать, тогда как большое приложение вынуждено заниматься поддержкой неисполняемых вещей вроде интерфейсов, протоколов и модульных ограничений.
@@ -99,4 +99,2775 @@ $ rdmd --main -unittest searching.d
[В начало ⮍](#5-1-написание-и-модульное-тестирование-простой-функции) [Наверх ⮍](#5-данные-и-функции-функциональный-стиль)
+## 5.2. Соглашения о передаче аргументов и классы памяти
+
+Как уже говорилось, в функцию `find` передаются два аргумента (пер
+вый – типа `int`, а второй – толстый указатель, представляющий массив
+типа `int[]`), которые копируются в ее личные владения. Когда функция
+`find` возвращает результат, толстый указатель копируется обратно в вы
+зывающий код. В этой последовательности действий легко распознать
+явный вызов по значению. В частности, изменения аргументов не будут
+«видны» инициатору вызова после того, как управление снова перей
+дет к нему. Однако остерегаться побочного эффекта все-таки нужно:
+учитывая, что *содержимое* среза не копируется, изменения отдельных
+элементов среза *будут видны* инициатору вызова. Рассмотрим пример:
+
+```d
+void fun(int x) { x += 42; }
+void gun(int[] x) { x = [ 1, 2, 3 ]; }
+void hun(int[] x) { x[0] = x[1]; }
+
+unittest
+{
+ int x = 10;
+ fun(x);
+ assert(x == 10); // Ничего не изменилось
+ int[] y = [ 10, 20, 30 ];
+ gun(y);
+ assert(y == [ 10, 20, 30 ]); // Ничего не изменилось
+ hun(y);
+ assert(y == [ 20, 20, 30 ]); // Изменилось!
+}
+```
+
+Что же произошло? В первых двух случаях функции `fun` и `gun` изменили
+только собственные копии параметров. В частности, во втором случае
+толстый указатель был перенаправлен на другую область памяти, но
+исходный массив не был затронут. Однако в третьем случае функция
+`hun` решила изменить один элемент массива, и это изменение отразилось
+на исходном массиве. Это легко понять, представив, что срез y находит
+ся совсем не в том же месте, что и три целых числа, которыми y управ
+ляет. Так что если вы присвоите срез целиком, а-ля `x = [1, 2, 3]`, то срез,
+который раньше содержала переменная `x`, будет предоставлен самому
+себе, а `x` начнет новую жизнь; но если вы измените какой-то элемент `x[i]`
+среза `x`, то другие срезы, которым виден этот элемент (в нашем случае –
+в коде, вызвавшем `fun`), будут видеть и это изменение.
+
+### 5.2.1. Параметры и возвращаемые значения, переданные по ссылке (с ключевым словом ref)
+
+Иногда нам действительно нужно, чтобы изменения были видны в вы
+зывающем коде. В этом случае поможет класс памяти `ref`:
+
+```d
+void bump(ref int x) { ++x; }
+unittest
+{
+ int x = 1;
+ bump(x);
+ assert(x == 2);
+}
+```
+
+Если функция ожидает значение по ссылке, то она принимает только
+«настоящие данные», а не временные значения. Все, что не является
+l-значением, отвергается во время компиляции. Например:
+
+```d
+bump(5); // Ошибка! Нельзя передать r-значение по ссылке
+```
+
+Это предотвращает глупые ошибки – когда кажется, что дело сделано,
+а на самом деле вызов прошел безрезультатно.
+
+Ключевым словом `ref` можно также снабдить результат функции. В этом
+случае за ним самим будет закреплен статус l-значения. Например, из
+меним функцию `bump` так:
+
+```d
+ref int bump(ref int x) { return ++x; }
+unittest
+{
+ int x = 1;
+ bump(bump(x)); // Два увеличения на 1
+ assert(x == 3);
+}
+```
+
+Внутренний вызов функции `bump` возвращает l-значение, поэтому такой
+результат можно правомерно использовать в качестве аргумента при
+внешнем вызове той же функции. Если бы определение `bump` выглядело
+так:
+
+```d
+int bump(ref int x) { return ++x; }
+```
+
+то компилятор отверг бы вызов `bump(bump(x))` как незаконную попытку
+привязать r-значение, возвращенное при вызове `bump(x)`, параметру, пе
+редаваемому по ссылке при внешнем вызове `bump`.
+
+### 5.2.2. Входные параметры (с ключевым словом in)
+
+Параметр с ключевым словом in считается предназначенным только
+для чтения, его нельзя изменить никаким способом. Например:
+
+```d
+void fun(in int x)
+{
+ x = 42; // Ошибка! Нельзя изменить параметр с ключевым словом in
+}
+```
+
+Этот код не компилируется, то есть ключевое слово `in` накладывает на
+код достаточно строгие ограничения. Функция `fun` не может изменить
+даже собственную копию аргумента.
+
+Практически неизменяемый параметр внутри функции, конечно, мо
+жет быть полезен при анализе ее реализации, но еще более любопыт
+ный эффект наблюдается *за пределами* функции. Ключевое слово `in` за
+прещает даже косвенные изменения параметра, то есть те изменения,
+которые отражаются на объекте после того, как функция вернет управ
+ление вызвавшему ее коду. Это делает неизменяемые параметры неве
+роятно полезными, поскольку они дают гарантии инициатору вызова,
+а не только внутренней реализации функции. Например:
+
+```d
+void fun(in int[] data)
+{
+ data = new int[10]; // Ошибка! Нельзя изменить неизменяемый параметр
+ data[5] = 42; // Ошибка! Нельзя изменить неизменяемый параметр
+}
+```
+
+В первом случае ошибка неудивительна, поскольку она того же типа,
+что и приведенная выше ошибка с изменением отдельного значения
+типа `int`. Гораздо интереснее, почему возникла вторая ошибка. Неким
+магическим образом компилятор распространил действие ключевого
+слова `in` с самого массива `data` на все его ячейки – то есть `in` обладает
+«глубоким» воздействием.
+
+Ограничение, на самом деле, распространяется на любую глубину, а не
+только на один уровень. Проиллюстрируем сказанное примером с мно
+гомерным массивом:
+
+```d
+// Массив массивов чисел имеет два уровня ссылок
+void fun(in int[][] data)
+{
+ data[5] = data[0]; // Ошибка! Нельзя изменить неизменяемый параметр
+ data[5][0] = data[0][5]; // Ошибка! Нельзя изменить неизменяемый параметр
+}
+```
+
+Так что ключевое слово `in` защищает свои данные от изменений *транзитивно*, полностью сверху донизу, учитывая все возможности косвен
+ного доступа[^2]. Такое поведение не является специфичным для масси
+вов, оно распространяется на все типы данных языка D. В действитель
+ности, ключевое слово `in` в контексте параметра – это синоним квали
+фикатора типа `const`[^3], подробно описанного в главе 8.
+
+### 5.2.3. Выходные параметры (с ключевым словом out)
+
+Иногда параметры передаются по ссылке только для того, чтобы функ
+ция с их помощью что-то вернула. В таких случаях можно воспользо
+ваться классом памяти `out`, напоминающим `ref`, – разница лишь в том,
+что перед входом в функцию `out` инициализирует свой аргумент значе
+нием по умолчанию (соответствующим типу аргумента):
+
+```d
+// Вычисляет частное и остаток от деления для аргументов a и b.
+// Возвращает частное по значению, а остаток – в параметре rem.
+int divrem(int a, int b, out int rem)
+{
+ assert(b != 0);
+ rem = a % b;
+ return a / b;
+}
+
+unittest
+{
+ int r;
+ int d = divrem(5, 2, r);
+ assert(d == 2 && r == 1);
+}
+```
+
+В этом коде можно было бы с тем же успехом вместо ключевого слова `out`
+использовать `ref`, поскольку выбор `out` всего лишь извещает инициато
+ра вызова, что функция `divrem` не ожидает от параметра `rem` осмысленно
+го значения.
+
+### 5.2.4. Ленивые аргументы (с ключевым словом lazy)[^4]
+
+Порой значение одного из аргументов функции требуется лишь в ис
+ключительном случае, а в остальных вычислять его не нужно и хоте
+лось бы избежать напрасных усилий. Рассмотрим пример:
+
+```d
+bool verbose; // Флаг, контролирующий отладочное журналирование
+void log(string message)
+{
+ // Если журналирование включено, выводим строку на экран
+ if (verbose)
+ writeln(message);
+}
+...
+int result = foo(); log("foo() returned " ~ to!string(result));
+```
+
+Как видим, вычислять выражение `"foo() returned " ~ to!string(result)`
+нужно, только если переменная `verbose` имеет значение `true`. При этом
+выражение, передаваемое этой функции в качестве аргумента, будет
+вычислено в любом случае. В данном примере это конкатенация двух
+строк, которая потребует выделения памяти и копирования в нее содер
+жимого каждой из них. И все это для того, чтобы узнать, что перемен
+ная `verbose` имеет значение `false` и значение аргумента никому не нуж
+но! Можно было бы передавать вместо строки делегат, возвращающий
+строку (делегаты описаны в разделе 5.6.1):
+
+```d
+void log(string delegate() message)
+{
+ if (verbose)
+ writeln(message());
+}
+...log({return "foo() returned " ~ to!string(result);});
+```
+
+В этом случае аргумент будет вычислен, только если он действительно
+нужен, но такая форма слишком громоздка. Поэтому D вводит такое по
+нятие, как «ленивые» аргументы. Такие аргументы объявляются с ат
+рибутом `lazy`, выглядят как обычные аргументы, но вычисляются толь
+ко тогда, когда требуется их значение.
+
+```d
+void log(lazy string message)
+{
+ if (verbose)
+ writeln(message); // Значение message вычисляется здесь
+}
+```
+
+### 5.2.5. Статические данные (с ключевым словом static)
+
+Несмотря на то что ключевое слово `static` не имеет отношения к переда
+че аргументов функциям, разговор о нем здесь к месту, поскольку, как
+и `ref`, атрибут `static` данных определяет *класс памяти*, то есть несколь
+ко подробностей хранения этих данных.
+
+Любое объявление переменной может быть дополнено ключевым сло
+вом `static`. В этом случае *для каждого потока исполнения* будет создана
+собственная копия этой переменной. Рациональное обоснование и по
+следствия этого отступления от установленной языком C традиции вы
+делять единственную копию `static`-переменной для всего приложения
+обсуждаются в главе 13.
+
+Статические данные сохраняют свое значение между вызовами функ
+ций независимо от места их определения (внутри или вне функции). Вы
+бор размещения статических данных в разнообразных контекстах каса
+ется только видимости, но не хранения. На уровне модуля данные с ат
+рибутом `static` в действительности обрабатываются так же, как и дан
+ные с атрибутом `private`.
+
+```d
+static int zeros; // Практически то же самое, что и private int zeros;
+
+void fun(int x)
+{
+ static int calls;
+ ++calls;
+ if (!x) ++zeros;
+ ...
+}
+```
+
+Статические данные должны быть инициализированы константами[^5],
+вычисляемыми во время компиляции. Инициализировать статические
+данные уровня функции при первом ее вызове можно с помощью про
+стого трюка, который использует в качестве напарника статическую
+логическую переменную:
+
+```d
+void fun(double x)
+{
+ static double minInput;
+ static bool minInputInitialized;
+ if (!minInputInitialized)
+ {
+ minInput = x;
+ minInputInitialized = true;
+ }
+ else
+ {
+ if (x < minInput) minInput = x;
+ }
+ ...
+}
+```
+
+## 5.3. Параметры типов
+
+Вернемся к функции `find`, определенной в разделе 5.1, поскольку в ней
+есть немало спорных моментов. Во-первых, эта функция может быть по
+лезна только в довольно редких случаях, поэтому стоит поискать воз
+можность ее обобщения. Начнем с простого наблюдения. Присутствие
+в `find` типа `int` – это пример жесткого кодирования, простого и ясного.
+В логике кода ничего не изменится, если придется искать значения ти
+па `double` в срезах типа `double[]` или значения типа `string` в срезах типа
+`string[]`. Поэтому можно попробовать заменить тип `int` некой заглуш
+кой – параметром функции `find`, который описывал бы тип, а не значе
+ние задействованных сущностей. Чтобы воплотить эту идею, нужно
+привести наше определение к следующему виду:
+
+```d
+T[] find(T)(T[] haystack, T needle)
+{
+ while (haystack.length > 0 && haystack[0] != needle)
+ {
+ haystack = haystack[1 .. $];
+ }
+ return haystack;
+}
+```
+
+Как и ожидалось, тело функции `find` не претерпело никаких изменений,
+изменилась только сигнатура. Теперь в ней две пары круглых скобок:
+в первой перечислены параметры типов функции, а вторая содержит
+обычный список параметров, которые могут воспользоваться только что
+определенными параметрами типов. Теперь можно обрабатывать не
+только срезы элементов типа `int`, но срезы *чего угодно* (неважно, встроен
+ные это или пользовательские типы). В довершение наш предыдущий
+тест `unittest` продолжает работать, так как компилятор автоматически
+выводит тип T из типов аргументов. Чисто сработано! Но не станем почи
+вать на лаврах и добавим тест модуля, который бы подтверждал оправ
+данность этих повышенных ожиданий:
+
+```d
+unittest
+{
+ // Проверка способностей к обобщению
+ double[] d = [ 1.5, 2.4 ];
+ assert(find(d, 1.0) == null);
+ assert(find(d, 1.5) == d);
+ string[] s = [ "one", "two" ];
+ assert(find(s, "two") == [ "two" ]);
+}
+```
+
+Что же происходит, когда компилятор видит усовершенствованное опре
+деление функции `find`? Компилятор сталкивается с гораздо более слож
+ной задачей, чем в случае с аргументом типа `int[]`, потому что теперь `T`
+еще неизвестен – это может быть какой угодно тип. А разные типы запи
+сываются по-разному, передаются по-разному и щеголяют разными оп
+ределениями оператора `==`. Решить эту задачу очень важно, поскольку
+параметры типов действительно открывают новые перспективы и в ра
+зы расширяют возможности для повторного использования кода. В на
+стоящее время наиболее распространены два подхода к генерации кода
+для параметризации типов:
+
+- *Гомогенная трансляция*: все данные приводятся к общему формату,
+что позволяет скомпилировать единственную версию `find`, которая
+подойдет всем.
+- *Гетерогенная трансляция*: при каждом вызове `find` с различными
+аргументами типов (`int`, `double`, `string` и т. д.) компилятор генерирует
+отдельную версию `find` для каждого использованного типа.
+
+Гомогенная трансляция подразумевает, что язык обязан предоставить
+универсальный интерфейс доступа к данным, которым воспользуется
+`find`. А гетерогенная трансляция больше напоминает помощника, пи
+шущего по одному варианту функции `find` для каждого формата дан
+ных, который вам может встретиться, при этом все варианты он строит
+по одной заготовке. Очевидно, что у обоих этих подходов есть как пре
+имущества, так и недостатки, о чем нередко ведутся жаркие споры в раз
+ных программистских сообществах. Плюсы гомогенной трансляции –
+универсальность, простота и компактность сгенерированного кода. На
+пример, в чисто функциональных языках все представляется в виде
+списков, а во многих чисто объектно-ориентированных языках – в виде
+объектов; в обоих случаях предлагается универсальный доступ к дан
+ным. Тем не менее гомогенной трансляции свойственны такие недостат
+ки, как строгость, недостаток выразительности и неэффективность. Ге
+терогенная трансляция, напротив, отличается специализированно
+стью, выразительной мощью и скоростью сгенерированного кода. Плата
+за это – распухание готового кода, усложнение языка и неуклюжая мо
+дель компиляции (обычный упрек в адрес гетерогенных подходов – что
+они представляют собой «возвеличенный макрос» [вздох]; а поскольку
+благодаря C макрос считается чем-то нехорошим, этот ярлык придает
+гетерогенной компиляции сильный негативный оттенок).
+
+Тут стоит обратить внимание на одну деталь: гетерогенная трансляция
+включает гомогенную по той простой причине, что «один формат» вхо
+дит в «множество форматов», а «одна реализация» – в «множество реа
+лизаций». На этом основании (все прочие спорные моменты пока отло
+жим) можно утверждать, что гетерогенная трансляция мощнее гомо
+генной. При наличии средства гетерогенной трансляции ничто не ме
+шает, по крайней мере теоретически, использовать один универсальный
+формат данных и одну универсальную функцию, когда захочется. Об
+ратное, при использовании гомогенного подхода, просто невозможно.
+Тем не менее наивно было бы считать гетерогенные подходы «лучши
+ми», поскольку кроме выразительной мощи есть другие аргументы, ко
+торые также нельзя упускать из виду.
+
+D использует гетерогенную трансляцию (внимание, ожидается бомбар
+дировка техническими терминами) с поиском статически определенных
+идентификаторов и отложенной проверкой типов. Это означает, что,
+встретив определение обобщенной функции `find`, компилятор D выпол
+няет синтаксический разбор ее тела, сохраняет результаты, запоминает
+место определения функции – и больше ничего, до тех пор пока кто-ни
+будь не вызовет `find`. В этот момент компилятор извлекает разобранное
+определение `find` и пытается скомпилировать его, подставив тип, кото
+рый инициатор вызова передал взамен `T`. Если функция использует
+идентификаторы (символы), компилятор ищет их в том контексте, где
+была определена эта функция.
+
+Если компилятор не смог сгенерировать функцию `find` для этого кон
+кретного типа, генерируется сообщение об ошибке. Что на самом деле
+довольно неприятно, поскольку исключение может возникнуть из-за не
+замеченной ошибки в `find`. Зато теперь у нас есть веский повод прочесть
+следующий раздел, потому что `find` содержит две ошибки – не функцио
+нальные, а связанные с обобщенностью: теперь понятно, что функция
+`find` одновременно и излишне, и недостаточно обобщенна. Посмотрим,
+как работает этот дзэнский тезис.
+
+## 5.4. Ограничения сигнатуры
+
+Допустим, у нас есть массив с элементами типа `double`, в котором мы
+хотим найти целое число. Казалось бы, все должно пройти довольно
+гладко:
+
+```d
+double[] a = [ 1.0, 2.5, 2.0, 3.4 ];
+a = find(a, 2); // Ошибка! Не определена функция find(double[], int)
+```
+
+Вот мы и в западне. В данной ситуации функция `find` ожидает значение
+типа `T[]` в качестве первого аргумента и значение типа `T` в качестве вто
+рого. Тем не менее `find` получает значение типа `double[]` и значение типа
+`int`, то есть `T = double` и `T = int` соответственно. Если мы достаточно при
+стально вглядимся в этот код, то, конечно же, заметим, что инициатор
+вызова в действительности хотел использовать в качестве `T` тип `double`
+и собирался реализовать свою задумку, рассчитывая на аккуратное не
+явное приведение значения типа `int` к типу `double`. Тем не менее застав
+лять язык пытаться комбинаторно выполнить сразу и неявное преобра
+зование, и вывод типов – в общем случае рискованное предприятие, по
+этому D все это проделать не пытается. Раз вы сказали `T[]` и `T`, то не мо
+жете передать `double[]` и `int`.
+
+Похоже, нашей реализации функции `find` недостает обобщенности, по
+скольку она требует, чтобы типы среза и искомого значения были иден
+тичны. А на самом деле для заданного типа среза мы должны прини
+мать *любое* значение, сравнимое с элементом среза с помощью операто
+ра `==`.
+
+Один параметр типа – хорошо, а два параметра типа – лучше:
+
+```d
+T[] find(T, E)(T[] haystack, E needle)
+{
+ while (haystack.length > 0 && haystack[0] != needle)
+ {
+ haystack = haystack[1 .. $];
+ }
+ return haystack;
+}
+```
+
+Теперь функция проходит тест на ура. Но технически полученная функ
+ция `find` лжет, поскольку заявляет, что принимает абсолютно любые
+`T` и `E`, в том числе их бессмысленные сочетания! Чтобы показать, поче
+му эту неточность нужно считать проблемой, рассмотрим следующий
+вызов:
+
+```d
+assert(find([1, 2, 3], "Hello")); // Ошибка! Сравнение haystack[0] != needle некорректно для int[] и string
+```
+
+Компилятор действительно обнаруживает проблему; однако находит он
+ее в сравнении, расположенном в теле функции `find`. Это может смутить
+неосведомленного пользователя, поскольку неясно, где именно возни
+кает ошибка: в месте вызова функции `find` или в ее теле. (В частности,
+имя файла и номер строки, возвращенные в отчете компилятора, прямо
+указывают внутрь определения функции `find`.) Если источник пробле
+мы находится в конце длинной цепочки вызовов, ситуация становится
+еще более запутанной. Хотелось бы это исправить. Итак, в чем же ко
+рень всех бед? В переносном смысле, функция `find` выписывает чеки,
+которые ее тело не может обналичить.
+
+В своей сигнатуре (это часть кода до первой фигурной скобки `{`) функ
+ция `find` торжественно заявляет, что принимает срез любого типа `T`
+и значение любого типа `E`. Компилятор радостно с этим соглашается, от
+правляет в `find` бессмысленные аргументы, устанавливает типы (`T = int`
+и `E = string`) и на этом успокаивается. Но как только дело доходит до
+тела `find`, компилятор смущенно обнаруживает, что не может сгенери
+ровать осмысленный код для сравнения `haystack[0] != needle`, и выводит
+сообщение об ошибке примерно следующего содержания: «Функция
+`find` откусила больше, чем может прожевать». Тело `find` в действитель
+ности может принять только некоторые из всех возможных сочетаний
+типов `T` и `E` – те, которые можно проверять на равенство.
+
+Можно было бы реализовать какой-то страховочный механизм. Но D
+выбрал другое решение: разрешить автору `find` систематически ограни
+чивать применимость функции. Верное место для указания ограниче
+ния такого рода – сигнатура функции `find`, как раз там, где `T` и `E` появ
+ляются впервые. Для этого в D применяется *ограничение сигнатуры*
+(*signature constraint*):
+
+```d
+T[] find(T, E)(T[] haystack, E needle)
+ if (is(typeof(haystack[0] != needle) == bool))
+{
+ ... // Реализация остается той же
+}
+```
+
+Выражение `if` в сигнатуре во всеуслышание заявляет, что функция `find`
+примет параметр `haystack` типа `T[]` и параметр `needle` типа `E`, только если
+выражение `haystack[0] != needle` возвращает логический тип. У этого
+ограничения есть ряд важных последствий. Во-первых, выражение `if`
+проясняет для автора, компилятора и читателя, чего именно функция
+`find` ждет от своих параметров, избавляя всех троих от необходимости
+исследовать тело функции (обычно куда более объемное, чем у нашей).
+Во-вторых, с выражением `if` в качестве буксира функция `find` теперь
+легко отклонит вызов при попытке передать параметры, не поддающие
+ся сравнению, что, в свою очередь, позволяет гладко срабатывать дру
+гим средствам языка, таким как перегрузка функций. В-третьих, новое
+определение помогает компилятору конкретизировать свои сообщения
+об ошибках: теперь очевидно, что ошибка происходит при обращении
+к функции `find`, а не в ее теле.
+
+Заметим, что выражение, к которому применяется оператор `typeof`, ни
+когда не вычисляется во время исполнения программы; оператор лишь
+определяет тип выражения, если оно скомпилируется. (Если выражение
+с оператором `typeof` не компилируется, то это не ошибка компиляции,
+а просто сигнал, что рассматриваемое выражение не имеет никакого ти
+па, а «никакого типа» – это не `bool`.) В частности, не стоит беспокоиться
+о том, что в проверку вовлечено значение `haystack[0]`, даже если длина
+`haystack` равна нулю. И обратно: в ограничении сигнатуры запрещается
+использовать условия, не вычислимые во время компиляции програм
+мы; например, нельзя ограничить функцию `find` условием `needle > 0`.
+
+## 5.5. Перегрузка
+
+Мы определили функцию `find`, чтобы определить срез и элемент. А те
+перь напишем новую версию функции `find`, которая сообщает, можно
+ли найти один срез в другом. Обычный подход к решению этой пробле
+мы – поиск полным перебором, с двумя вложенными циклами. Такой
+алгоритм не очень эффективен: время его работы пропорционально про
+изведению длин рассматриваемых срезов. Но мы пока не будем беспоко
+иться об эффективности алгоритма, а сосредоточимся на определении
+хорошей сигнатуры для только что добавленной функции. Предыду
+щий раздел снабдил нас практически всем, что нужно. И действитель
+но, сама собой напрашивается реализация:
+
+```d
+T1[] find(T1, T2)(T1[] longer, T2[] shorter)
+ if (is(typeof(longer[0 .. 1] == shorter) : bool))
+{
+ while (longer.length >= shorter.length)
+ {
+ if (longer[0 .. shorter.length] == shorter) break;
+ longer = longer[1 .. $];
+ }
+ return longer;
+}
+```
+
+Ага! Как видите, на этот раз мы не попали в западню – не сделали функ
+цию слишком специализированной. Не самое лучшее определение вы
+глядело бы так:
+
+```d
+// Нет! Эта сигнатура слишком строгая!
+bool find(T)(T[] longer, T[] shorter)
+{
+ ...
+}
+```
+
+Оно, конечно, немного короче, но зато на порядок строже. Наша реали
+зация, не копируя данные, может сказать, содержит ли срез элементов
+типа `int` срез элементов типа `long`, а срез элементов типа `double` – срез
+элементов типа `float`. Упрощенной сигнатуре эти возможности были
+просто недоступны. Вам бы пришлось или повсюду копировать данные,
+чтобы гарантировать наличие на месте нужных типов, или вообще от
+казаться от затеи с общей функцией и выполнять поиск вручную. А что
+это за функция, если она хорошо смотрится в игрушечных примерах
+и не справляется с серьезной нагрузкой!
+
+Поскольку мы добрались до реализации, заметим уже хорошо знако
+мое сужение среза `longer` по одному элементу слева (во внешнем цикле).
+Задача внутреннего цикла – сравнение массивов `longer[0 .. shorter.length] == shorter`, где сравниваются первые `shorter.length` элементов
+среза `longer` с элементами среза `shorter`.
+
+D поддерживает перегрузку функций: несколько функций могут разде
+лять одно и то же имя, если отличаются числом аргументов или типом
+хотя бы одного из них. Во время компиляции правила языка определя
+ют, какая именно функция должна быть вызвана. Перегрузка основана
+на нашей врожденной лингвистической способности избавляться от дву
+смысленности в значении слов, используя контекст. Это средство языка
+позволяет предоставить обширную функциональность, избегая соответ
+ствующего роста количества терминов, которые должен запомнить ини
+циатор вызовов. С другой стороны, если правила выбора реализации
+функции при вызове слишком неопределенны, люди могут думать, что
+вызывают одну функцию, а на самом деле будут вызывать другую. А ес
+ли упомянутые правила, наоборот, сделать слишком жесткими, про
+граммисту придется искажать логику своего кода, объясняя компиля
+тору, какую функцию он имел в виду. D старается сохранить простоту
+правил, и в этом конкретном случае применяемое правило не является
+заумным: если вычисление ограничения сигнатуры функции (выраже
+ния `if`) возвращает `false`, функция просто удаляется из множества пере
+грузки – ее вообще перестают рассматривать как претендента на вызов.
+Для наших двух версий функции `find` соответствующие выражения `if`
+никогда не являются истинными одновременно (с одними и теми же ар
+гументами). Так что при любом вызове `find` по крайней мере один вари
+ант перегрузки себя скрывает; никогда не возникает двусмысленность,
+над которой нужно ломать голову. Итак, продолжим ход своей мысли
+с помощью теста модуля:
+
+```d
+unittest
+{
+ // Проверим, как работает новая версия функции find
+ double[] d1 = [ 6.0, 1.5, 2.25, 3 ];
+ float[] d2 = [ 1.5, 2.25 ];
+ assert(find(d1, d2) == d1[1 .. $]);
+}
+```
+
+Неважно, где расположены эти две функции `find`: в одном или разных
+файлах; между ними никогда не возникнет соревнование, поскольку
+выражения `if` в ограничениях их сигнатур никогда не являются истин
+ными одновременно. Продолжая обсуждение правил перегрузки, пред
+ставим, что мы очень много работаем с типом `int[]` и хотим определить
+для него оптимизированный вариант функции `find`:
+
+```d
+int[] find(int[] longer, int[] shorter)
+{
+ ...
+}
+```
+
+В этой записи версия функции `find` не имеет параметров типа. Кроме то
+го, вполне ясно, что между обобщенной версией `find`, которую мы опре
+делили выше, и специализированной версией для целых значений про
+исходит некое состязание. Каково относительное положение этих двух
+функций в пищевой цепи перегрузки и какой из них удастся захватить
+вызов ниже?
+
+```d
+int[] ints1 = [ 1, 2, 3, 5, 2 ];
+int[] ints2 = [ 3, 5 ];
+auto test = find(ints1, ints2); // Корректно или ошибка? Обобщенная или специализированная?
+```
+
+Подход D к решению этого вопроса очень прост: выбор всегда падает на
+более специализированную функцию. Однако в более общем случае по
+нятие «более специализированная» требует некоторого объяснения; оно
+подразумевает, что существует некоторое отношение порядка специали
+зированности, «меньше или равно» для функций. И оно существует на
+самом деле; это отношение называется *отношением частичного порядка на множестве функций* (*partial ordering of functions*).
+
+### 5.5.1. Отношение частичного порядка на множестве функций
+
+Судя по названию, без черного пояса по матан-фу с этим не разобраться,
+а между тем отношение частичного порядка – очень простое понятие.
+Считайте это распространением знакомого нам числового отношения ≤
+на другие множества, в нашем случае на множество функций. Допус
+тим, есть две функции `foo1` и `foo2`, и нужно узнать, является ли `foo1` чуть
+менее подходящей для вызова, чем `foo2` (вместо «`foo1` подходит меньше,
+чем `foo2`» будем писать `foo1` ≤ `foo2`). Если определить такое отношение, то
+у нас появится критерий, по которому можно определить, какая из
+функций выигрывает в состязании за вызов при перегрузке: при вызове
+`foo` можно будет отсортировать всех претендентов с помощью отноше
+ния ≤ и выбрать самую «большую» из найденных функцию `foo`. Чтобы
+частичный порядок работал в полную силу, это отношение должно быть
+рефлексивным (`a` ≤ `a`), антисимметричным (если `a` ≤ `b` и `b` ≤ `a`, считает
+ся, что `a` и `b` идентичны) и транзитивным (если `a` ≤ `b` и `b` ≤ `c`, то `a` ≤ `с`).
+
+D определяет отношение частичного порядка на множестве функций
+очень просто: если функция `foo1` может быть вызвана с типами парамет
+ров `foo2`, то `foo1` ≤ `foo2`. Возможны случаи, когда `foo1` ≤ `foo2` и `foo2` ≤ `foo1`
+одновременно; в таких ситуациях говорится, что функции *одинаково специализированны*. Например:
+
+```d
+// Три одинаково специализированных функции: любая из них
+// может быть вызвана с типом параметра другой
+void sqrt(real);
+void sqrt(double);
+void sqrt(float)
+```
+
+Эти функции одинаково специализированны, поскольку любая из них
+может быть вызвана как с типом `float`, так и с `double` или `real` (как ни
+странно, это разумно, несмотря на неявное преобразование с потерями,
+см. раздел 2.3.2).
+
+Также возможно, что ни одна из функций не ≤ другой; в этом случае го
+ворится, что `foo1` и `foo2` *неупорядочены*.[^6] Можно привести множество
+случаев неупорядоченности, например:
+
+```d
+// Две неупорядоченные функции: ни одна из них
+// не может быть вызвана с типом параметра другой.
+void print(double);
+void print(string);
+```
+
+Нас больше всего интересуют случаи, когда истинно ровно одно нера
+венство из пары `foo1` ≤ `foo2` и `foo2` ≤ `foo1`. Пусть истинно первое неравен
+ство, тогда говорится, что функция `foo1` менее специализированна, чем
+функция `foo2`. А именно:
+
+```d
+// Две упорядоченные функции: write(double) менее специализированна,
+// чем write(int), поскольку первая может быть вызвана с int,
+// а последняя не может быть вызвана с double.
+void write(double);
+void write(int);
+```
+
+Ввод отношения частичного порядка позволяет D принимать решение
+относительно перегруженного вызова `foo(arg1, ..., argn)` по следующему
+простому алгоритму:
+
+1. Если существует всего одно соответствие (типы и количество пара
+метров соответствуют списку аргументов), то использовать его.
+2. Сформировать множество кандидатов `{foo1, ..., fook}`, которые бы
+принимали вызов, если бы другие перегруженные версии вообще не
+существовали. Именно на этом шаге срабатывает механизм опреде
+ления типов и вычисляются условия в ограничениях сигнатур.
+3. Если полученное множество пусто, то выдать ошибку «нет соответ
+ствия».
+4. Если не все функции из сформированного множества определены
+в одном и том же модуле, то выдать ошибку «попытка кроссмодуль
+ной перегрузки».
+5. Исключить из множества претендентов на вызов все функции, менее
+специализированные по сравнению с другими функциями из этого
+множества; оставить только самые специализированные функции.
+6. Если оставшееся множество содержит больше одной функции, вы
+дать ошибку «двусмысленный вызов».
+7. Единственный элемент множества – победитель.
+
+Вот и все. Рассмотрим первый пример:
+
+```d
+void transmogrify(uint) {}
+void transmogrify(long) {}
+
+unittest
+{
+ transmogrify(42); // Вызывает transmogrify(uint)
+}
+```
+
+Здесь нет точного соответствия, можно применить любую из функций,
+поэтому на сцене появляется частичное упорядочивание. Из него следу
+ет, что, несмотря на способность обеих функций принять вызов, первая
+из них более специализированна, поэтому победа присуждается ей. (Хо
+рошо это или плохо, но `int` автоматически приводится к `uint`.) А теперь
+добавим в наш набор обобщенную функцию:
+
+```d
+// То же, что и выше, плюс ...
+void transmogrify(T)(T value) {}
+
+unittest
+{
+ transmogrify(42); // Как и раньше, вызывает transmogrify(uint)
+ transmogrify("hello"); // Вызывает transmogrify(T), T=string
+ transmogrify(1.1); // Вызывает transmogrify(T), T=double
+}
+```
+
+Что же происходит, когда функция `transmogrify(uint)` сравнивается
+с функцией `transmogrify(T)(T)` на предмет специализированности? Хотя
+было решено, что `T = int`, во время сравнения `T` не заменяется на `int`,
+обобщенность сохраняется. Может ли функция `transmogrify(uint)` при
+нять некоторый произвольный тип `T`? Нет, не может. Поэтому можно
+сделать вывод, что версия `transmogrify(T)(T)` менее специализированна,
+чем `transmogrify(uint)`, так что обобщенная функция исключается из
+множества претендентов на вызов. Итак, в общем случае предпочтение
+отдается необобщенным функциям, даже когда для их применения тре
+буется неявное приведение типов.
+
+### 5.5.2. Кроссмодульная перегрузка
+
+Четвертый шаг алгоритма из предыдущего раздела заслуживает особо
+го внимания. Вот немного измененный пример с перегруженными вер
+сиями для типов `uint` и `long` (разница лишь в том, что задействовано
+больше файлов):
+
+```d
+// В модуле calvin.d
+void transmogrify(long) { ... }
+// В модуле hobbes.d
+void transmogrify(uint) { ... }
+
+// Модуль client.d
+import calvin, hobbes;
+unittest
+{
+ transmogrify(42);
+}
+```
+
+Перегруженная версия `transmogrify(uint)` из модуля `hobbes.d` является бо
+лее специализированной; но компилятор все же отказывается вызвать
+ее, диагностируя двусмысленность. D твердо отвергает кроссмодульную
+перегрузку. Если бы такая перегрузка была разрешена, то значение вы
+зова зависело бы от взаимодействия множества включенных модулей
+(в общем случае может быть много модулей, много перегруженных вер
+сий и больше сложных вызовов, за которые будет вестись борьба). Пред
+ставьте: вы добавляете в работающий код всего одну новую команду
+`import` – и его поведение изменяется непредсказуемым образом! Кроме
+того, если разрешить кроссмодульную перегрузку, читать код явно ста
+нет на порядок труднее: чтобы выяснить, какая функция будет вызвана,
+нужно будет знать, что содержит не один модуль, а все включенные мо
+дули, поскольку в каком-то из них может быть определено лучшее соот
+ветствие. И даже хуже: если бы имел значение порядок определений на
+верхнем уровне, вызов вида `transmogrify(5)` мог бы в действительности
+завершиться вызовом различных функций в зависимости от их располо
+жения в файле. Кроссмодульная перегрузка – это неиссякаемый источ
+ник проблем, поскольку подразумевает, что при чтении фрагмента кода
+нужно постоянно держать в голове большой меняющийся контекст.
+
+Один модуль может содержать группу перегруженных версий, реали
+зующих нужную функциональность для разных типов. Второй модуль
+может вторгнуться, только чтобы добавить что-то новое к этой функ
+циональности. Однако второй модуль может определять собственную
+группу перегруженных версий. Пока функция в одном модуле не начи
+нает угонять вызовы, которые по праву должны были принадлежать
+функциям другого модуля, двусмысленность не возникает. До вызова
+функции нет возможности узнать, существует ли конфликт. Рассмот
+рим пример:
+
+```d
+// В модуле calvin.d
+void transmogrify(long) { ... }
+void transmogrify(uint) { ... }
+
+// В модуле hobbes.d
+void transmogrify(double) { ... }
+
+// В модуле susie.d
+void transmogrify(int[]) { ... }
+void transmogrify(string) { ... }
+
+// Модуль client.d
+import calvin, hobbes, susie;
+
+unittest
+{
+ transmogrify(5); // Ошибка! кроссмодульная перегрузка, затрагивающая модули calvin и hobbes.
+ calvin.transmogrify(5); // Все в порядке, точное требование, вызвана calvin.transmogrify(uint)
+ transmogrify(5.5); // Все в порядке, только hobbes может принять этот вызов.
+ transmogrify("привет"); // Привет от Сьюзи
+}
+```
+
+Кельвин, Хоббс и Сьюзи взаимодействуют интересными способами. Об
+ратите внимание, насколько тонки различия между двусмысленностя
+ми в примере; первый вызов порождает конфликт между модулями
+`calvin.d` и `hobbes.d`, но это совершенно не значит, что эти модули взаимно
+несовместимы: третий вызов проходит гладко, поскольку ни одна функ
+ция в других модулях не в состоянии обслужить его. Наконец, модуль
+`susie.d` определяет собственные перегруженные версии и никогда не
+конфликтует с остальными двумя модулями (в отличие от одноимен
+ных персонажей комикса[^7]).
+
+**Управление перегрузкой**
+
+Где бы вы ни встретили двусмысленность из-за кроссмодульной пере
+грузки, вы всегда можете указать направление перегрузки одним из
+двух основных способов. Первый – уточнить свою мысль, снабдив имя
+функции именем модуля, как это показано на примере второго вызова
+`calvin.transmogrify(5)`. Поступив так, вы ограничите область поиска функ
+ции единственным модулем `calvin.d`. Внутри этого модуля также дейст
+вуют правила перегрузки. Более очевидный способ – назначить про
+блемному идентификатору *локальный псевдоним*. Например:
+
+```d
+// Внутри calvin.d
+import hobbes;
+alias hobbes.transmogrify transmogrify;
+```
+
+Эта директива делает нечто весьма интересное: она свозит все перегру
+женные версии `transmogrify` из модуля `hobbes.d` в модуль `calvin.d`. Так
+что если модуль `calvin.d` содержит упомянутую директиву, то можно
+считать, что, помимо собственных перегруженных версий, он опреде
+ляет все перегруженные версии, которые определял `hobbes.d`. Это очень
+мило со стороны модуля `calvin.d`: он демократично советуется с модулем
+`hobbes.d` всякий раз, когда нужно принять решение, какая версия `transmogrify` должна быть вызвана. Иначе, если бы модулям `calvin.d` и `hobbes.d`
+не повезло и они решили бы игнорировать существование друг друга,
+модуль `client.d` все равно мог бы вызвать `transmogrify`, назначив псевдо
+нимы обеим перегруженным версиям (и `calvin.transmogrify`, и `hobbes.transmogrify`).
+
+```d
+// Внутри client.d
+alias calvin.transmogrify transmogrify;
+alias hobbes.transmogrify transmogrify;
+```
+
+Теперь при любом вызове `transmogrify` из модуля `client.d` решение о перегрузке будет приниматься так, будто перегруженные версии `transmogrify`, определенные в модулях `calvin.d` и `hobbes.d`, присутствуют в мо
+дуле `client.d`.
+
+## 5.6. Функции высокого порядка. Функциональные литералы
+
+Мы уже знаем, как найти элемент или срез в другом срезе. Однако под
+поиском не всегда подразумевается просто поиск заданного значения.
+Задача может быть сформулирована и так: «Найти в массиве чисел пер
+вый отрицательный элемент». Несмотря на все свое могущество, наша
+библиотека поиска не в состоянии выполнить это задание.
+
+Основная идея функции `find` в том, что она ищет значение, удовлетво
+ряющее некоторому логическому условию, или предикату; до сих пор
+в роли предиката всегда выступало сравнение на равенство (оператор `==`).
+Однако более гибкая функция `find` может принимать предикат от поль
+зователя и выстраивать логику линейного поиска вокруг него. Если уда
+стся наделить функцию `find` такой мощью, она превратится в *функцию высокого порядка*, то есть функцию, которая может принимать другие
+функции в качестве аргументов. Это очень мощный подход к решению
+задач, поскольку объединяя собственную функциональность и функ
+циональность, предоставляемую ее аргументами, функция высокого
+порядка достигает гибкости поведения, недоступной простым функци
+ям. Чтобы заставить функцию `find` принимать предикат, воспользуем
+ся *параметром-псевдонимом*.
+
+```d
+T[] find(alias pred, T)(T[] input)
+ if (is(typeof(pred(input[0])) == bool))
+{
+ for (; input.length > 0; input = input[1 .. $])
+ {
+ if (pred(input[0])) break;
+ }
+ return input;
+}
+```
+
+Эта новая перегруженная версия функции `find` принимает не только
+«классический» параметр, но и загадочный параметр-псевдоним `alias pred`. Параметру-псевдониму можно поставить в соответствие любой ар
+гумент: значение, тип, имя функции – все, что можно выразить знака
+ми. А теперь посмотрим, как вызывать эту новую перегруженную вер
+сию функции `find`.
+
+```d
+unittest
+{
+ int[] a = [ 1, 2, 3, 4, -5, 3, -4 ]; // Найти первое отрицательное число
+ auto b = find!(function bool(int x) { return x < 0; })(a);
+}
+```
+
+На этот раз функция `find` принимает два списка аргументов. Первый
+список отличается синтаксисом `!(...)` и содержит обобщенные аргумен
+ты. Второй список содержит классические аргументы. Обратите внима
+ние: несмотря на то что функция `find` объявляет два обобщенных пара
+метра (`alias pred` и `T`), вызывающий ее код указывает только один аргу
+мент. Вызов имеет такой вид, поскольку никто не отменял работу меха
+низма определения типов: по контексту автоматически определяется,
+что `T = int`. До этого момента при наших вызовах `find` никогда не возни
+кало необходимости указывать какие-либо обобщенные аргументы: ком
+пилятор определял их за нас. Однако на этот раз автоматически опреде
+лить `pred` невозможно, поэтому мы указали его в виде функционального
+литерала. Функциональный литерал – это запись
+
+```d
+function bool(int x) { return x < 0; }
+```
+
+где `function` – ключевое слово, а все остальное – обычное определение
+функции, только без имени.
+
+Функциональные литералы (также известные как анонимные функ
+ции, или лямбда-функции) очень полезны во множестве ситуаций, одна
+ко их синтаксис сложноват. Длина литерала в наше примере – 41 знак,
+но только около 5 знаков занимаются настоящим делом. Чтобы решить
+эту проблему, D позволяет серьезно урезать синтаксис. Первое сокраще
+ние – это уничтожение возвращаемого типа и типов параметров: компи
+лятор достаточно умен, чтобы определить их все, поскольку тело ано
+нимной функции всегда под рукой.
+
+```d
+auto b = find!(function(x) { return x < 0; })(a);
+```
+
+Второе сокращение – изъятие собственно ключевого слова `function`. Мож
+но применять оба сокращения одновременно, как это сделано здесь (по
+лучается очень сжатая форма записи):
+
+```d
+auto b = find!((x) { return x < 0; })(a);
+```
+
+Эта запись абсолютно понятна для посвященных, в круг которых вы во
+шли пару секунд назад.
+
+### 5.6.1. Функциональные литералы против литералов делегатов
+
+Важное требование к механизму лямбда-функций: он должен разре
+шать доступ к контексту, в котором была определена лямбда-функция.
+Рассмотрим слегка измененный вариант:
+
+```d
+unittest
+{
+ int[] a = [ 1, 2, 3, 4, -5, 3, -4 ];
+ int z = -2;
+ // Найти первое число меньше z
+ auto b = find!((x) { return x < z; })(a);
+ assert(b == a[4 .. $]);
+}
+```
+
+Этот видоизмененный пример работает, что уже о многом говорит. Но
+если, просто ради эксперимента, вставить перед функциональным ли
+тералом ключевое слово, код загадочным образом перестает работать!
+
+```d
+auto b = find!(function(x) { return x < z; })(a); // Ошибка! Функция не может получить доступ к кадру стека вызывающей функции!
+```
+
+Что же происходит и что это за жалоба о кадре стека? Очевидно, должен
+быть какой-то внутренний механизм, с помощью которого функцио
+нальный литерал получает доступ к переменной `z` – он не может чудом
+добыть ее расположение из воздуха. Этот механизм закодирован в виде
+скрытого параметра – *указателя на кадр стека*, принимаемого литера
+лом. Компилятор использует указатель на кадр стека, чтобы осуществ
+лять доступ к внешним переменным, таким как `z`. Тем не менее функ
+циональному литералу, который *не* использует никаких локальных
+переменных, не требуется дополнительный параметр. Будучи статиче
+ски типизированным языком, D должен различать эти случаи, и он
+действительно различает их. Кроме функциональных литералов есть
+еще литералы делегатов, которые создаются так:
+
+```d
+unittest
+{
+ int z = 3;
+ auto b = find!(delegate(x) { return x < z; })(a); // OK
+}
+```
+
+В отличие от функций, делегаты имеют доступ к включающему их фрей
+му. Если в литерале нет ключевых слов `function` и `delegate`, компилятор
+автоматически определяет, какое из них подразумевалось. И снова на
+помощь приходит механизм определения типов по контексту, позволяя
+самому сжатому, самому удобному коду еще и автоматически делать то,
+что нужно.
+
+```d
+auto f = (int i) {};
+assert(is(f == function));
+```
+
+## 5.7. Вложенные функции
+
+Теперь можно вызывать функцию `find` с произвольным функциональ
+ным литералом, что довольно изящно. Но если литерал сильно разрас
+тается или появляется желание использовать его несколько раз, стано
+вится неудобно писать тело функции в месте ее вызова (предположи
+тельно несколько раз). Хотелось бы вызывать `find` с именованной функ
+цией (а не анонимной); кроме того, желательно сохранить право доступа
+к локальным переменным на случай, если понадобится к ним обратить
+ся. Для этой и многих других задач D предоставляет такое средство,
+как вложенные функции.
+
+Определение вложенной функции выглядит точно так же, как опреде
+ление обычной функции, за исключением того, что вложенная функ
+ция объявляется внутри другой функции. Например:
+
+```d
+void transmogrify(int[] input, int z)
+{
+ // Вложенная функция
+ bool isTransmogrifiable(int x)
+ {
+ if (x == 42)
+ {
+ throw new Exception("42 нельзя трансмогрифировать");
+ }
+ return x < z;
+ }
+ // Найти первый изменяемый элемент в массиве input
+ input = find!(isTransmogrifiable)(input);
+ ...
+ // ...и снова
+ input = find!(isTransmogrifiable)(input);
+ ...
+}
+```
+
+Вложенные функции могут быть очень полезны во многих ситуациях.
+Не делая ничего свыше того, что может сделать обычная функции, вло
+женная функция повышает удобство и модульность, поскольку распо
+ложена прямо внутри функции, которая ее использует, и имеет доступ
+к ее контексту. Последнее преимущество особенно важно; если бы в рас
+смотренном примере нельзя было воспользоваться вложенностью, по
+лучить доступ к `z` было бы гораздо сложнее.
+
+Применив тот же трюк, что и функциональный литерал (скрытый пара
+метр), вложенная функция `isTransmogrifiable` получает доступ к фрейму
+стека своего родителя, в частности к переменной `z`. Иногда может пона
+добиться заведомо избежать таких обращений к родительскому фрейму,
+превратив `isTransmogrifiable` в самую обычную функцию, за исключени
+ем места ее определения (внутри `transmogrify`). Для этого просто добавь
+те перед определением `isTransmogrifiable` ключевое слово `static` (а какое
+еще?):
+
+```d
+void transmogrify(int[] input, int z)
+{
+ static int w = 42;
+ // Вложенная обычная функция
+ static bool isTransmogrifiable(int x)
+ {
+ if (x == 42)
+ {
+ throw new Exception("42 нельзя трансмогрифировать ");
+ }
+ return x < w; // Попытка обратиться к z вызвала бы ошибку
+ }
+ ...
+}
+```
+
+Теперь, с ключевым словом `static` в качестве буксира, функции `isTransmogrifiable` доступны лишь данные, определенные на уровне модуля,
+и данные внутри `transmogrify`, также помеченные ключевым словом
+`static` (как показано на примере переменной `w`). Любые данные, которые
+могут изменяться от вызова к вызову, такие как параметры функций
+или нестатические переменные, недоступны (но, разумеется, могут быть
+переданы явно).
+
+## 5.8. Замыкания
+
+Как уже говорилось, `alias` – это чисто символическое средство; все, что
+оно делает, – придает одному идентификатору значение другого. В на
+шем предыдущем примере `pred` – это не настоящее значение, так же как
+и имя функции – это не значение; `pred` нельзя ничего присвоить. Если
+требуется создать массив функций (например, последовательность ко
+манд), ключевое слово `alias` не поможет. Здесь определенно нужно что-
+то еще, и это не что иное, как возможность иметь осязаемый объект
+функции, который можно записывать и считывать, сильно напоминаю
+щий указатель на функцию в C.
+
+Рассмотрим, например, такую непростую задачу: «Получив значение `x`
+типа `T`, возвратить функцию, которая находит первое значение, равное `x`,
+в массиве элементов типа `T`». Подобное химически чистое, косвенное оп
+ределение типично для функций высокого порядка: вы ничего *не делаете* сами, а только возвращаете то, что должно быть сделано. То есть нуж
+но написать функцию, которая (внимание) возвращает другую функ
+цию, которая, в свою очередь, принимает параметр типа `T[]` и возвраща
+ет значение типа `T[]`. Итак, возвращаемый тип функции, которую мы
+собираемся написать, – `T[] delegate(T[])`. Почему `delegate`, а не `function`?
+Как отмечалось выше, вдобавок к своим аргументам делегат получает
+доступ еще и к состоянию, в котором он определен, а функция – только
+к аргументам. А наша функция как раз должна обладать некоторым со
+стоянием, поскольку необходимо как-то сохранять значение `x`.
+
+Это очень важный момент, поэтому его следует подчеркнуть. Представь
+те, что тип `T[] function(T[])` – это просто адрес функции (одно машинное
+слово). Эта функция обладает доступом только к своим параметрам
+и глобальным переменным программы. Если передать двум указателям
+на одну и ту же функцию одни и те же аргументы, они получат доступ
+к одному и тому же состоянию программы. Любой, кто пробовал рабо
+тать с обратными вызовами (callbacks) C – например, для оконных сис
+тем или запуска потоков, – знаком с вечной проблемой: указатели на
+функции не имеют доступа к собственному локальному состоянию.
+Способ, который обычно применяется в C для того, чтобы обойти эту
+проблему, – использование параметра типа `void*` (нетипизированный
+адрес), через который и передается информация о состоянии. Другие
+системы обратных вызовов, вроде старой капризной библиотеки MFC,
+сохраняют дополнительное состояние в глобальном ассоциативном мас
+сиве, третьи, такие как Active Template Library (ATL), динамически
+создают новые функции с помощью ассемблера. Везде, где необходимо
+взаимодействовать с обратными вызовами C, применяются некоторые
+решения, позволяющие обратным вызовам получать доступ к локаль
+ным состояниям; это далеко не простая задача.
+
+С ключевым словом `delegate` все эти проблемы испаряются. Делегаты
+достигают этого ценой своего размера: делегат хранит указатель на
+функцию и указатель на окружение этой функции. Хотя это и больше
+по весу и порой медленнее, но в то же время и значительно мощнее. Так
+что в собственных разработках гораздо предпочтительнее использовать
+делегаты, а не функции. (Конечно же, функция вида `function` незамени
+ма при взаимодействии с C через обратные вызовы.)
+
+Теперь, когда уже так много сказано, попробуем написать новую функ
+цию – `finder`. Не забудем, что вернуть нужно `T[] delegate(T[])`.
+
+```d
+import std.algorithm;
+
+T[] delegate(T[]) finder(T)(T x)
+ if (is(typeof(x == x) == bool))
+{
+ return delegate(T[] a) { return find(a, x); };
+}
+
+unittest
+{
+ auto d = finder(5);
+ assert(d([1, 3, 5, 7, 9]) == [ 5, 7, 9 ]);
+ d = finder(10);
+ assert(d([1, 3, 5, 7, 9]) == []);
+}
+```
+
+Трудно не согласиться, что такие вещи, как две команды `return` в одной
+строке, для непосвященных всегда будут выглядеть странновато. Что ж,
+при первом знакомстве причудливой наверняка покажется не только
+эта функция высокого порядка. Так что начнем разбирать функцию
+`finder` построчно: она параметризирована с помощью типа `T`, принимает
+обычный параметр типа `T` и возвращает значение типа `T[] delegate(T[])`;
+кроме того, на `T` налагается ограничение: два значения типа `T` должны
+быть сравнимы, а результат сравнения должен быть логическим. (Как
+и раньше, «глупое» сравнение `x == x` здесь только ради типов, а не для
+каких-то определенных значений.) Затем `finder` разумно делает свое де
+ло, возвращая литерал делегата. У этого литерала короткое тело, в ко
+тором вызывается наша ранее определенная функция `find`, завершаю
+щая выполнение условий поставленной задачи. Возвращенный делегат
+называется *замыканием* (*closure*).
+
+Порядок использования функции `finder` ожидаем: ее вызов возвращает
+делегат, который потом можно вызвать и которому можно присваивать
+новые значения. Переменная `d`, определенная в тесте модуля, имеет тип
+`T[] delegate(T[])`, но благодаря ключевому слову `auto` этот тип можно не
+указывать явно. На самом деле, если быть абсолютно честным, с помо
+щью ключевого слова `auto` можно сократить и определение `finder`; все
+типы присутствовали в нем лишь для облегчения понимания примера.
+Вот гораздо более краткое определение функции `finder`:
+
+```d
+auto finder(T)(T x) if (is(typeof(x == x) == bool))
+{
+ return (T[] a) { return find(a, x); };
+}
+```
+
+Обратите внимание на использование ключевого слова `auto` вместо воз
+вращаемого типа функции, а также на то, что ключевое слово `delegate`
+опущено; компилятор с радостью позаботится обо всем этом за нас. Тем
+не менее в литерале делегата запись `T[]` указать необходимо. Ведь ком
+пилятор должен за что-то зацепиться, чтобы сотворить волшебство, обе
+щанное ключевым словом `auto`: возвращаемый тип делегата определя
+ется по типу функции `find(a, x)`, который, в свою очередь, определяется
+по типам `a` и `x`; в результате такой цепочки выводов делегат приобретает
+тип `T[] delegate(T[])`, этот же тип возвращает функция `finder`. Без зна-
+ния типа `a` вся эта цепочка рассуждений не может быть осуществима.
+
+### 5.8.1. Так, это работает... Стоп, не должно... Нет, все же работает!
+
+Наш тест модуля `unittest` помогает исследовать поведение функции
+`finder`, но, конечно же, не доказывает корректность ее работы. Важный
+и совсем неочевидный вопрос: возвращаемый функцией `finder` делегат
+использует значение `x`, а где находится `x` после того, как `finder` вернет
+управление? На самом деле, в этом вопросе слышится серьезное опасе
+ние за происходящее (ведь D использует для вызова функций обычный
+стек вызовов): инициатор вызова вызывает функцию `finder`, х отправля
+ется на вершину стека вызовов, функция `finder` возвращает результат,
+стек восстанавливает свое состояние до вызова `finder`... а значит, возвра
+щенный функцией `finder` делегат использует для доступа адрес в стеке,
+по которому уже нет нужного значения!
+
+«Продолжительность жизни» локального окружения (в нашем случае
+окружение состоит только из x, но оно может быть сколь угодно боль
+шим) – это классическая проблема реализации замыканий, и каждый
+язык, поддерживающий замыкания, должен ее как-то решать. В язы
+ке D применяется следующий подход[^8]. В общем случае все вызовы ис
+пользуют обычный стек. А обнаружив замыкание, компилятор автома
+тически копирует используемый контекст в кучу и устанавливает связь
+между делегатом и областью памяти в куче, позволяя ему использовать
+расположенные в ней данные. Выделенная в куче память подлежит сбо
+ру мусора.
+
+Недостаток такого подхода в том, что каждый вызов `finder` порождает
+новое требование выделить память. Тем не менее замыкания очень вы
+разительны и позволяют применить многие интересные парадигмы
+программирования, поэтому в большинстве случаев затраты более чем
+оправданны.
+
+## 5.9. Не только массивы. Диапазоны. Псевдочлены
+
+Раздел 5.3 закончился загадочным утверждением: «функция `find` одно
+временно и излишне, и недостаточно обобщенна». Затем мы узнали, по
+чему функция `find` излишне обобщенна, и исправили эту ошибку, нало
+жив дополнительные ограничения на типы ее параметров. Пришло вре
+мя выяснить, почему эта функция все же недостаточно обобщенна.
+
+В чем смысл линейного поиска? В поисках заданного значения или зна
+чения, удовлетворяющего заданному условию, просматриваются эле
+менты указанной структуры данных. Проблема в том, что до сих пор мы
+работали только с непрерывными массивами (срезами, встречающимися
+в нашем определении `find` в виде `T[]`), но к понятию линейного поиска не
+прерывность не имеет никакого отношения. (Она имеет отношение толь
+ко к механизмам организации просмотра.) Ограничившись типом `T[]`,
+мы лишили функцию `find` доступа ко множеству других структур дан
+ных, с которыми может работать алгоритм линейного поиска. Язык,
+предлагающий, к примеру, сделать `find` методом некоторого типа `Array`
+(«массив»), вполне заслуживает вашего скептического взгляда. Это не
+значит, что решить задачу с помощью этого языка невозможно; просто
+наверняка поработать пришлось бы гораздо больше, чем это необходимо.
+
+Пора начать все с нуля, пересмотрев нашу базовую реализацию `find`.
+Для удобства приведем ее здесь:
+
+```d
+T[] find(T)(T[] haystack, T needle)
+{
+ while (haystack.length > 0 && haystack[0] != needle)
+ {
+ haystack = haystack[1 .. $];
+ }
+ return haystack;
+}
+```
+
+Какие основные операции мы применяем к массиву `haystack` и что озна
+чает каждая из них?
+
+1. `haystack.length > 0` сообщает, остались ли еще элементы в `haystack`.
+2. `haystack[0]` осуществляет доступ к первому элементу `haystack`.
+3. `haystack = haystack[1 .. $]` исключает из рассмотрения первый эле
+мент `haystack`.
+
+Конкретный способ, каким массивы реализуют эти операции, непросто
+распространить на другие контейнеры. Например, проверять с помо
+щью выражения `haystack.length > 0`, есть ли в односвязном списке эле
+менты, – подход, достойный премии Дарвина[^9]. Если не обеспечено по
+стоянное кэширование длины списка (что по многим причинам весьма
+проблематично), то для вычисления длины списка таким способом по
+требуется время, пропорциональное самой длине списка, а быстрое об
+ращение к началу списка занимает всего лишь несколько машинных
+инструкций. Применить к спискам индексацию – столь же проигрыш
+ная идея. Так что выделим сущность рассмотренных операций, пред
+ставим полученный результат в виде трех именованных функций и ос
+тавим их реализацию типу `haystack`. Примерный синтаксис базовых опе
+раций, необходимых для реализации алгоритма линейного поиска:
+
+1. `haystack.empty` – для проверки `haystack` на пустоту.
+2. `haystack.front` – для получения первого элемента `haystack`.
+3. `haystack.popFront()` – для исключения из рассмотрения первого эле
+мента `haystack`.
+
+Обратите внимание: первые две операции не изменяют `haystack` и потому
+не используют круглые скобки, третья же операция изменяет `haystack`,
+и синтаксически это отражено в виде скобок `()`. Переопределим функ
+цию `find`, применив в ее определении новый блестящий синтаксис:
+
+```d
+R find(R, T)(R haystack, T needle)
+ if (is(typeof(haystack.front != needle) == bool))
+{
+ while (!haystack.empty && haystack.front != needle)
+ {
+ haystack.popFront();
+ }
+ return haystack;
+}
+```
+
+Было бы неплохо сейчас погреться в лучах этого благотворного опреде
+ления, если бы не суровая реальность: тесты модулей не проходят. Да
+и могло ли быть иначе, когда встроенный тип среза `T[`] и понятия не
+имеет о том, что нас внезапно осенило и мы решили определить новое
+множество базовых операций с произвольными именами `empty`, `front`
+и `popFront`. Мы должны определить их для всех типов `T[]`. Естественно,
+все они будут иметь простейшую реализацию, но они все равно нам
+нужны, чтобы заставить нашу милую абстракцию снова заработать
+с тем типом данных, с которого мы начали.
+
+### 5.9.1. Псевдочлены и атрибут @property
+
+Наша синтаксическая проблема заключается в том, что все вызовы
+функций до сих пор выглядели как `функция(аргумент)`, а теперь мы хотим
+определить такие вызовы: `аргумент.функция()` и `аргумент.функция`, то есть
+*вызов метода* и *обращение к свойству* соответственно. Как мы узнаем
+из следующего раздела, для пользовательских типов они определяются
+довольно-таки просто, но `T[]` – это встроенный тип. Как же быть?
+
+Язык D видит в этом чисто синтаксическую проблему и разрешает ее
+посредством нотации псевдочленов: если компилятор встретит запись
+`a.функция(b, c, d)`, где `функция` не является членом типа значения a, он за
+менит этот вызов на `функция(a, b, c, d)`[^10] и попытается обработать вызов
+в этой новой форме. (При этом попытки обратного преобразования не
+предпринимаются: если вы напишете `функция(a, b, c, d)` и это окажется
+бессмыслицей, версия `a.функция(b, c, d)` не проверяется.) Предназначе
+ние псевдометодов – позволить вызывать обычные функции с помощью
+знакомого кому-то из нас синтаксиса «отправить-сообщение-объекту».
+Итак, без лишних слов реализуем `empty`, `front` и `popFront` для встроенных
+массивов. Для этого хватит трех строк:
+
+```d
+@property bool empty(T)(T[] a) { return a.length == 0; }
+@property ref T front(T)(T[] a) { return a[0]; }
+void popFront(T)(ref T[] a) { a = a[1 .. $]; }
+```
+
+С помощью ключевого слова `@property` объявляется *атрибут*, называе
+мый *свойством* (*property*). Атрибут всегда начинается со знака `@` и про
+сто свидетельствует о том, что у определяемого символа есть определен
+ные качества. Одни атрибуты распознаются компилятором, другие оп
+ределяет и использует только сам программист[^11]. В частности, атрибут
+«property» распознается компилятором и сигнализирует о том, что функ
+ция, обладающая этим атрибутом, вызывается без `()` после ее имени.[^12]
+
+Также обратите внимание на использование в двух местах ключевого
+слова `ref` (см. раздел 5.2.1). Во-первых, оно употребляется при определе
+нии возвращаемого типа `front`; смысл в том, чтобы позволить вам изме
+нять элементы массива, если вы того пожелаете. Во вторых, `ref` исполь
+зует функция `popFront`, чтобы гарантировать непосредственное измене
+ние среза.
+
+Благодаря этим трем простым определениям модифицированная функ
+ция `find` компилируется и запускается без проблем, что доставляет
+огромное удовлетворение; мы обобщили функцию `find` так, что теперь
+она будет работать с любым типом, для которого определены функции
+`empty`, `front` и `popFront`, а затем завершили круг, применив обобщенную
+версию функции для решения той задачи, которая и послужила толч
+ком к обобщению. Если три базовые функции для работы с `T` будут под
+вергнуты *инлайнингу* (*inlining*)[^13], обобщенная версия `find` останется та
+кой же эффективной, как и ее предыдущая ущербная реализация, ра
+ботающая только со срезами.
+
+Если бы функции `empty`, `front` и `popFront` были полезны исключительно
+в определении функции `find`, то полученная абстракция оказалась бы
+не особенно впечатляющей. Ладно, нам удалось применить ее к `find`, но
+пригодится ли тройка `empty-front-popFront`, когда мы задумаем опреде
+лить другую функцию, или придется начинать все с нуля и писать дру
+гие примитивы? К счастью, обширный опыт показывает, что в понятии
+обобщенного доступа к коллекции данных определенно есть нечто фун
+даментальное. Это понятие настолько полезно, что было увековечено
+в виде паттерна «Итератор» в знаменитой книге «Паттерны проектиро
+вания»; библиотека C++ STL усовершенствовала это понятие,
+определив концептуальную иерархию итераторов: итератор ввода, од
+нонаправленный итератор, двунаправленный итератор, итератор про
+извольного доступа.
+
+В терминах языка D абстрактный тип данных, позволяющий переме
+щаться по коллекции элементов, – это *диапазон* (*range*). (Название
+«итератор» тоже подошло бы, но этот термин уже приобрел определен
+ное значение в контексте ранее созданных библиотек, поэтому его ис
+пользование могло бы вызвать путаницу.) У диапазонов D больше сход
+ства с шаблоном «Итератор», чем с итераторами библиотеки STL (диапа
+зон D можно грубо смоделировать с помощью пары итераторов из STL);
+тем не менее диапазоны D наследуют разбивку по категориям, опреде
+ленную для итераторов STL. В частности, тройка `empty-front-popFront`
+определяет *диапазон ввода* (*input range*); в результате поиск хорошей
+реализации функции `find` привел нас к открытию сложного отношения
+между линейным поиском и диапазонами ввода: нельзя реализовать
+линейный поиск в структуре данных с меньшей функциональностью,
+чем у диапазона ввода, но было бы ошибкой вдруг потребовать от вашей
+коллекции большей функциональности, чем у диапазона ввода (напри
+мер, не стоит требовать массивов с индексированным доступом к эле
+ментам). Практически идентичную реализацию функции `find` можно
+найти в модуле `std.algorithm` стандартной библиотеки.
+
+### 5.9.2. Свести – но не к абсурду
+
+Как насчет непростой задачи, использующей только диапазоны ввода?
+Условия звучат так: определить функцию `reduce`[^14], которая принимает
+диапазон ввода `r`, операцию `fun` и начальное значение `x`, последовательно
+рассчитывает `x = fun(x, e)` для каждого элемента `e` из `r` и возвращает `x`.
+Функция высокого порядка `reduce` весьма могущественна, поскольку
+позволяет выразить множество интересных сверток. Эта функция –
+одно из основных средств многих языков программирования, позволя
+ющих создавать функции более высокого порядка. В них она носит
+имена `accumulate`, `compress`, `inject`, `foldl` и т. д. Разработку функции
+`reduce` начнем с определения нескольких тестов модулей – в духе разра
+ботки через тестирование:
+
+```d
+unittest
+{
+ int[] r = [ 10, 14, 3, 5, 23 ];
+ // Вычислить сумму всех элементов
+ int sum = reduce!((a, b) { return a + b; })(0, r);
+ assert(sum == 55);
+ // Вычислить минимум
+ int min = reduce!((a, b) { return a < b ? a : b; })(r[0], r);
+ assert(min == 3);
+}
+```
+
+Как можно заметить, функция `reduce` очень гибка и полезна – конечно,
+если закрыть глаза на маленький нюанс: эта функция еще не существу
+ет. Поставим цель реализовать `reduce` так, чтобы она работала в соответ
+ствии с определенными выше тестами. Теперь мы знаем достаточно,
+чтобы с самого начала написать крепкий, «промышленный» вариант
+функции `reduce`: в разделе 5.3 показано, как передать в функцию аргу
+менты; раздел 5.4 научил нас накладывать на `reduce` ограничения, что
+бы она принимала только осмысленные аргументы; в разделе 5.6 мы
+видели, как можно передать в функцию функциональные литералы че
+рез параметры-псевдонимы; а сейчас мы вплотную подошли к созда
+нию элегантного и простого интерфейса диапазона ввода.
+
+```d
+V reduce(alias fun, V, R)(V x, R range)
+ if (is(typeof(x = fun(x, range.front)))
+ && is(typeof(range.empty) == bool)
+ && is(typeof(range.popFront())))
+{
+ for (; !range.empty; range.popFront())
+ {
+ x = fun(x, range.front);
+ }
+ return x;
+}
+```
+
+Скомпилируйте, запустите тесты модулей, и вы увидите, что все про
+верки пройдут прекрасно. И все же гораздо симпатичнее было бы опре
+деление `reduce`, где ограничения сигнатуры не достигали бы объема са
+мой реализации. Кроме того, стоит ли писать нудные проверки, чтобы
+удостовериться, что `R` – это *диапазон ввода*? Столь многословные огра
+ничения – это скрытое дублирование. К счастью, проверки для диапа
+зонов уже тщательно собраны в стандартном модуле `std.range`, восполь
+зовавшись которым, можно упростить реализацию `reduce`:
+
+```d
+import std.range;
+
+V reduce(alias fun, V, R)(V x, R range)
+ if (isInputRange!R && is(typeof(x = fun(x, range.front))))
+{
+ for (; !range.empty; range.popFront())
+ {
+ x = fun(x, range.front);
+ }
+ return x;
+}
+```
+
+Такой вариант уже гораздо лучше смотрится. Имея в распоряжении
+функцию `reduce`, можно вычислить не только сумму и минимум, но
+и множество других агрегирующих функций, таких как число, ближай
+шее к заданному, наибольшее число по модулю и стандартное отклоне
+ние. Функция `reduce` из модуля `std.algorithm` стандартной библиотеки
+выглядит практически так же, как и наша версия выше, за исключени-
+ем того, что она принимает в качестве аргументов несколько функций
+для вычисления; это позволяет очень быстро вычислять значения мно
+жества агрегирующих функций, поскольку выполняется всего один
+проход по входным данным.
+
+## 5.10. Функции с переменным числом аргументов
+
+В традиционной программе «Hello, world!», приведенной в начале кни
+ги, для вывода приветствия в стандартный поток использовалась функ
+ция `writeln` из стандартной библиотеки. У этой функции есть интерес
+ная особенность: она принимает любое число аргументов любых типов.
+В языке D определить функцию с переменным числом аргументов мож
+но разными способами, отвечающими тем или иным нуждам разработ
+чика. Начнем с самого простого.
+
+### 5.10.1. Гомогенные функции с переменным числом аргументов
+
+Гомогенная функция с переменным числом аргументов, принимающая
+любое количество аргументов одного типа, определяется так:
+
+```d
+import std.algorithm, std.array;
+
+// Вычисляет среднее арифметическое множества чисел, переданных непосредственно или в виде массива.
+double average(double[] values...)
+{
+ if (values.empty)
+ {
+ throw new Exception("Среднее арифметическое для нуля элементов " ~ "не определено");
+ }
+ return reduce!((a, b) { return a + b; })(0.0, values) / values.length;
+}
+
+unittest
+{
+ assert(average(0) == 0);
+ assert(average(1, 2) == 1.5);
+ assert(average(1, 2, 3) == 2);
+ // Передача массивов и срезов тоже срабатывает
+ double[] v = [1, 2, 3];
+ assert(average(v) == 2);
+}
+```
+
+(Обратите внимание на очередное удачное использование `reduce`.) Инте
+ресная деталь функции `average`: многоточие ... после параметра `values`,
+который является срезом. (Если бы это было не так или если бы пара
+метр `values` не был последним в списке аргументов функции `average`,
+компилятор диагностировал бы это многоточие как ошибку.)
+
+Вызов функции `average` со срезом массива элементов типа `double` (как по
+казано в последней строке теста модуля) ничем не примечателен. Однако
+благодаря многоточию эту функцию можно вызывать с любым числом
+аргументов, при условии что каждый из них можно привести к типу
+`double`. Компилятор автоматически сформирует из этих аргументов срез
+и передаст его в `average`.
+
+Может показаться, что это средство едва ли не тот же синтаксический
+сахар, позволяющий компилятору заменить `average(a, b, c)` на `average([a, b, c])`. Однако благодаря своему синтаксису вызова гомогенная
+функция с переменным числом аргументов перегружает другие функ
+ции в своем контексте. Например:
+
+```d
+// Исключительно ради аргумента
+double average() {}
+double average(double) {}
+// Гомогенная функция с переменным числом аргументов
+double average(double[] values...) { /* То же, что и выше */ ... }
+
+unittest
+{
+ average(); // Ошибка! Двусмысленный вызов перегруженной функции!
+}
+```
+
+Присутствие первых двух перегруженных версий `average` делает дву
+смысленным вызов без аргументов или с одним аргументом версии `average` с переменным числом аргументов. Избавиться от двусмысленности
+поможет явная передача среза, например `average([1, 2])`.
+
+Если в одном и том же контексте одновременно присутствуют обе функ
+ции – и с фиксированным, и с переменным числом аргументов,– каж
+дая из которых ожидает срез того же типа, что и другая, то при вызове
+с явно заданным срезом предпочтение отдается функции с фиксирован
+ным числом аргументов:
+
+```d
+import std.stdio;
+
+void average(double[]) { writeln("с фиксированным числом аргументов"); }
+void average(double[]...) { writeln("с переменным числом аргументов"); }
+
+void main()
+{
+ average(1, 2, 3); // Пишет "с переменным числом аргументов"
+ average([1, 2, 3]); // Пишет "с фиксированным числом аргументов"
+}
+```
+
+Кроме срезов можно использовать в качестве аргумента массив фикси
+рованной длины (в этом случае количество аргументов также фиксиро
+вано) и класс[^15]. Подробно классы описаны в главе 6, а здесь лишь не
+сколько слов о взаимодействии классов и функций с переменным чис
+лом аргументов.
+
+Если написать `void foo(T obj...)`, где `T` – имя класса, то внутри `foo` будет
+создан экземпляр `T`, причем его конструктору будут переданы аргумен
+ты, переданные функции. Если для данного набора аргументов конст
+руктора класса `T` не существует, будет сгенерирована ошибка. Созданный
+экземпляр является локальным для данной функции, память под него
+может быть выделена в стеке, поэтому он не возвращается функцией.
+
+### 5.10.2. Гетерогенные функции с переменным числом аргументов
+
+Вернемся к функции `writeln`. Она явно должна делать не совсем то же са
+мое, что функция `average`, поскольку `writeln` принимает аргументы раз
+ных типов. Для обработки произвольного числа аргументов произволь
+ных типов предназначена гетерогенная функция с переменным числом
+аргументов, которую определяют так:
+
+```d
+import std.conv;
+
+void writeln(T...)(T args)
+{
+ foreach (arg; args)
+ {
+ stdout.rawWrite(to!string(arg));
+ }
+ stdout.rawWrite('\n');
+ stdout.flush();
+}
+```
+
+Эта реализация немного сыровата и неэффективна, но она работает.
+`T` внутри `writeln` – *кортеж типов параметров* (тип, который группиру
+ет несколько типов), а `args` – *кортеж параметров*. Цикл `foreach` опреде
+ляет, что `args` – это кортеж типов, и генерирует код, радикально отли
+чающийся от того, что получается в результате обычного выполнения
+инструкции `foreach` (например, когда цикл `foreach` применяется для
+просмотра массива). Рассмотрим, например, такой вызов:
+
+```d
+writeln("Печатаю целое: ", 42, " и массив: ", [ 1, 2, 3 ]);
+```
+
+Для такого вызова конструкция `foreach` сгенерирует код следующего
+вида:
+
+```d
+// Аппроксимация сгенерированного кода
+void writeln(string a0, int a1, string a2, int[] a3)
+{
+ stdout.rawWrite(to!string(a0));
+ stdout.rawWrite(to!string(a1));
+ stdout.rawWrite(to!string(a2));
+ stdout.rawWrite(to!string(a3));
+ stdout.rawWrite('\n');
+ stdout.flush();
+}
+```
+
+В модуле `std.conv` определены версии `to!string` для всех типов (включая
+и сам тип `string`, для которого функция `to!string` – тождественное ото
+бражение), так что функция работает, по очереди преобразуя каждый
+аргумент в строку и печатая ее «сырые» байты в стандартный поток вы
+вода.
+
+Обратиться к типам или значениям кортежа параметров можно и без
+цикла `foreach`. Если `n` – известное во время компиляции неизменяемое
+число, то выражение `T[n]` возвратит `n`-й тип, а выражение `args[n]` – `n`-е зна
+чение в кортеже параметров. Получить число аргументов можно с по
+мощью выражения `T.length` или `args.length` (оба являются константами,
+известными во время компиляции). Если вы уже заметили сходство
+с массивами, то не будете удивлены, узнав, что с помощью выражения
+`T[$ - 1]` можно получить доступ к последнему типу в `T` (а `args[$ - 1]` –
+псевдоним для последнего значения в `args`). Например:
+
+```d
+import std.stdio;
+
+void testing(T...)(T values)
+{
+ writeln("Переданных аргументов: ", values.length, ".");
+ // Обращение к каждому индексу и каждому значению
+ foreach (i, value; values)
+ {
+ writeln(i, ": ", typeid(T[i]), " ", value);
+ }
+}
+
+void main()
+{
+ testing(5, "здравствуй", 4.2);
+}
+```
+
+Эта программа напечатает:
+
+```
+Переданных аргументов: 3.
+0: int 5
+1: immutable(char)[] здравствуй
+2: double 4.2
+```
+
+#### 5.10.2.1. Тип без имени
+
+Функция `writeln` делает слишком много специфичного, чтобы быть
+обобщенной: она всегда добавляет в конце `'\n'` и затем использует функ
+цию `flush` для записи данных буферов потока. Попробуем определить
+функцию `writeln` через базовую функцию `write`, которая просто выводит
+все аргументы по очереди:
+
+```d
+import std.conv;
+
+void write(T...)(T args)
+{
+ foreach (arg; args)
+ {
+ stdout.rawWrite(to!string(arg));
+ }
+}
+
+void writeln(T...)(T args)
+{
+ write(args, '\n');
+ stdout.flush();
+}
+```
+
+Обратите внимание, как `writeln` делегирует запись `args` и `'\n'` функции
+`write`. При передаче кортеж параметров автоматически разворачивает
+ся, так что вызов `writeln(1, "2", 3)` делегирует функции `write` запись из
+четырех, а не трех аргументов. Такое поведение немного необычно и не
+совсем понятно, поскольку практически во всех остальных случаях в D
+под одним идентификатором понимается одно значение. Этот пример
+может удивить даже подготовленных:
+
+```d
+void fun(T...)(T args)
+{
+ gun(args);
+}
+
+void gun(T)(T value)
+{
+ writeln(value);
+}
+
+unittest
+{
+ fun(1); // Все в порядке
+ fun(1, 2.2); // Ошибка! Невозможно найти функцию gun принимающую два аргумента!
+}
+```
+
+Первый вызов проходит гладко, чего нельзя сказать о втором. Вы ожи
+дали, что все будет в порядке, ведь любое значение (а значит, и `args`) об
+ладает каким-то типом, и потому тип `args` должен выводиться функци
+ей `gun`. Но что происходит на самом деле?
+
+Все значения действительно обладают типами, которые корректно от
+слеживаются компилятором. Виновен вызов `gun(args)`, поскольку компи
+лятор автоматически расширяет этот вызов, когда бы кортеж парамет
+ров ни передавался в качестве аргумента функции. Даже если вы напи
+сали `gun(args)`, компилятор всегда развернет такой вызов до `gun(args[0], args[1], ..., args[$ - 1])`. Под вторым вызовом подразумевается вызов
+`gun(args[0], args[1])`, который требует несуществующей функции `gun`
+с двумя аргументами, – отсюда и ошибка.
+
+Чтобы более глубоко исследовать этот случай, напишем «забавную»
+функцию `fun` для печати типа значения `args`.
+
+```d
+void fun(T...)(T args)
+{
+ writeln(typeof(args).stringof);
+}
+```
+
+Конструкция `typeof` – не вызов функции; это выражение всего лишь
+возвращает тип `args`, поэтому можно не волноваться относительно авто
+матической развертки. Свойство `.stringof`, присущее всем типам, воз
+вращает имя типа, так что давайте еще раз скомпилируем и запустим
+программу. Она печатает:
+
+```
+(int)
+(int, double)
+```
+
+Итак, действительно похоже на то, что компилятор отслеживает типы
+кортежей параметров, и для них определено строковое представление.
+Тем не менее невозможно явно определить кортеж параметров: типа
+`(int, double)` не существует.
+
+```d
+// Бесполезно
+(int, double) value = (1, 4.2);
+```
+
+Все объясняется тем, что кортежи в своем роде уникальны: это типы,
+которые внутренне используются компилятором, но не могут быть вы
+ражены в тексте программы. Никаким образом невозможно взять и на
+писать тип кортежа параметров. Потому нет и литерала, порождающе
+го вывод кортежа параметров (если бы был, то необходимость в указа
+нии имени типа отпала бы: ведь есть ключевое слово `auto`).
+
+#### 5.10.2.2. Тип данных Tuple и функция tuple
+
+Концепция типов без имен и значений без литералов может заинтересо
+вать любителя острых ощущений, однако программист практического
+склада увидит здесь нечто угрожающее. К счастью (наконец-то! эти сло
+ва должны были появиться рано или поздно), это не столько ограниче
+ние, сколько способ сэкономить на синтаксисе. Есть замечательная воз
+можность представлять типы кортежей параметров с помощью типа
+`Tuple`, а значения кортежей параметров – с помощью функции `tuple`.
+И то и другое находится в стандартном модуле `std.typecons`. Таким обра
+зом, кортеж параметров, содержащий `int` и `double`, можно записать так:
+
+```d
+import std.typecons;
+
+unittest
+{
+ Tuple!(int, double) value = tuple(1, 4.2); // Ого!
+}
+```
+
+Учитывая, что выражение `tuple(1, 4.2)` возвращает значение типа `Tuple!(int, double)`, следующий код эквивалентен только что представлен
+ному:
+
+```d
+auto value = tuple(1, 4.2); // Двойное “ого!"
+```
+
+Тип `Tuple!(int, double)` такой же, как и все остальные типы, он не делает
+никаких фокусов с автоматической разверткой, так что если вы хотите
+развернуть его до составных частей, нужно сделать это явно с помощью
+свойства `.expand` типа `Tuple`. Для примера переплавим нашу программу
+с функциями `fun` и `gun` и в результате получим следующий код:
+
+```d
+import std.stdio, std.typecons;
+
+void fun(T...)(T args)
+{
+ // Создать кортеж, чтобы "упаковать" все аргументы в одно значение
+ gun(tuple(args));
+}
+
+void gun(T)(T value)
+{
+ // Расширить кортеж и получить исходное множество параметров
+ writeln(value.expand);
+}
+
+void main()
+{
+ fun(1); // Все в порядке
+ fun(1, 2.2); // Все в порядке
+}
+```
+
+Посмотрите, как функция `fun` группирует все аргументы в один кортеж
+(`Tuple`) и передает его в функцию `gun`, которая разворачивает получен
+ный кортеж, извлекая все, что он содержит. Выражение `value.expand`
+автоматически заменяется на список аргументов, содержащий все, что
+вы отправили в `Tuple`.
+
+В реализации типа `Tuple` есть пара тонких моментов, но она использует
+средства, доступные любому программисту. Изучение определения ти
+па `Tuple` (которое можно найти в стандартной библиотеке) было бы по
+лезным упражнением.
+
+### 5.10.3. Гетерогенные функции с переменным числом аргументов. Альтернативный подход[^16]
+
+Предыдущий подход всем хорош, однако применение шаблонов накла
+дывает на функции ряд ограничений. Поскольку приведенная выше
+реализация использует шаблоны, для каждого возможного кортежа па
+раметров создается свой экземпляр шаблонной функции. Это не позво
+ляет делать шаблонные функции виртуальными методами класса, объ
+являть их нефинальными членами интерфейсов, а при невнимательном
+подходе может приводить к излишнему разрастанию результирующего
+кода (поэтому шаблонная функция должна быть небольшой, чтобы ком
+пилятор счел возможной ее inline-подстановку). Поэтому D предлагает
+еще два способа объявить функцию с переменным числом аргументов.
+Оба способа были добавлены в язык до появления шаблонов с перемен
+ным числом аргументов, и сегодня считаются небезопасными и устарев
+шими. Тем не менее они присутствуют и используются в текущих реа
+лизациях языка, чаще всего из соображений совместимости.
+
+#### 5.10.3.1. Функции с переменным числом аргументов в стиле C
+
+Первый способ язык D унаследовал от языка C. Вспомним функцию
+`printf`. Вот ее сигнатура на D:
+
+```d
+extern(C) int printf(in char* format, ...);
+```
+
+Разберем ее по порядку. Запись `extern(C)` обозначает тип компоновки.
+В данном случае указано, что функция использует тип компоновки C. То
+есть параметры передаются в функцию в соответствии с соглашением
+о вызовах языка C. Также в C не используется искажение имен (mang
+ling) функций, поэтому такая функция не может быть перегружена по
+типам аргументов. Если две такие функции с одинаковыми именами
+объявлены в разных модулях, возникнет конфликт имен. Как правило,
+`extern(C)` используется для взаимодействия с кодом, уже написанным
+на C или других языках. `in char* format` – обязательный первый аргу
+мент функции, за которым следует переменное число аргументов, что
+символизирует уже знакомое нам многоточие (`...`).
+
+Для начала вспомним, как аргументы передаются функции в языке C.
+C передает аргументы через стек, помещая в него аргументы, начиная
+с последнего. За удаление аргументов из стека отвечает вызывающая
+подпрограмма. Например, при вызове `printf("%d + %d = %d", 2, 3, 5)` пер
+вым в стек будет помещен аргумент 5, после него 3, затем 2 и последней –
+строка формата. В итоге строка формата оказывается на вершине стека
+и будет доступна в вызываемой функции. Для получения остальных ар
+гументов в C используются макросы, определенные в файле `stdarg.h`.
+
+В языке D соответствующие функции определены в модуле `std.c.stdarg`.
+Во-первых, в данном модуле определен тип `va_list`, который является
+указателем на список необязательных аргументов. Функция `va_start`
+инициализирует переменную `va_list` указателем на начало списка не
+обязательных аргументов.
+
+```d
+void va_start(T)( out va_list ap, ref T parmn );
+```
+
+Первый аргумент – инициализируемая переменная `va_list`, второй –
+ссылка на последний обязательный аргумент, то есть последний аргу
+мент, тип которого известен. На основании него вычисляется указатель
+на первый элемент списка необязательных аргументов. Именно поэто
+му функция с переменным числом аргументов в C должна иметь хотя
+бы один обязательный параметр, чтобы `va_start` было к чему привя
+заться. Объявление `extern(C) int foo(...);` недопустимо.
+
+Функция `va_arg` получает значение очередного аргумента заданного ти
+па. Тип этого аргумента может быть получен в результате каких-то опе
+раций с предыдущими аргументами, и проверить правильность его по
+лучения невозможно. Указатель на список при этом изменяется так,
+чтобы он указывал на следующий элемент списка.
+
+```d
+T va_arg(T)( ref va_list ap );
+```
+
+Функция `va_copy` предназначена для копирования переменной типа `va_list`. Если `va_list` – указатель на стек функции, выполняется копирова
+ние указателя. Если же в вашей системе аргументы передаются через
+регистры, потребуется выделение памяти и копирование списка.
+
+```d
+void va_copy( out va_list dest, va_list src );
+```
+
+Функция `va_end` вызывается по завершении работы со списком аргу
+ментов. Каждый вызов `va_start` или `va_copy` должен сопровождаться вы
+зовом `va_end`.
+
+```d
+void va_end( va_list ap );
+```
+
+Интерфейс `stdarg` является кроссплатформенным, а сама реализация
+функций с переменным числом аргументов может быть различной для
+разных платформ. В некоторых платформах аргументы передаются че
+рез стек, и `va_list` – указатель на верхний элемент списка в стеке. В не
+которых аргументы могут передаваться через регистры. Также разным
+может быть выравнивание элементов в стеке и направление роста сте
+ка. Поэтому следует пользоваться именно этим интерфейсом, а не пы
+таться договориться с функцией в обход него. Пример функции для
+преобразования в строку значения нужного типа:
+
+```d
+import std.c.stdarg, std.conv;
+
+extern(C) string cToString(string type, ...)
+{
+ va_list args_list;
+ va_start(args_list, type);
+ scope(exit) va_end(args_list);
+ switch (type)
+ {
+ case "int":
+ auto int_val = va_arg!int(args_list);
+ return to!string(int_val);
+ case "double":
+ auto double_val = va_arg!double(args_list);
+ return to!string(double_val);
+ case "complex":
+ auto re_val = va_arg!double(args_list);
+ auto im_val = va_arg!double(args_list);
+ return to!string(re_val) ~ " + " ~ to!string(im_val) ~ "i";
+ case "string":
+ return va_arg!string(args_list);
+ default:
+ assert(0, "Незнакомый тип");
+ }
+}
+
+unittest
+{
+ assert(cToString("int", 5) == "5");
+ assert(cToString("double", 2.0) == "2");
+ assert(cToString("string", "Test string") == "Test string");
+ assert(cToString("complex", 3.5, 2.7) == "3.5 + 2.7i");
+}
+```
+
+В этом примере мы первым аргументом передаем тип следующих аргу
+ментов, и на основании этого аргумента функция определяет, каких
+аргументов ей ждать дальше. Однако если мы допустим ошибку в вызо
+ве, то спасти нас уже никто не сможет. В этом и заключается опасность
+подобных функций: ошибка в вызове может привести к аппаратной
+ошибке внутри самой функции. Например, если мы напишем:
+
+```d
+cToString("string", 3.5, 2.7);
+```
+
+результат будет непредсказуемым. Поэтому, например, функция `scanf`
+может оказаться небезопасной, если строка формата берется из ненадеж
+ного источника, ведь с правильно подобранной строкой формата и аргу
+ментом можно получить перезапись адреса возврата функции и заста
+вить программу выполнить какой-то свой, наверняка вредоносный код.
+Поэтому язык D предлагает менее опасный способ создания функций
+с переменным числом аргументов.
+
+#### 5.10.3.2. Функции с переменным числом аргументов в стиле D
+
+Функцию с переменным числом аргументов в стиле D можно объявить
+так:
+
+```d
+void foo(...);
+```
+
+То есть делается абсолютно то же самое, что и в случае выше, но выбира
+ется тип компоновки D (по умолчанию или явным указанием `extern(D)`),
+и обязательный аргумент можно не указывать. В самой же приведен
+ной функции применяется не такой подход, как в языке C. Внутри та
+кой функции доступны два идентификатора: `_arguments` типа `TypeInfo[]`
+и `_argptr` типа `va_list`. Идентификатор `_argptr` указывает на начало спи
+ска аргументов, а `_arguments` – на массив идентификаторов типа для каж
+дого переданного аргумента. Количество переданных аргументов соот
+ветствует длине массива.
+
+Об идентификаторах типов следует рассказать подробнее. Идентифика
+тор типа – это объект класса `TypeInfo` или производного от него. Полу
+чить идентификатор типа `T` можно с помощью выражения `typeid(T)`.
+Для каждого типа есть один и только один идентификатор. То есть ра
+венство `typeid(int) is typeid(int)` всегда верно. Полный список парамет
+ров класса `TypeInfo` следует искать в документации по вашему компиля
+тору или в модуле `object`. Модуль `object`, объявленный в файле `object.di`,
+импортируется в любом модуле по умолчанию, то есть можно использо
+вать любые объявленные в нем символы без каких-то дополнительных
+объявлений. Вот безопасный вариант предыдущего примера:
+
+```d
+import std.c.stdarg, std.conv;
+
+string dToString(string type, ...)
+{
+ va_list args_list;
+ va_copy(args_list, _argptr);
+ scope(exit) va_end(args_list);
+ switch (type)
+ {
+ case "int":
+ assert(_arguments.length == 1 && _arguments[0] is typeid(int), "Аргумент должен иметь тип int.");
+ auto int_val = va_arg!int(args_list);
+ return to!string(int_val);
+ case "double":
+ assert(_arguments.length == 1 &&_arguments[0] is typeid(double), "Аргумент должен иметь тип double.");
+ auto double_val = va_arg!double(args_list);
+ return to!string(double_val);
+ case "complex":
+ assert(_arguments.length == 2 &&
+ _arguments[0] is typeid(double) &&
+ _arguments[1] is typeid(double),
+ "Для типа complex должны быть переданы два аргумента типа double.");
+ auto re_val = va_arg!double(args_list);
+ auto im_val = va_arg!double(args_list);
+ return to!string(re_val) ~ " + " ~ to!string(im_val) ~ "i";
+ case "string":
+ assert(_arguments.length == 1 &&_arguments[0] is typeid(string),
+ "Аргумент должен иметь тип string.");
+ return va_arg!string(args_list).idup;
+ default:
+ assert(0);
+ }
+}
+
+unittest
+{
+ assert(dToString("int", 5) == "5");
+ assert(dToString("double", 2.0) == "2");
+ assert(dToString("string", "Test string") == "Test string");
+ assert(dToString("complex", 3.5, 2.7) == "3.5 + 2.7i");
+}
+```
+
+Этот вариант автоматически проверят типы переданных аргументов.
+Однако не забывайте, что корректность типа, переданного `va_arg`, оста
+ется за вами – использование неправильного типа приведет к непред
+сказуемой ситуации. Если вас это беспокоит, то для полной безопасно
+сти вы можете использовать конструкцию `Variant` из модуля стандарт
+ной библиотеки `std.variant`:
+
+```d
+import std.stdio, std.variant;
+
+void pseudoVariadic(Variant[] vars)
+{
+ foreach (var; vars)
+ if (var.type == typeid(string))
+ writeln("Строка: ", var.get!string);
+ else if (var.type == typeid(int))
+ writeln("Целое число: ", var.get!int);
+ else
+ writeln("Незнакомый тип: ", var.type);
+}
+
+void templatedVariadic(T...)(T args)
+{
+ pseudoVariadic(variantArray(args));
+}
+
+void main()
+{
+ templatedVariadic("Здравствуй, мир!", 42);
+}
+```
+
+При этом функция `templatedVariadic`, скорее всего, будет встроена в код
+путем inline-подстановки, и накладных расходов на лишний вызов
+функции и разрастание шаблонного кода не будет.
+
+## 5.11. Атрибуты функций
+
+К функциям на D можно присоединять *атрибуты* – особые средства,
+извещающие программиста и компилятор о том, что функция обладает
+некоторыми качествами. Функции проверяются на соответствие своим
+атрибутам, поэтому, чтобы узнать важную информацию о поведении
+функции, достаточно взглянуть на ее сигнатуру: атрибуты предостав
+ляют твердые гарантии, это не простые комментарии или соглашения.
+
+### 5.11.1. Чистые функции
+
+Чистота функций – заимствованное из математики понятие, полезное
+как в теории, так и на практике. В языке D функция считается чистой,
+если все, что она делает, сводится к возвращению результата и возвра
+щаемое значение зависит только от ее аргументов.
+
+В классической математике все функции чистые, поскольку в классиче
+ской математике нет состояний и изменений. Чему равен √2? Примерно
+1,4142; так было вчера, будет завтра и вообще всегда. Можно доказать,
+что значение √2 было тем же еще до того, как человечество открыло кор
+ни, алгебру, числа, и даже *до* появления человечества, способного оце
+нить красоту математики, и столь же долго пребудет неизменным после
+тепловой смерти Вселенной. Математические результаты вечны.
+
+Чистота – это благо для функций, пусть даже иногда и с ограничения
+ми, впрочем, как и в жизни. (Кстати, как и в жизни, чистоты не так
+просто достичь. Более того, по мнению некоторых, излишества в неко
+торых проявлениях чистоты на самом деле могут раздражать.) В пользу
+чистоты говорит тот факт, что о чистой функции легче делать выводы.
+Чистота гарантирует: чтобы узнать, что делает та или иная функция,
+достаточно взглянуть на ее вызов. Можно заменять эквивалентные вы
+зовы функций значениями, а значения – эквивалентными вызовами
+функций. Можно быть уверенным, что ошибки в чистых функциях не
+обладают эффектом шрапнели – они не могут повлиять на что-либо еще
+помимо результата самой функции.
+
+Кроме того, чистые функции могут выполняться в буквальном смысле
+параллельно, так как они никаким образом, кроме их результата, не
+взаимодействуют с остальным кодом программы. В противоположность
+им, насыщенные изменениями[^17] нечистые функции при параллельном
+выполнении склонны наступать друг другу на пятки. Но даже если вы
+полнять их последовательно, результат может неуловимо зависеть от
+порядка, в котором они вызываются. Многих из нас это не удивляет –
+мы настолько свыклись с таким раскладом, что считаем преодоление
+трудностей неотъемлемой частью процесса написания кода. Но если хо
+тя бы некоторые части приложения будут написаны «чисто», это прине
+сет большую пользу, освежив программу в целом.
+
+Определить чистую функцию можно, добавив в начало ее определения
+ключевое слово `pure`:
+
+```d
+pure bool leapYear(uint y)
+{
+ return (y % 4) == 0 && (y % 100 || (y % 400) == 0);
+}
+```
+
+Например, сигнатура функции
+
+```d
+pure bool leapYear(uint y);
+```
+
+гарантирует пользователю, что функция `leapYear` не пишет в стандарт
+ный поток вывода. Кроме того, уже по сигнатуре видно, что вызов `leapYear(2020)` всегда будет возвращать одно и то же значение.
+
+Компилятор также в курсе значения ключевого слова `pure`, и именно он
+ограждает программиста от любых действий, способных нарушить чис
+тоту функции `leapYear`. Приглядитесь к следующим изменениям:
+
+```d
+pure bool leapYear(uint y)
+{
+ auto result = (y % 4) == 0 && (y % 100 || (y % 400) == 0);
+ if (result) writeln(y, " – високосный год!"); // Ошибка! Из чистой функции невозможно вызвать нечистую функцию!
+ return result;
+}
+```
+
+Функция `writeln` не является и не может стать чистой. И если бы она за
+являла обратное, компилятор бы избавил ее от такого заблуждения.
+Компилятор гарантирует, что чистая функция вызывает только чистые
+функции. Вот почему измененная функция `leapYear` не компилируется.
+С другой стороны, проверку компилятора успешно проходят такие функ
+ции, как `daysInYear`:
+
+```d
+// Чистота подтверждена компилятором
+pure uint daysInYear(uint y)
+{
+ return 365 + leapYear(y);
+}
+```
+
+#### 5.11.1.1. «Чист тот, кто чисто поступает»
+
+По традиции функциональные языки запрещают абсолютно любые из
+менения, чтобы программа могла называться чистой. D ослабляет это
+ограничение, разрешая функциям изменять собственное локальное
+и временное состояние. Таким образом, даже если внутри функции есть
+изменения, для окружающего кода она все еще непогрешима.
+
+Посмотрим, как работает это допущение. В качестве примера возьмем
+наивную реализацию функции Фибоначчи в функциональном стиле:
+
+```d
+ulong fib(uint n)
+{
+ return n < 2 ? n : fib(n - 1) + fib(n - 2);
+}
+```
+
+Ни один преподаватель программирования никогда не должен учить
+реализовывать расчет чисел Фибоначчи таким способом. Чтобы вычис
+лить результат, функции `fib` требуется *экспоненциальное время*, поэто
+му все, чему она может научить, – это пренебрежение сложностью и це
+ной вычислений, лозунг «небрежно, зато находчиво» и спортивный
+стиль вождения. Хотите знать, чем плох экспоненциальный порядок?
+Вызовы `fib(10)` и `fib(20)` на современной машине не займут много време
+ни, но вызов `fib(50)` обрабатывается уже 19 минут. Вполне вероятно, что
+вычисление `fib(1000)` переживет человечество (только смысла в этом ни
+какого, в отличие от примера с √2.)
+
+Хорошо, но как выглядит «правильная» функциональная реализация
+Фибоначчи?
+
+```d
+ulong fib(uint n)
+{
+ ulong iter(uint i, ulong fib_1, ulong fib_2)
+ {
+ return i == n ? fib_2 : iter(i + 1, fib_1 + fib_2, fib_1);
+ }
+ return iter(0, 1, 0);
+}
+```
+
+Переработанная версия вычисляет `fib(50)` практически мгновенно. Эта
+реализация требует для выполнения *O*(*n*)[^18] времени, поскольку оптими
+зация хвостовой рекурсии (см. раздел 1.4.2) позволяет уменьшить
+сложность вычислений. (Стоит отметить, что для расчета чисел Фибо
+наччи существуют и алгоритмы с временем выполнения *O*(log *n*)).
+
+Проблема в том, что новая функция `fib` как бы утратила былое велико
+лепие. Особенность переработанной реализации – две переменные со
+стояния, маскирующиеся под параметры функции, и вполне можно бы
+ло с чистой совестью написать явный цикл, который зачем-то был зака
+муфлирован функцией `iter`:
+
+```d
+ulong fib(uint n)
+{
+ ulong fib_1 = 1, fib_2 = 0;
+ foreach (i; 0 .. n)
+ {
+ auto t = fib_1;
+ fib_1 += fib_2;
+ fib_2 = t;
+ }
+ return fib_2;
+}
+```
+
+К сожалению, это уже не функциональный стиль. Только посмотрите
+на все эти изменения, происходящие в цикле. Один неверный шаг –
+и с вершин математической чистоты мы скатились к неискушенности
+чумазых низов.
+
+Но подумав немного, мы увидим, что итеративная функция `fib` *не* такая
+уж чумазая. Если принять ее за черный ящик, то можно заметить, что
+при одних и тех же аргументах функция `fib` всегда возвращает один
+и тот же результат, а ведь «красив тот, кто красиво поступает». Тот факт,
+что она использует локальное изменение состояния, делает ее менее
+функциональной по букве, но не по духу. Продолжая эту мысль, прихо
+дим к очень интересному выводу: пока изменяемое состояние внутри
+функции остается полностью *временным* (то есть хранит данные в сте
+ке) и *локальным* (то есть не передается по ссылке другим функциям,
+которые могут его нарушить), эту функцию можно считать чистой.
+
+Вот как D определяет функциональную чистоту: в реализации чистой
+функции разрешается использовать изменения, если они временные
+и локальные. Сигнатуру такой функции можно снабдить ключевым сло
+вом `pure`, и компилятор без помех скомпилирует этот код:
+
+```d
+pure ulong fib(uint n)
+{
+ ... // Итеративная реализация
+}
+```
+
+Принятые в D допущения, смягчающие математическое понятие чисто
+ты, очень полезны, поскольку позволяют взять лучшее из двух миров:
+железные гарантии функциональной чистоты и удобную реализацию
+(если код с изменениями более предпочтителен).
+
+### 5.11.2. Атрибут nothrow
+
+Атрибут `nothrow` сообщает, что данная функция никогда не порождает
+исключения. Как и атрибут `pure`, атрибут `nothrow` проверяется во время
+компиляции. Например:
+
+```d
+import std.stdio;
+
+nothrow void tryLog(string msg)
+{
+ try {
+ stderr.writeln(msg);
+ } catch (Exception) {
+ // Проигнорировать исключение
+ }
+}
+```
+
+Функция `tryLog` прилагает максимум усилий, чтобы записать в журнал
+сообщение. Если возникает исключение, она его молча игнорирует. Это
+качество позволяет использовать функцию `tryLog` на критических уча
+стках кода. При определенных обстоятельствах было бы глупо позво
+лить некоторой важной транзакции сорваться только из-за невозмож
+ности сделать запись в журнал. Устройство кода, представляющего со
+бой транзакцию, основано на том, что некоторые из его участков нико
+гда не порождают исключения, а применение атрибута `nothrow` позволяет
+статически гарантировать это свойство критических участков.
+
+Проверка семантики функций с атрибутом `nothrow` гарантирует, что ис
+ключение никогда не просочится из функции. Для каждой инструкции
+внутри функции должно быть истинно одно из утверждений: 1) эта ин
+струкция не порождает исключения (в случае вызова функции это воз
+можно, только если вызываемая функция также не порождает исключе
+ния), 2) эта инструкция расположена внутри инструкции `try`, «съедаю
+щей» исключения. Проиллюстрируем второй случай примером:
+
+```d
+nothrow void sensitive(Widget w)
+{
+ tryLog("Начинаем опасную операцию");
+ try {
+ w.mayThrow(); // Вызов может породить исключение
+ tryLog("Опасная операция успешно завершена");
+ } catch (Exception) {
+ tryLog("Опасная операция завершилась неудачей");
+ }
+}
+```
+
+Первый вызов функции `tryLog` можно не помещать в блок `try`, поскольку
+компилятор уже знает, что эта функция не порождает исключения.
+Аналогично вызов внутри блока `catch` можно не «защищать» с помо
+щью дополнительного блока `try`.
+
+Как соотносятся атрибуты `pure` и `nothrow`? Может показаться, что они
+совершенно независимы друг от друга, но на самом деле между ними
+есть некоторая взаимосвязь. По крайней мере в стандартной библиоте
+ке многие функции, например самые трансцендентные (такие как `exp`,
+`sin`, `cos`), имеют оба атрибута – и `pure`, и `nothrow`.
+
+## 5.12. Вычисления во время компиляции
+
+В подтверждение поговорки, что счастье приходит к тому, кто умеет
+ждать (или терпеливо читать), в этом последнем разделе обсуждается
+очень интересное средство D. Лучшее в этом средстве то, что вам не
+нужно много учиться, чтобы начать широко его применять.
+
+Рассмотрим пример, достаточно большой, чтобы быть осмысленным.
+Предположим, вы хотите создать лучшую библиотеку генераторов слу
+чайных чисел. Есть много разных генераторов случайных чисел, в том
+числе линейные конгруэнтные генераторы.
+У таких генераторов есть три целочисленных параметра: модуль *m* > 0,
+множитель 0 < *a* < *m* и наращиваемое значение[^19] 0 < *c* < *m*. Начав с про
+извольного начального значения 0 ≤ *x*0 < *m*, линейный конгруэнтный
+генератор вычисляет псевдослучайные числа по следующей рекуррент
+ной формуле:
+
+*x*n+1 = (*ax*n + *c*) mod *m*
+
+Запрограммировать такой алгоритм очень просто: достаточно сохра
+нять состояние, определяемое числами *m*, *a*, *c* и *x*n, и определить функ
+цию `getNext` для получения следующего значения *x*n+1.
+
+Но здесь есть подвох. Не все комбинации *a*, *m* и *c* дадут хороший генера
+тор случайных чисел. Для начала, при *a* = 1 и *c* = 1 генератор формиру
+ет последовательность 0, 1, …, *m* – 1, 0, ..., *m* – 1, 0, 1, ..., которую слу
+чайной уж никак не назовешь.
+
+С большими значениями *a* и *c* таких очевидных рисков можно избе
+жать, однако появляется менее заметная проблема: периодичность.
+Из-за оператора деления по модулю числа генерируются всегда между 0
+и *m* – 1, так что неплохо было бы сделать значение *m* настолько боль
+шим, насколько это возможно (обычно в качестве значения этого пара
+метра берут степень двойки, чтобы оно соответствовало размеру машин
+ного слова: это позволяет обойтись без затрат на деление по модулю).
+Проблема в том, что сгенерированная последовательность может обла
+дать периодом гораздо меньшим, чем *m*. Пусть мы работаем с типом `uint`
+и выбираем *m* = 232 (тогда нам даже операция деления по модулю не
+нужна), *a* = 210, *c* = 123, *а* для *x*0 возьмем какое-нибудь сумасшедшее
+значение, например 1 780 588 661. Запустим следующую программу:
+
+```d
+import std.stdio;
+
+void main()
+{
+ enum uint a = 210, c = 123, x0 = 1_780_588_661;
+ auto x = x0;
+ foreach (i; 0 .. 100)
+ {
+ x = a * x + c;
+ writeln(x);
+ }
+}
+```
+
+Вместо пестрого набора случайных чисел мы увидим нечто неожидан
+ное:
+
+```
+1 261464181
+2 3367870581
+3 2878185589
+4 3123552373
+5 3110969461
+6 468557941
+7 3907887221
+8 317562997
+9 2263720053
+10 2934808693
+11 2129502325
+12 518889589
+13 1592631413
+14 3740115061
+15 3740115061
+16 3740115061
+17 ...
+```
+
+Начинает генератор вполне задорно. По крайней мере, с непривычки
+может показаться, что он неплохо справляется с генерацией случай
+ных чисел. Однако уже с 14-го шага генератор зацикливается: по стран
+ному стечению обстоятельств, породить которое могла только матема
+тика, 3 740 115 061 оказалось (и всегда будет оказываться) точно равным
+(3 740 115 061 * 210 + 123) mod 232. Это период единицы, худшее из воз
+можного!
+
+Значит, необходимо выбрать такие параметры *m*, *a* и *c*, чтобы сгенери
+рованная последовательность псевдослучайных чисел гарантированно
+имела большой период. Дальнейшие исследования этой проблемы вы
+явили следующие условия генерации последовательности псевдослу
+чайных чисел с периодом *m* (наибольший возможный период):
+
+1. *c* и *m* взаимно просты.
+2. Значение *a* – 1 кратно всем простым делителям *m*.
+3. Если *a* – 1 кратно 4, то и *m* кратно 4.
+
+Взаимную простоту *c* и *m* можно легко проверить сравнением наиболь
+шего общего делителя этих чисел с 1. Для вычисления наибольшего
+общего делителя воспользуемся алгоритмом Евклида[^20]:
+
+```d
+// Реализация алгоритма Евклида
+ulong gcd(ulong a, ulong b)
+{
+ while (b)
+ {
+ auto t = b;
+ b = a % b;
+ a = t;
+ }
+ return a;
+}
+```
+
+Евклид выразил свой алгоритм с помощью вычитания, а не деления по
+модулю. Для версии с делением по модулю требуется меньше итераций,
+но на современных машинах `%` может вычисляться довольно-таки мед
+ленно (видимо, именно это и остановило Евклида).
+
+Реализовать вторую проверку немного сложнее. Можно было бы напи
+сать функцию `factorize`, возвращающую все возможные простые дели
+тели числа с их степенями, и воспользоваться ею, но `factorize` – это боль
+ше, чем нам необходимо. Стремясь к простейшему решению, которое
+могло бы сработать, проще всего написать функцию `primeFactorsOnly(n)`,
+возвращающую произведение простых делителей `n`, но без степеней. То
+гда наша задача сводится к проверке выражения `(a - 1) % primeFactorsOnly(m) == 0`. Итак, приступим к реализации функции `primeFactorsOnly`.
+
+Есть много способов получить простые делители некоторого числа *n*.
+Один из простых: сгенерировать простые числа *p*1, *p*2, *p*3, ..., для каждого
+значения *p*k выяснить, делится ли *n* на *p*k, и если делится, то умножить *p*k
+на значение-аккумулятор *r*. Когда очередное число *p*k окажется больше
+*n*, вычисления прекращаются. Аккумулятор *r* содержит искомое значе
+ние – произведение всех простых делителей *n*, взятых по одному разу.
+
+(Догадываюсь, что сейчас вы задаетесь вопросом, имеет ли все это отно
+шение к вычислениям во время компиляции. Ответ: имеет. Прошу не
+много терпения.)
+
+Более простую версию можно получить, избавившись от генерации
+простых чисел. Можно просто вычислять *n* mod *k* для возрастающих
+значений *k*, образующих следующую последовательность (начиная с 2):
+2, 3, 5, 7, 9, ... Всякий раз, когда *n* делится на *k*, аккумулятор умножа
+ется на *k*, а *n* «очищается» от всех степеней *k*: *n* присваивается значение
+*n* / *k*, пока *n* делится на *k*. Таким образом, мы сохранили значение *k*
+и одновременно уменьшили число *n* настолько, что теперь оно не делит
+ся на *k*. Это не выглядит как самый экономный метод, но задумайтесь
+о том, что генерация простых чисел могла бы потребовать сравнимых
+трудозатрат, по крайней мере в случае простой реализации. Реализа
+ция этой идеи могла бы выглядеть так:
+
+```d
+ulong primeFactorsOnly(ulong n)
+{
+ ulong accum = 1;
+ ulong iter = 2;
+ for (; n >= iter * iter; iter += 2 - (iter == 2))
+ {
+ if (n % iter) continue;
+ accum *= iter;
+ do n /= iter; while (n % iter == 0);
+ }
+ return accum * n;
+}
+```
+
+Команда `iter += 2 - (iter == 2)`, обновляющая значение переменной `iter`,
+всегда увеличивает его на `2`, кроме случая, когда `iter` равно `2`: тогда зна
+чение этой переменной заменяется на `3`. Таким образом, переменная `iter`
+принимает значения `2`, `3`, `5`, `7`, `9` и т. д. Было бы слишком расточительно
+проверять каждое четное число, например `4`, поскольку число `2` уже бы
+ло проверено и все его степени извлечены из `n`.
+
+Почему в качестве условия продолжения цикла выбрана проверка `n >= iter * iter`, а не `n >= iter`? Ответ не вполне прямолинеен. Если число `iter`
+больше √`n` и отличается от самого числа `n`, то есть уверенность, что чис
+ло `n` не делится на число `iter`: если бы делилось, должен был бы сущест
+вовать некоторый множитель `k`, такой, что `n == k * iter`, но все делители
+меньше `iter` только что были рассмотрены, так что `k` должно быть боль
+ше `iter`, и следовательно, произведение `k * iter` – больше `n`, что делает
+равенство невозможным.
+
+Протестируем функцию `primeFactorsOnly`:
+
+```d
+unittest
+{
+ assert(primeFactorsOnly(100) == 10);
+ assert(primeFactorsOnly(11) == 11);
+ assert(primeFactorsOnly(7 * 7 * 11 * 11 * 15) == 7 * 11 * 15);
+ assert(primeFactorsOnly(129 * 2) == 129 * 2);
+}
+```
+
+В завершение нам необходима небольшая функция-обертка, выполняю
+щая три рассмотренные проверки трех потенциальных параметров ли
+нейного конгруэнтного генератора:
+
+```d
+bool properLinearCongruentialParameters(ulong m, ulong a, ulong c)
+{
+ // Проверка границ
+ if (m == 0 || a == 0 || a >= m || c == 0 || c >= m) return false;
+ // c и m взаимно просты
+ if (gcd(c, m) != 1) return false;
+ // Значение a - 1 кратно всем простым делителям m
+ if ((a - 1) % primeFactorsOnly(m)) return false;
+ // Если a - 1 кратно 4, то и m кратно 4
+ if ((a - 1) % 4 == 0 && m % 4) return false;
+ // Все тесты пройдены
+ return true;
+}
+```
+
+Протестируем некоторые популярные значения `m`, `a` и `c`:
+
+```d
+unittest
+{
+ // Наш неподходящий пример
+ assert(!properLinearCongruentialParameters(1UL << 32, 210, 123));
+ // Пример из книги "Numerical Recipes"
+ assert(properLinearCongruentialParameters(1UL << 32, 1664525, 1013904223));
+ // Компилятор Borland C/C++
+ assert(properLinearCongruentialParameters(1UL << 32, 22695477, 1));
+ // glibc
+ assert(properLinearCongruentialParameters(1UL << 32, 1103515245, 12345));
+ // ANSI C
+ assert(properLinearCongruentialParameters(1UL << 32, 134775813, 1));
+ // Microsoft Visual C/C++
+ assert(properLinearCongruentialParameters(1UL << 32, 214013, 2531011));
+}
+```
+
+Похоже, функция `properLinearCongruentialParameters` работает как надо,
+то есть мы справились со всеми деталями тестирования состоятельно
+сти линейного конгруэнтного генератора. Так что пора притормозить,
+заглушить мотор и покаяться. Какое отношение имеет вся эта простота
+и делимость к вычислениям во время компиляции? Где мясо?[^21] Где шаб
+лоны, макросы или как там они еще называются? Многообещающие
+инструкции `static if`? Умопомрачительные генерация кода и расшире
+ние кода?
+
+На самом деле, вы только что увидели все, что только можно рассказать
+о вычислениях во время компиляции. Задав константам `m`, `n` и `с` любые
+числовые значения, можно вычислить `properLinearCongruentialParameters`
+*во время компиляции*, никак не изменяя эту функцию или функции,
+которые она вызывает. В компилятор D встроен интерпретатор, кото
+рый вычисляет функции на D во время компиляции – со всей арифме
+тикой, циклами, изменениями, ранними возвратами и даже трансцен
+дентными функциями.
+
+От вас требуется только указать компилятору, что вычисления нужно
+выполнить во время компиляции. Для этого есть несколько способов:
+
+```d
+unittest
+{
+ enum ulong m = 1UL << 32, a = 1664525, c = 1013904223;
+ // Способ 1: воспользоваться инструкцией static assert
+ static assert(properLinearCongruentialParameters(m, a, c));
+ // Способ 2: присвоить результат символической константе, объявленной с ключевым словом enum
+ enum proper1 = properLinearCongruentialParameters(m, a, c);
+ // Способ 3: присвоить результат статическому значению
+ static proper2 = properLinearCongruentialParameters(m, a, c);
+}
+```
+
+Мы еще не рассматривали структуры и классы в подробностях, но от
+метим, немного опережая события, что типичный вариант использова
+ния функции `properLinearCongruentialParameters` – ее размещение внут
+ри структуры или класса, определяющего линейный конгруэнтный ге
+нератор. Например:
+
+```d
+struct LinearCongruentialEngine(UIntType, UIntType a, UIntType c, UIntType m)
+{
+ static assert(properLinearCongruentialParameters(m, a, c), "Некорректная инициализация LinearCongruentialEngine");
+ ...
+}
+```
+
+Собственно, эти строки скопированы из одноименной структуры, кото
+рую можно найти в стандартном модуле `std.random`.
+
+Изменив время выполнения проверки (теперь она выполняется на эта
+пе компиляции, а не во время исполнения программы), мы получили
+два любопытных последствия. Во-первых, можно было бы отложить
+проверку до исполнения программы, расположив вызов `properLinearCongruentialParameters` в конструкторе структуры `LinearCongruentialEngine`. Но обычно чем раньше узнаешь об ошибках, тем лучше, особен
+но если это касается библиотеки, которая почти не контролирует то,
+как ее используют. При статической проверке некорректно созданные
+экземпляры `LinearCongruentialEngine` не сигнализируют об ошибках:
+исключается сама возможность их появления. Во-вторых, используя
+константы, известные во время компиляции, код имеет хороший шанс
+работать быстрее, чем код с обычными значениями `m`, `a` и `c`. На боль
+шинстве современных процессоров константы в виде литералов могут
+быть сделаны частью потока команд, так что их загрузка вообще не
+требует никаких дополнительных обращений к памяти. И посмотрим
+правде в глаза: линейные конгруэнтные генераторы – не самые случай
+ные в мире, и используют их главным образом благодаря скорости.
+
+Процесс интерпретации на пару порядков медленнее генерации кода,
+но гораздо быстрее традиционного метапрограммирования на основе
+шаблонов C++. Кроме того, вычисления во время компиляции (в разум
+ных пределах) в некотором смысле «бесплатны».
+
+На момент написания этой книги у интерпретатора есть ряд ограниче
+ний[^22]. Выделение памяти под объекты, да и просто выделение памяти за
+прещены (хотя встроенные массивы работают). Статические данные,
+вставки на ассемблере и небезопасные средства, такие как объединения
+(`union`) и некоторые приведения типов (`cast`), также под запретом. Мно
+жество ограничений на то, что можно сделать во время компиляции, на
+ходится под постоянным давлением. Задумка в том, чтобы разрешить
+интерпретировать во время компиляции все, что находится в безопас
+ном множестве D. В конце концов, способность интерпретировать код во
+время компиляции – это новшество, открывающее очень интересные
+возможности, которые заслуживают дальнейшего исследования.
+
[^1]: Функция `find` ищет «иголку» (`needle`) в «стоге сена» (`haystack`). – *Прим. науч. ред.*
+[^2]: Следует подчеркнуть, что проверка выполнения подобных соглашений выполняется на этапе компиляции, и если компилятор обмануть, например с помощью приведения типов, то соглашения можно нарушить. Пример: `(cast(int[])data)[5] = 42;` даст именно то, что ожидается. Но это уже моветон. – *Прим. науч. ред.*
+[^3]: На самом деле, `in` означает `scope const`, однако семантика `scope` не до конца продумана и, возможно, в дальнейшем `scope` вообще исчезнет из языка. – *Прим. науч. ред.*
+[^4]: Описание этой части языка намеренно не было включено в оригинал книги, но поскольку эта возможность есть в текущих реализациях языка, мы добавили ее описание. – *Прим. науч. ред.*
+[^5]: На самом деле, их *можно* инициализировать только константами, а можно вообще не инициализировать (тогда они принимают значение по умолчанию). – *Прим. науч. ред.*
+[^6]: Именно этот момент делает «частичный порядок» «частичным». В случае отношения полного порядка (например ≤ для действительных чисел) неупорядоченных элементов нет.
+[^7]: Речь о ежедневном комиксе американского художника Билла Уоттерсона «Кельвин и Хоббс». – *Прим. пер.*
+[^8]: Тот же подход используют ML и другие реализации функциональных языков.
+[^9]: Премия Дарвина – виртуальная премия, ежегодно присуждаемая тем, кто наиболее глупым способом лишился жизни или способности к зачатию, в результате не внеся свой вклад в генофонд человечества (и тем самым улучшив его). – *Прим. пер.*
+[^10]: Хотя в приведенном примере о типе аргумента `a` ничего не сказано, текущая на момент выпуска книги версия компилятора 2.057 работает указанным образом только в том случае, если `a` – массив. В ответ на пример `(7).someprop()` для функции `void someprop(int a){}` компилятор скажет, что нет свойства `someprop` для типа `int`. – *Прим. науч. ред.*
+[^11]: Версия компилятора 2.057 не поддерживает атрибуты, объявляемые пользователем. В будущем такая поддержка может появиться. – *Прим. науч. ред.*
+[^12]: На момент выхода книги такое поведение по умолчанию носило рекомендательный характер. Функция без аргументов и без атрибута `@property` могла вызываться как с пустой парой скобок, так и без. Так сделано из соображений обратной совместимости с кодом, написанным до ввода данного атрибута. Заставить компилятор проверять корректность использования скобок позволяет ключ компиляции `-property` (`dmd` 2.057). В дальнейшем некорректное применение скобок может быть запрещено, поэтому там, где требуется функция, ведущая себя как свойство, следует использовать `@property`. – *Прим. науч. ред.*
+[^13]: Инлайнинг (inline-подстановка) – подстановка кода функции в месте ее вызова. Позволяет снизить накладные расходы на вызов функции при передаче аргументов, переходе по адресу, обратном переходе, а также нагрузку на кэш памяти процессора. В версиях языка C до C99 это достигалось с помощью макросов, в C99 и С++ появились ключевое слово `inline` и inline-подстановка методов классов, описанных внутри описания класса. В языке D inline-подстановка отдается на откуп компилятору. Компилятор будет сам решать, где рационально ее применить, а где – нет. – *Прим. науч. ред.*
+[^14]: Reduce (англ.) – сокращать, сводить. – *Прим. науч. ред.*
+[^15]: Описание этой части языка намеренно не было включено в оригинал книги, но поскольку эта возможность присутствует в текущих реализациях языка, мы добавили ее описание. – *Прим. науч. ред.*
+[^16]: Описание этой части языка намеренно не было включено в оригинал книги, но поскольку эта возможность присутствует в текущих реализациях языка, мы добавили ее описание в перевод. – *Прим. науч. ред.*
+[^17]: В данном контексте речь идет об изменениях, которые повлияли бы на последующие вызовы функции, например об изменении глобальных переменных. – *Прим. науч. ред.*
+[^18]: «O» большое – математическое обозначение, применяемое при оценке асимптотической сложности алгоритма. – *Прим. ред.*
+[^19]: Равенство *c* нулю также допустимо, но соответствующая теоретическая часть гораздо сложнее, потому ограничимся значениями *c* > 0.
+[^20]: Непонятно как, но алгоритм Евклида всегда умудряется попадать в хорошие (хм...) книги по программированию.
+[^21]: Распространенный в США и Канаде мем, изначально связанный с фаст-фудом. – *Прим. ред.*
+[^22]: Многие из этих ограничений уже сняты. – *Прим. науч. ред.*