Difference between revisions of "Reading the documentation (in Russian)"

From D Wiki
Jump to: navigation, search
(Created page with "=== Введение === <p> Сравнительно недавно на свет появился язык программирования D: мощный инструме...")
 
 
(3 intermediate revisions by 3 users not shown)
Line 7: Line 7:
 
Я не могу придумать уместного перевода слова scope на русский язык, и чтобы не вводить никого в заблуждение, введу термин "скоуп" как новый. Проще всего понять скоуп как участок кода от фигурной скобки до фигурной скобки:  
 
Я не могу придумать уместного перевода слова scope на русский язык, и чтобы не вводить никого в заблуждение, введу термин "скоуп" как новый. Проще всего понять скоуп как участок кода от фигурной скобки до фигурной скобки:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
import std.stdio;
 
import std.stdio;
  
Line 18: Line 18:
 
writeln( number); // |
 
writeln( number); // |
 
} // v скоуп 1 закончился
 
} // v скоуп 1 закончился
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Область кода за пределами всех скобок – это тоже скоуп, "outer", или внешний:  
 
Область кода за пределами всех скобок – это тоже скоуп, "outer", или внешний:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
import std.stdio; //^ это внешний скоуп
 
import std.stdio; //^ это внешний скоуп
 
//|
 
//|
Line 35: Line 35:
 
//|
 
//|
 
struct Struct //|
 
struct Struct //|
{}</pre>
+
{}</syntaxhighlight>
 
<p>
 
<p>
 
Скоупы нужны для смыслового деления кода, для выделения участков кода, которые выполняются или компилируются в определенных условиях, для ограничения области перечисления полей в структурах и для многих других целей. Команды, помещенные в разные скоупы, ведут себя по разному.  
 
Скоупы нужны для смыслового деления кода, для выделения участков кода, которые выполняются или компилируются в определенных условиях, для ограничения области перечисления полей в структурах и для многих других целей. Команды, помещенные в разные скоупы, ведут себя по разному.  
Line 41: Line 41:
 
===== Статические и динамические скоупы =====
 
===== Статические и динамические скоупы =====
 
<p>
 
<p>
Мы уже говорили, что все команды выполняются последовательно – одна за другой. Однако это происходит только в ''динамических'' скоупах. Существуют и другие – ''статические'' – скоупы. Например, когда мы рассматривали первые примеры, мы не говорили, для чего нужна область кода за пределами <tt>main(){…}</tt> и что означают команды там. Расписывая построчно алгоритм программы, эти команды не учитывались.  
+
Мы уже говорили, что все команды выполняются последовательно – одна за другой. Однако это происходит только в ''динамических'' скоупах. Существуют и другие – ''статические'' – скоупы. Например, когда мы рассматривали первые примеры, мы не говорили, для чего нужна область кода за пределами <tt>main(){…}</tt> и что означают команды там. Расписывая построчно алгоритм программы, мы эти команды не учитывали.  
 
</p>
 
</p>
 
<p>
 
<p>
Line 93: Line 93:
 
Следующий пример иллюстрирует эти скоупы. Здесь статические скоупы и команды помечены буквой s, а динамические – буквой d.  
 
Следующий пример иллюстрирует эти скоупы. Здесь статические скоупы и команды помечены буквой s, а динамические – буквой d.  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
s import std.stdio; //внешний скоуп кода программы - статический
 
s import std.stdio; //внешний скоуп кода программы - статический
 
s
 
s
Line 138: Line 138:
 
d }
 
d }
 
d }
 
d }
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Обратите внимание на несколько вещей. Статические скоупы могут быть внутри динамических и наоборот, при этом они не конфликтуют: характер скоупа определяет внутренний скоуп, не влияя на внешний. Внешний скоуп продолжается после закрытия внутреннего и сохраняет свой характер. Может показаться странным, что код выполняется построчно сверху вниз, но при этом мы видим динамические скоупы до <tt>main()</tt> – это видно в члене-функции структуры и в регулярной функции. Действительно, если программа начинает выполняться с <tt>main(){</tt>, то как она попадет в эти скоупы сверху? Все просто: как только в теле <tt>main()</tt> появляется одна из этих функций, она начинает выполняться, при этом "указатель", который сопровождает выполняемые строчки, перескакивает в тело этой функции. Как только все строки внутри функции выполнены, указатель возвращается туда, откуда его вызвали. В теле функции может быть вызвана и другая функция – любое количество функций может выполняться одна внутри другой. При этом внешняя функция всегда ожидает выполнения внутренней. Подробнее об этой концепции можно узнать в главе "функции" (http://ddili.org/ders/d.en/functions.html).  
 
Обратите внимание на несколько вещей. Статические скоупы могут быть внутри динамических и наоборот, при этом они не конфликтуют: характер скоупа определяет внутренний скоуп, не влияя на внешний. Внешний скоуп продолжается после закрытия внутреннего и сохраняет свой характер. Может показаться странным, что код выполняется построчно сверху вниз, но при этом мы видим динамические скоупы до <tt>main()</tt> – это видно в члене-функции структуры и в регулярной функции. Действительно, если программа начинает выполняться с <tt>main(){</tt>, то как она попадет в эти скоупы сверху? Все просто: как только в теле <tt>main()</tt> появляется одна из этих функций, она начинает выполняться, при этом "указатель", который сопровождает выполняемые строчки, перескакивает в тело этой функции. Как только все строки внутри функции выполнены, указатель возвращается туда, откуда его вызвали. В теле функции может быть вызвана и другая функция – любое количество функций может выполняться одна внутри другой. При этом внешняя функция всегда ожидает выполнения внутренней. Подробнее об этой концепции можно узнать в главе "функции" (http://ddili.org/ders/d.en/functions.html).  
Line 148: Line 148:
 
Также заметим, что в конце <tt>main()</tt>мы свободно открыли и закрыли очередной скоуп. Этот скоуп, так же как и внешний по отношению к нему, стал динамическим. В языке D можно организовывать скоупы даже без их привязки к statement'ам, функциям, структурам или классам, однако это используется крайне редко и так можно делать только в динамическом скоупе.  
 
Также заметим, что в конце <tt>main()</tt>мы свободно открыли и закрыли очередной скоуп. Этот скоуп, так же как и внешний по отношению к нему, стал динамическим. В языке D можно организовывать скоупы даже без их привязки к statement'ам, функциям, структурам или классам, однако это используется крайне редко и так можно делать только в динамическом скоупе.  
 
</p>
 
</p>
 +
 
===== Пространство имен =====
 
===== Пространство имен =====
 
<p>
 
<p>
Line 155: Line 156:
 
Внутреннее пространство имен включает все внешние пространства, но не наоборот. Пример:  
 
Внутреннее пространство имен включает все внешние пространства, но не наоборот. Пример:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
import std.stdio;
 
import std.stdio;
  
Line 168: Line 169:
 
write( b); //!!! не копмилируется - b недоступна во внешнем скоупе
 
write( b); //!!! не копмилируется - b недоступна во внешнем скоупе
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
С того момента, как была объявлена переменная<tt> a</tt>, она доступна для обращения любой строчкой ниже. То же касается и переменной <tt>b</tt>. Однако переменная <tt>b</tt> была объявлена внутри вложенного скоупа, поэтому пространство имени переменной <tt>b</tt> начинается со строки ее объявления и заканчивается, как только заканчивается этот скоуп. Переменная же <tt>a </tt>доступна и во вложенном скоупе, она перестает быть доступной только по выходу из <tt>main(){...}</tt>. По этой причине последняя команда вывода на экран не может быть скомпилирована.  
 
С того момента, как была объявлена переменная<tt> a</tt>, она доступна для обращения любой строчкой ниже. То же касается и переменной <tt>b</tt>. Однако переменная <tt>b</tt> была объявлена внутри вложенного скоупа, поэтому пространство имени переменной <tt>b</tt> начинается со строки ее объявления и заканчивается, как только заканчивается этот скоуп. Переменная же <tt>a </tt>доступна и во вложенном скоупе, она перестает быть доступной только по выходу из <tt>main(){...}</tt>. По этой причине последняя команда вывода на экран не может быть скомпилирована.  
Line 178: Line 179:
 
Еще одно правило касается определений функций, наследованных классов и statement'ов. Суть в том, что все имена, которые были введены в списке аргументов функции, или использовались для определения цикла, или существовали в родительском классе, входят в пространство имен образовавшегося скоупа. Пример:  
 
Еще одно правило касается определений функций, наследованных классов и statement'ов. Суть в том, что все имена, которые были введены в списке аргументов функции, или использовались для определения цикла, или существовали в родительском классе, входят в пространство имен образовавшегося скоупа. Пример:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
void main(){
 
void main(){
 
int a;
 
int a;
Line 186: Line 187:
 
}
 
}
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Как мы видим, переменная <tt>i</tt> была объявлена внутри определения цикла <tt>for</tt>, и она доступна в скоупе этого цикла.  
 
Как мы видим, переменная <tt>i</tt> была объявлена внутри определения цикла <tt>for</tt>, и она доступна в скоупе этого цикла.  
Line 193: Line 194:
 
Как известно, дважды вводить одно и то же имя в одном пространстве имен нельзя – такой код не скомпилируется. Есть одно исключение: при перечислении аргументов члена-функции класса или структуры может использоваться имя какого-то поля этого класса или структуры. Как говорят, оригинальное имя при этом "затирается", и по этому имени теперь можно обратиться только к аргументу функции. Но имя оригинального поля не пропадает: оно все еще доступно через ключевое слово <tt>this</tt> с точкой после него. Так, следующий код описывает класс, конструктор которого присваивает полю класса<tt> number</tt> значение своего аргумента, который называется так же:  
 
Как известно, дважды вводить одно и то же имя в одном пространстве имен нельзя – такой код не скомпилируется. Есть одно исключение: при перечислении аргументов члена-функции класса или структуры может использоваться имя какого-то поля этого класса или структуры. Как говорят, оригинальное имя при этом "затирается", и по этому имени теперь можно обратиться только к аргументу функции. Но имя оригинального поля не пропадает: оно все еще доступно через ключевое слово <tt>this</tt> с точкой после него. Так, следующий код описывает класс, конструктор которого присваивает полю класса<tt> number</tt> значение своего аргумента, который называется так же:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
class MyClass
 
class MyClass
 
{
 
{
Line 206: Line 207:
 
}
 
}
 
}
 
}
</pre>
+
</syntaxhighlight>
 
===== Рекомендация =====
 
===== Рекомендация =====
 
<p>
 
<p>
В целом при размещении нужных команд внутри динамического скоупа у программиста имеется некоторая вольность, не говоря уже о статических. Хотя синтаксис D этого не требует, существуют "правила хорошего тона" в написании программ. Так, чаще всего вводить определения и объявления рекомендуется непосредственно перед тем, как ими начинают пользоваться. Если вы объявляете переменные непосредственно перед тем, как они входят в дело, отпадает необходимость комментариев: сразу понятно, для чего нужна эта переменная. Также место в памяти будет выделяться по мере надобности, а не сразу  большим объемом, а если пространства имен уже использованных и уже ненужных переменных будут постепенно закрываться, то место будет освобождаться по мере выполнения программы и она в целом не займет много места в ОЗУ.  
+
В целом при размещении нужных команд внутри динамического скоупа у программиста имеется некоторая вольность, не говоря уже о статических. Хотя синтаксис D этого не требует, существуют "правила хорошего тона" в написании программ. Так, чаще всего вводить определения и объявления рекомендуется непосредственно перед тем, как ими начинают пользоваться. Если вы объявляете переменные непосредственно перед тем, как они входят в дело, отпадает необходимость комментариев: сразу понятно, для чего нужна эта переменная.  
 
</p>
 
</p>
 
<p>
 
<p>
Так же стараются держать пространства имен по возможности беднее. Чем больше имен доступно в одном скоупе, тем больше вероятность запутаться в этом обилии имен и больше возможность того, что одно имя затрет другое (например, такое возможно при импорте сразу нескольких библиотек с конфликтующими именами). Для этих целей переменные объявляют так "глубоко", как это возможно, чтобы их пространства имен кончались возможно скорее. Также зачастую из библиотек импортируют только одну или несколько необходимых функций или типов, не импортируя ее целиком.  
+
Так же стараются держать пространства имен по возможности беднее. Чем больше имен доступно в одном скоупе, тем больше вероятность запутаться в этом обилии имен и больше возможность того, что одно имя затрет другое (хотя чаще конфликт имен приводит к ошибке компиляции. Например, такое возможно при импорте сразу нескольких библиотек, содержащих одинаковые). Для этих целей переменные объявляют так "глубоко", как это возможно, чтобы их пространства имен кончались возможно скорее. Также зачастую из библиотек импортируют только одну или несколько необходимых функций или типов, не импортируя ее целиком. Более того, если переменные объявляются только в том скоупе, где они используются, то место в памяти для них будет выделяться по мере надобности, а не сразу  большим объемом, и если пространства имен уже использованных и уже ненужных переменных будут постепенно закрываться, то место будет освобождаться по мере выполнения программы и она в целом не займет много места в ОЗУ.  
 
</p>
 
</p>
 
<p>
 
<p>
Line 237: Line 238:
 
Файл mymodule.d:  
 
Файл mymodule.d:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
/* Это модуль, который  *
 
/* Это модуль, который  *
 
  * называется "mymodule" */
 
  * называется "mymodule" */
Line 276: Line 277:
 
  * тем не менее, он компилируется в составе другого модуля, содержащего main() *
 
  * тем не менее, он компилируется в составе другого модуля, содержащего main() *
 
  * как его часть. */
 
  * как его часть. */
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Файл app.d:  
 
Файл app.d:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
import std.stdio;
 
import std.stdio;
 
import mymodule; // теперь функция int fastDoubling(int arg) доступна
 
import mymodule; // теперь функция int fastDoubling(int arg) доступна
Line 288: Line 289:
 
writeln( result);
 
writeln( result);
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Как мы видим, функция, определенная в модуле mymodule.d, доступна в другом модуле при подключении. Файл app.d компилируется и скомпилированный код выдает верный результат:  
 
Как мы видим, функция, определенная в модуле mymodule.d, доступна в другом модуле при подключении. Файл app.d компилируется и скомпилированный код выдает верный результат:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
30
 
30
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Остальные особенности работы с модулями и библиотеками вы найдете  в соответствующих главах учебников и других источниках.  
 
Остальные особенности работы с модулями и библиотеками вы найдете  в соответствующих главах учебников и других источниках.  
Line 309: Line 310:
 
Часто для пользования библиотекой не нужно знать всех подробностей. Объясним это на примере. Мы знаем, как в общем случае объявляется функция:  
 
Часто для пользования библиотекой не нужно знать всех подробностей. Объясним это на примере. Мы знаем, как в общем случае объявляется функция:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
type_name function_name(type1 arg1, type2 arg2, /*...*/ typen argn)
 
type_name function_name(type1 arg1, type2 arg2, /*...*/ typen argn)
 
{
 
{
 
/*...*/
 
/*...*/
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Объявление состоит из типа, который возвращает функция, ее названия и списка аргументов в скобках, где указывается тип и имя для каждого аргумента парами, которые перечисляются запятой. Фигурные скобки, ограничивающие тело функции, не указываются в документации.  
 
Объявление состоит из типа, который возвращает функция, ее названия и списка аргументов в скобках, где указывается тип и имя для каждого аргумента парами, которые перечисляются запятой. Фигурные скобки, ограничивающие тело функции, не указываются в документации.  
Line 321: Line 322:
 
Однако в действительности мы можем встретить более сложный и непонятный интерфейс функции:  
 
Однако в действительности мы можем встретить более сложный и непонятный интерфейс функции:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
/* ?    ?        ?        ?    type  func_name  ?    arg_type  arg  */
 
/* ?    ?        ?        ?    type  func_name  ?    arg_type  arg  */
 
pure  nothrow  @property  @safe  void  timeOfDay( in  TimeOfDay  tod);  
 
pure  nothrow  @property  @safe  void  timeOfDay( in  TimeOfDay  tod);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
  (взято из модуля std.datitime: http://dlang.org/phobos/std_datetime.html#.DateTime.timeOfDay)  
+
  (взято из модуля std.datetime: http://dlang.org/phobos/std_datetime.html#.DateTime.timeOfDay)  
 
</p>
 
</p>
 
<p>
 
<p>
 
Такого кода не нужно пугаться. Определения в документации могут содержать много непонятных слов, но структура у этого определения всегда одна и та же. Первое, что нужно делать всегда – выделить самое главное. Отбросив лишнее, от нашего "страшного" определения останется:  
 
Такого кода не нужно пугаться. Определения в документации могут содержать много непонятных слов, но структура у этого определения всегда одна и та же. Первое, что нужно делать всегда – выделить самое главное. Отбросив лишнее, от нашего "страшного" определения останется:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
/*type  func_name  arg_type  arg  */
 
/*type  func_name  arg_type  arg  */
 
void    timeOfDay( TimeOfDay  tod);  
 
void    timeOfDay( TimeOfDay  tod);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Теперь это функция, которая называется <tt>timeOfDay</tt>, не возвращает никакого значения (тип – <tt>void</tt>) и имеет один аргумет <tt>tod</tt> типа <tt>TimeOfDay</tt>. В большинстве случаев это все, что нужно знать о <tt>timeOfDay</tt> для того, чтобы использовать ее в своей программе. Поясним, по какой схеме нужно отбрасывать "лишние" слова для получения красивого и краткого определения.  
 
Теперь это функция, которая называется <tt>timeOfDay</tt>, не возвращает никакого значения (тип – <tt>void</tt>) и имеет один аргумет <tt>tod</tt> типа <tt>TimeOfDay</tt>. В большинстве случаев это все, что нужно знать о <tt>timeOfDay</tt> для того, чтобы использовать ее в своей программе. Поясним, по какой схеме нужно отбрасывать "лишние" слова для получения красивого и краткого определения.  
Line 342: Line 343:
 
От функции должна остаться такая схема:  
 
От функции должна остаться такая схема:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
type_name function_name(types arguments);  
 
type_name function_name(types arguments);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
где типы аргументов и их имена идут парами через запятую. Этого достаточно, чтобы вызвать функцию – по ее названию – и "скормить" ей все необходимые ей параметры нужных типов, а также использовать результат этой функции, зная ее тип. Название функции и название ее аргументов обычно позволяет понять, для чего эта функция нужна и что она делает. Если этого недостаточно, то смысл функции можно понять из комментариев в коде модуля, в документации или по примерам.  
 
где типы аргументов и их имена идут парами через запятую. Этого достаточно, чтобы вызвать функцию – по ее названию – и "скормить" ей все необходимые ей параметры нужных типов, а также использовать результат этой функции, зная ее тип. Название функции и название ее аргументов обычно позволяет понять, для чего эта функция нужна и что она делает. Если этого недостаточно, то смысл функции можно понять из комментариев в коде модуля, в документации или по примерам.  
Line 351: Line 352:
 
Если перед типом какой-то переменной стоит ключевое слово <tt>in</tt>, <tt> out</tt> или <tt>ref</tt>, то имеет смысл урезать определение до такого состояния, где в списке аргументов могут встречаться не только пары, но и тройки:  
 
Если перед типом какой-то переменной стоит ключевое слово <tt>in</tt>, <tt> out</tt> или <tt>ref</tt>, то имеет смысл урезать определение до такого состояния, где в списке аргументов могут встречаться не только пары, но и тройки:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
type_name function_name(keywords types arguments);  
 
type_name function_name(keywords types arguments);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Здесь вместо<tt> keywords</tt> перед некоторыми из аргументов могут стоять перечисленные выше ключевые слова. Их нельзя опускать, потому что они непосредственно связаны с тем, что и как делает функция, в частности, с ее сторонними эффектами. Так, некоторые аргументы функции могут выступать в роли результатов ее работы, а не входных данных.  
 
Здесь вместо<tt> keywords</tt> перед некоторыми из аргументов могут стоять перечисленные выше ключевые слова. Их нельзя опускать, потому что они непосредственно связаны с тем, что и как делает функция, в частности, с ее сторонними эффектами. Так, некоторые аргументы функции могут выступать в роли результатов ее работы, а не входных данных.  
Line 363: Line 364:
 
<tt>ref</tt> – это ключевое слово делает аргумент ссылочным типом для функции. Поэтому функция может использовать такие аргументы и как входные данные, и может модифицировать сами переменные. Часто используется для подобных функций:  
 
<tt>ref</tt> – это ключевое слово делает аргумент ссылочным типом для функции. Поэтому функция может использовать такие аргументы и как входные данные, и может модифицировать сами переменные. Часто используется для подобных функций:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
void Twice( ref int arg)
 
void Twice( ref int arg)
 
{
 
{
 
arg *= 2;
 
arg *= 2;
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Эта функция удваивает значение своего аргумента. Можно сказать, что аргумент такой функции является и входным данным, и результатом ее работы одновременно. Отметим, что в качестве такого параметра нельзя передать литерал:  
 
Эта функция удваивает значение своего аргумента. Можно сказать, что аргумент такой функции является и входным данным, и результатом ее работы одновременно. Отметим, что в качестве такого параметра нельзя передать литерал:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
twice( 5); // <- compilation error
 
twice( 5); // <- compilation error
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
<tt>in</tt> – таким ключевым словом помечаются аргументы, которые функция может использовать только как входные данные. Функция не может модифицировать их значения, только считывать.  
 
<tt>in</tt> – таким ключевым словом помечаются аргументы, которые функция может использовать только как входные данные. Функция не может модифицировать их значения, только считывать.  
Line 391: Line 392:
 
Обычно структура, класс или интерфейс объявляются просто:  
 
Обычно структура, класс или интерфейс объявляются просто:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
struct    Struct;
 
struct    Struct;
 
class    Class;
 
class    Class;
 
interface Interface;  
 
interface Interface;  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Смысл обычно имеет не название структуры, класса или интерфейса, а поля и методы. В документации они, как правило, указываются ниже, равно как и в коде программы. На сайте библиотеки Фобос все доступные поля и члены-функции указаны рядом с объявлением структуры, класса или интерфейса, по ссылкам можно перейти к их определениям.  
 
Смысл обычно имеет не название структуры, класса или интерфейса, а поля и методы. В документации они, как правило, указываются ниже, равно как и в коде программы. На сайте библиотеки Фобос все доступные поля и члены-функции указаны рядом с объявлением структуры, класса или интерфейса, по ссылкам можно перейти к их определениям.  
Line 405: Line 406:
 
Большое внимание нужно уделять родительским классам и интерфейсам, если класс или интерфейс являются наследованными. Все поля и члены-функции, доступные в родительском классе/интерфейсе, доступны и в наследованном. Список родительских классов и интерфейсов следует после двоеточия после названия класса или интерфейса:  
 
Большое внимание нужно уделять родительским классам и интерфейсам, если класс или интерфейс являются наследованными. Все поля и члены-функции, доступные в родительском классе/интерфейсе, доступны и в наследованном. Список родительских классов и интерфейсов следует после двоеточия после названия класса или интерфейса:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
class SubClass : SuperClass, Interface1, Interface2;  
 
class SubClass : SuperClass, Interface1, Interface2;  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Также после названия класса, структуры или интерфейса могут следовать скобки. Это те же скобки, которые могут появиться перед списком аргументов у функции: это список параметров шаблона, который мы рассмотрим позже.  
 
Также после названия класса, структуры или интерфейса могут следовать скобки. Это те же скобки, которые могут появиться перед списком аргументов у функции: это список параметров шаблона, который мы рассмотрим позже.  
Line 422: Line 423:
 
Определение функции в общем виде может выглядеть так:  
 
Определение функции в общем виде может выглядеть так:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
keywords... behavior... safety_level type_name function_name(keywords types args = default) keywords...;  
 
keywords... behavior... safety_level type_name function_name(keywords types args = default) keywords...;  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Здесь подчеркнуты обязательные слова, без которых объявить функцию нельзя. Как мы видим, это те самые слова, которые мы оставляем, урезая функцию до минимума. Рассмотрим, что может означать каждое слово в этой схеме.  
 
Здесь подчеркнуты обязательные слова, без которых объявить функцию нельзя. Как мы видим, это те самые слова, которые мы оставляем, урезая функцию до минимума. Рассмотрим, что может означать каждое слово в этой схеме.  
Line 432: Line 433:
 
В указанной схеме тип фигурирует дважды: как тип, который возвращает функция <tt>type_name</tt>, и как тип аргументов <tt>types</tt>. Стоит отдельно подчеркнуть, что тип необязательно состоит из одного слова. Несколько примеров:  
 
В указанной схеме тип фигурирует дважды: как тип, который возвращает функция <tt>type_name</tt>, и как тип аргументов <tt>types</tt>. Стоит отдельно подчеркнуть, что тип необязательно состоит из одного слова. Несколько примеров:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
int
 
int
 
int[5]
 
int[5]
Line 439: Line 440:
 
immutable int
 
immutable int
 
immutable(int)[]
 
immutable(int)[]
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Все эти записи представляют собой тот или иной тип. Особого внимания заслуживают типы, помеченные как <tt>const</tt> или <tt>immutable</tt> – хотя название типа состоит из двух слов, разделенных запятой, оба эти слова, упомянутые в определении функции, будут относиться к типу, поэтому слово <tt>const</tt> или <tt>immutable</tt> не входит в <tt>keywords</tt>:  
 
Все эти записи представляют собой тот или иной тип. Особого внимания заслуживают типы, помеченные как <tt>const</tt> или <tt>immutable</tt> – хотя название типа состоит из двух слов, разделенных запятой, оба эти слова, упомянутые в определении функции, будут относиться к типу, поэтому слово <tt>const</tt> или <tt>immutable</tt> не входит в <tt>keywords</tt>:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
/*        keyword  |--type--|  arg */
 
/*        keyword  |--type--|  arg */
 
int twice(  ref    const  int  arg)
 
int twice(  ref    const  int  arg)
Line 450: Line 451:
 
return result;
 
return result;
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
То есть в общей схеме <tt>type_name</tt> и <tt>types </tt>может быть представлен комбинацией слов, а не только одним словом. Чтобы не запутаться, можно делать запись без пробелов:  
 
То есть в общей схеме <tt>type_name</tt> и <tt>types </tt>может быть представлен комбинацией слов, а не только одним словом. Чтобы не запутаться, можно делать запись без пробелов:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
int twice( ref  const(int)  arg);  
 
int twice( ref  const(int)  arg);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Тип, возвращаемый функцией, может быть обозначен как <tt>auto</tt>. В этом случае этот тип выводится компилятором на основании выражения, возвращаемого оператором <tt>return</tt>. Для аргументов функции ключевое слово <tt>auto</tt> недопустимо.  
 
Тип, возвращаемый функцией, может быть обозначен как <tt>auto</tt>. В этом случае этот тип выводится компилятором на основании выражения, возвращаемого оператором <tt>return</tt>. Для аргументов функции ключевое слово <tt>auto</tt> недопустимо.  
Line 466: Line 467:
 
Слово <tt>inout</tt> обязательно появляется в определении дважды: перед типом функции и перед одним из аргументов:  
 
Слово <tt>inout</tt> обязательно появляется в определении дважды: перед типом функции и перед одним из аргументов:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
inout int twice( inout int arg);  
 
inout int twice( inout int arg);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Это ключевое слово означает, что модифицируемость (mutability) функции определяется модифицируемостью ее аргумента. Модифицируемость может быть трех видов: ''mutable '' (устанавливается по умолчанию), <tt>const</tt> и <tt>immutable</tt>. Со словом <tt>inout </tt>результат, который возвращает функция, имеет ту же mutability, что и аргумент. По этой причине часто пишут так:  
 
Это ключевое слово означает, что модифицируемость (mutability) функции определяется модифицируемостью ее аргумента. Модифицируемость может быть трех видов: ''mutable '' (устанавливается по умолчанию), <tt>const</tt> и <tt>immutable</tt>. Со словом <tt>inout </tt>результат, который возвращает функция, имеет ту же mutability, что и аргумент. По этой причине часто пишут так:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
inout(int) twice( inout(int) arg);  
 
inout(int) twice( inout(int) arg);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Эта запись может быть более понятна: вместо <tt>inout</tt> может стоять слово <tt>const</tt>, <tt>immutable</tt> или ничего не стоять в зависимости от того, от какого параметра вызывается функция. Из последнего кода можно сделать вывод, что <tt>inout</tt> можно отнести как к типу, так и к ключевому слову в перечислении аргументов. Однако стоит помнить, что нельзя одновременно использовать одно из слов <tt>ref</tt>, <tt>in</tt> или <tt>out</tt> вместе с <tt>inout</tt>.  
 
Эта запись может быть более понятна: вместо <tt>inout</tt> может стоять слово <tt>const</tt>, <tt>immutable</tt> или ничего не стоять в зависимости от того, от какого параметра вызывается функция. Из последнего кода можно сделать вывод, что <tt>inout</tt> можно отнести как к типу, так и к ключевому слову в перечислении аргументов. Однако стоит помнить, что нельзя одновременно использовать одно из слов <tt>ref</tt>, <tt>in</tt> или <tt>out</tt> вместе с <tt>inout</tt>.  
Line 482: Line 483:
 
Рассмотрим ключевые слова, которые могут находиться перед именем функции. Это могут быть: <tt>ref</tt>, <tt>inout</tt> или <tt>auto ref</tt>. <tt>inout</tt> мы уже рассмотрели: он обязательно появляется дважды – перед названием функции и перед одним из аргументов. <tt>ref</tt> означает примерно то же, что и ключевое слово<tt> ref</tt> для аргументов. Аргументы, помеченные словом <tt>ref</tt>, передаются как псевдонимы (<tt>alias</tt>) параметров. Точно так же результат, возвращаемый функцией, помеченной как <tt>ref</tt>, возвращается как псевдоним одной из переменных в теле функции. Хороший пример:  
 
Рассмотрим ключевые слова, которые могут находиться перед именем функции. Это могут быть: <tt>ref</tt>, <tt>inout</tt> или <tt>auto ref</tt>. <tt>inout</tt> мы уже рассмотрели: он обязательно появляется дважды – перед названием функции и перед одним из аргументов. <tt>ref</tt> означает примерно то же, что и ключевое слово<tt> ref</tt> для аргументов. Аргументы, помеченные словом <tt>ref</tt>, передаются как псевдонимы (<tt>alias</tt>) параметров. Точно так же результат, возвращаемый функцией, помеченной как <tt>ref</tt>, возвращается как псевдоним одной из переменных в теле функции. Хороший пример:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
ref int greater(ref int first, ref int second)
 
ref int greater(ref int first, ref int second)
 
{
 
{
 
return (first > second) ? first : second;
 
return (first > second) ? first : second;
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Эта функция возвращает больший из двух своих аргументов по ссылке, то есть не копируя его значение. Использование ключевого слова <tt>ref</tt> перед функцией имеет определенные ограничения и особенности, о которых можно в подробностях узнать в соответствующей главе учебника или в других источниках (http://ddili.org/ders/d.en/functions_more.html).  
 
Эта функция возвращает больший из двух своих аргументов по ссылке, то есть не копируя его значение. Использование ключевого слова <tt>ref</tt> перед функцией имеет определенные ограничения и особенности, о которых можно в подробностях узнать в соответствующей главе учебника или в других источниках (http://ddili.org/ders/d.en/functions_more.html).  
Line 550: Line 551:
 
Как уже было сказано, ключевое слово <tt>inout</tt> всегда появляется дважды в определении. Один раз это слово определяет модифицируемость типа результата, который возвращает функция. Случай, когда вид этой модифицируемости выводится из аргументов функции, мы уже рассмотрели. Модифицируемость также может быть выведена из модифицируемости всего объекта, на котором вызван метод: структуре или классе. Для этого второй раз ключевое слово <tt>inout</tt> употребляется не перед одним из аргументов, а после скобок списка аргументов:  
 
Как уже было сказано, ключевое слово <tt>inout</tt> всегда появляется дважды в определении. Один раз это слово определяет модифицируемость типа результата, который возвращает функция. Случай, когда вид этой модифицируемости выводится из аргументов функции, мы уже рассмотрели. Модифицируемость также может быть выведена из модифицируемости всего объекта, на котором вызван метод: структуре или классе. Для этого второй раз ключевое слово <tt>inout</tt> употребляется не перед одним из аргументов, а после скобок списка аргументов:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
struct Struct
 
struct Struct
 
{
 
{
Line 559: Line 560:
 
}
 
}
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Метод <tt>memberFunc </tt>возвращает число <tt>int </tt>такой же модифицируемости, какой обладает весь объект <tt>Struct</tt>:  
 
Метод <tt>memberFunc </tt>возвращает число <tt>int </tt>такой же модифицируемости, какой обладает весь объект <tt>Struct</tt>:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
immutable(Struct) S = Struct(10);
 
immutable(Struct) S = Struct(10);
 
auto num = S.memberFunc;
 
auto num = S.memberFunc;
 
assert( is( typeof( num) == immutable(int) ) );
 
assert( is( typeof( num) == immutable(int) ) );
 
//  тип num это immutable(int)
 
//  тип num это immutable(int)
</pre>
+
</syntaxhighlight>
 
==== keywords при аргументах функции====
 
==== keywords при аргументах функции====
 
<p>
 
<p>
Line 577: Line 578:
 
Ключевое слово <tt>lazy</tt> меняет порядок вычисления функций, если параметры одной функции зависят от результатов другой. Рассмотрим пример:  
 
Ключевое слово <tt>lazy</tt> меняет порядок вычисления функций, если параметры одной функции зависят от результатов другой. Рассмотрим пример:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
a = Div( Sum( b, c), d);  
 
a = Div( Sum( b, c), d);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
  В этом примере сначала вычисляется значение функции <tt>Sum </tt>от параметров <tt>b</tt>  и <tt>с</tt>, а затем этот результат используется как первый параметр функции <tt>Div</tt>: функция<tt> Sum</tt> вычисляется первой. Однако на ноль делить нельзя, поэтому если <tt>d=0</tt>, то в вычислении <tt>Sum( b, c) </tt> нет никакого смысла: это окажется пустой потерей времени. По этой причине первый аргумент функции <tt>Div</tt> может быть помечен как <tt>lazy</tt> (ленивый):  
 
  В этом примере сначала вычисляется значение функции <tt>Sum </tt>от параметров <tt>b</tt>  и <tt>с</tt>, а затем этот результат используется как первый параметр функции <tt>Div</tt>: функция<tt> Sum</tt> вычисляется первой. Однако на ноль делить нельзя, поэтому если <tt>d=0</tt>, то в вычислении <tt>Sum( b, c) </tt> нет никакого смысла: это окажется пустой потерей времени. По этой причине первый аргумент функции <tt>Div</tt> может быть помечен как <tt>lazy</tt> (ленивый):  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
int Div( lazy int arg1, int arg2)
 
int Div( lazy int arg1, int arg2)
 
{
 
{
Line 591: Line 592:
 
return arg1 / arg2; // <- Sum начнет вычисляться здесь
 
return arg1 / arg2; // <- Sum начнет вычисляться здесь
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
В этом случае вычисление функции <tt>Sum </tt>будет отложено до тех пор, пока оно не потребуется в теле функции, что позволяет иногда избежать выполнения лишних операций. Если вторым параметром функции <tt>Div </tt>окажется <tt>0</tt>, будет возвращена ошибка, а <tt>Sum </tt>не будет вычислено вовсе. В качестве такого параметра по-прежнему можно передавать переменные или литералы, это необязательно должны быть функции, хотя свойство <tt>lazy</tt> повлияет только на случай, когда параметром является результат вычисления функции.  
 
В этом случае вычисление функции <tt>Sum </tt>будет отложено до тех пор, пока оно не потребуется в теле функции, что позволяет иногда избежать выполнения лишних операций. Если вторым параметром функции <tt>Div </tt>окажется <tt>0</tt>, будет возвращена ошибка, а <tt>Sum </tt>не будет вычислено вовсе. В качестве такого параметра по-прежнему можно передавать переменные или литералы, это необязательно должны быть функции, хотя свойство <tt>lazy</tt> повлияет только на случай, когда параметром является результат вычисления функции.  
Line 607: Line 608:
 
Если после названия аргумента функции стоит знак равно и после него следует какое-то значение, это означает, что для этого аргумента задано значение по умолчанию. В этом случае значение этого параметра при вызове функции можно опускать: тогда аргумент примет свое значение по умолчанию.  
 
Если после названия аргумента функции стоит знак равно и после него следует какое-то значение, это означает, что для этого аргумента задано значение по умолчанию. В этом случае значение этого параметра при вызове функции можно опускать: тогда аргумент примет свое значение по умолчанию.  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
int Sum( int a, int b, int c = 0)
 
int Sum( int a, int b, int c = 0)
 
{
 
{
 
return a + b + c;  
 
return a + b + c;  
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
В приведенном примере для аргумента c задано значение по умолчанию <tt>0</tt>. Функция теперь может быть вызвана как от трех, так и от двух параметров, при этом она работает так, как если бы значение последнего параметра было равно 0:  
 
В приведенном примере для аргумента c задано значение по умолчанию <tt>0</tt>. Функция теперь может быть вызвана как от трех, так и от двух параметров, при этом она работает так, как если бы значение последнего параметра было равно 0:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
assert( Sum( 5, 5, 5) == 15);
 
assert( Sum( 5, 5, 5) == 15);
 
assert( Sum( 5, 5) == 10);  
 
assert( Sum( 5, 5) == 10);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Того же эффекта можно добиться, если перегрузить функцию для трех и для двух аргументов, что является неудобным как для разработчика модуля, так и для пользователя.  
 
Того же эффекта можно добиться, если перегрузить функцию для трех и для двух аргументов, что является неудобным как для разработчика модуля, так и для пользователя.  
Line 626: Line 627:
 
Значения по умолчанию можно задавать только для аргументов, которые идут в конце:  
 
Значения по умолчанию можно задавать только для аргументов, которые идут в конце:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
int Sum( int a, int b, int c = 0); // <- допустимое определение
 
int Sum( int a, int b, int c = 0); // <- допустимое определение
 
int Sum( int a, int b = 0, int c = 0); // <- допустимое определение
 
int Sum( int a, int b = 0, int c = 0); // <- допустимое определение
Line 632: Line 633:
 
int Sum( int a = 0, int b, int c); // <- ошибка компиляции
 
int Sum( int a = 0, int b, int c); // <- ошибка компиляции
 
int Sum( int a = 0, int b = 0, int c); // <- ошибка компиляции
 
int Sum( int a = 0, int b = 0, int c); // <- ошибка компиляции
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Значение по умолчанию – это необязательно литерал. Это может быть и какое-то более сложное выражение:  
 
Значение по умолчанию – это необязательно литерал. Это может быть и какое-то более сложное выражение:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
class Class
 
class Class
 
{}
 
{}
  
 
void func( Class arg = new Class() );
 
void func( Class arg = new Class() );
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Для пользователя модуля главное запомнить, что если для аргумента задано значение по умолчанию, то при вызове функции параметр можно опускать, при этом в функцию в качестве пропущенного параметра будет передано значение по умолчанию.  
 
Для пользователя модуля главное запомнить, что если для аргумента задано значение по умолчанию, то при вызове функции параметр можно опускать, при этом в функцию в качестве пропущенного параметра будет передано значение по умолчанию.  
Line 649: Line 650:
 
Хотя мы уже рассмотрели схему функции, представленную выше, в полном объеме, сама эта схема не полна. Существует еще три особенности, которые нужно рассмотреть.  
 
Хотя мы уже рассмотрели схему функции, представленную выше, в полном объеме, сама эта схема не полна. Существует еще три особенности, которые нужно рассмотреть.  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
type_name function_name(types args ...);  
 
type_name function_name(types args ...);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
  (в этой схеме рассмотренные выше необязательные особенности опущены, но они могут также использоваться одновременно с произвольным количеством параметров)  
 
  (в этой схеме рассмотренные выше необязательные особенности опущены, но они могут также использоваться одновременно с произвольным количеством параметров)  
Line 658: Line 659:
 
Мы видим, что на конце списка аргументов в скобках стоит троеточие. Это означает, что количество параметров, от которых вызывается функция, произвольно. Рассмотрим пример:   
 
Мы видим, что на конце списка аргументов в скобках стоит троеточие. Это означает, что количество параметров, от которых вызывается функция, произвольно. Рассмотрим пример:   
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
int Sum( int[] arg ...);  
 
int Sum( int[] arg ...);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
  (обратите внимание на то, что тип аргумента должен быть обязательно определен как слайс)  
 
  (обратите внимание на то, что тип аргумента должен быть обязательно определен как слайс)  
Line 667: Line 668:
 
  Эта функция может быть вызвана от произвольного количества слагаемых и возвращает их сумму:  
 
  Эта функция может быть вызвана от произвольного количества слагаемых и возвращает их сумму:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
int sum = Sum( 1, 2, 3, 4);
 
int sum = Sum( 1, 2, 3, 4);
 
assert( sum == 10 );  
 
assert( sum == 10 );  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Функции, у которых количество параметров может меняться, называются вариадическими (variadic functions). Известный пример такой функции – функция <tt>writeln(...)</tt> из модуля <tt>std.stdio</tt>, которая может одновременно принять и вывести на экран любое число параметров.  
 
Функции, у которых количество параметров может меняться, называются вариадическими (variadic functions). Известный пример такой функции – функция <tt>writeln(...)</tt> из модуля <tt>std.stdio</tt>, которая может одновременно принять и вывести на экран любое число параметров.  
Line 677: Line 678:
 
Тип аргумента должен быть слайсом потому, что в тело функции этот аргумент попадает и используется как слайс. Однако квадратные скобки – всего лишь часть синтаксиса произвольного количества параметров, и это не означает, что в качестве параметра должен быть передан слайс. Пример выше демонстрирует это. Вместе с тем, вместо перечисления нескольких параметров можно передать и один массив – слайс:  
 
Тип аргумента должен быть слайсом потому, что в тело функции этот аргумент попадает и используется как слайс. Однако квадратные скобки – всего лишь часть синтаксиса произвольного количества параметров, и это не означает, что в качестве параметра должен быть передан слайс. Пример выше демонстрирует это. Вместе с тем, вместо перечисления нескольких параметров можно передать и один массив – слайс:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
int[] array = [1, 2, 3, 4];
 
int[] array = [1, 2, 3, 4];
 
int sum = Sum( array);  
 
int sum = Sum( array);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
В списке аргументов функции может быть только один аргумент с такой особенностью, и он должен стоять в конце.   
 
В списке аргументов функции может быть только один аргумент с такой особенностью, и он должен стоять в конце.   
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
void func( int arg1, int arg2, int[] args ...);  // <- допустимое определение
 
void func( int arg1, int arg2, int[] args ...);  // <- допустимое определение
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Также отметим, что произвольное количество аргументов подразумевает также и нулевое их количество. В этом случае в функцию будет передан пустой слайс, и поведение функции в этом случае остается на усмотрение программиста, разрабатывавшего модуль.  
 
Также отметим, что произвольное количество аргументов подразумевает также и нулевое их количество. В этом случае в функцию будет передан пустой слайс, и поведение функции в этом случае остается на усмотрение программиста, разрабатывавшего модуль.  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
int sum = Sum();
 
int sum = Sum();
 
assert( sum == 0);  
 
assert( sum == 0);  
</pre>
+
</syntaxhighlight>
 
==== Шаблоны====
 
==== Шаблоны====
 
<p>
 
<p>
 
Иногда встречается схема:  
 
Иногда встречается схема:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
type_name function_name(...)(types args);  
 
type_name function_name(...)(types args);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Между названием функции и списком аргументов появляются еще одни скобки. В этом случае функция становится ''шаблоном '' (<tt>template</tt>), а вместо многоточия в скобках указываются параметры шаблона. Шаблонами могут также быть структуры, классы, интерфейсы и практически любые другие определения, доступные в D. Параметром шаблона необязательно должно быть значение (как в случае параметров функции), чаще всего это тип, но может быть и что-то другое. Благодаря шаблонам становится возможным, например, вызывать функцию от параметров разных типов, не перегружая ее, то есть составив только одно ее определение в исходном коде программы. Шаблоны предоставляют и множество других преимуществ.  
 
Между названием функции и списком аргументов появляются еще одни скобки. В этом случае функция становится ''шаблоном '' (<tt>template</tt>), а вместо многоточия в скобках указываются параметры шаблона. Шаблонами могут также быть структуры, классы, интерфейсы и практически любые другие определения, доступные в D. Параметром шаблона необязательно должно быть значение (как в случае параметров функции), чаще всего это тип, но может быть и что-то другое. Благодаря шаблонам становится возможным, например, вызывать функцию от параметров разных типов, не перегружая ее, то есть составив только одно ее определение в исходном коде программы. Шаблоны предоставляют и множество других преимуществ.  
Line 708: Line 709:
 
Сначала мы рассмотрим шаблоны-функции, а затем распространим эту особенность на более общий случай. Первое представление о шаблонах можно получить из примера:  
 
Сначала мы рассмотрим шаблоны-функции, а затем распространим эту особенность на более общий случай. Первое представление о шаблонах можно получить из примера:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
T Sum(T)( T a, T b)
 
T Sum(T)( T a, T b)
 
{
 
{
Line 714: Line 715:
 
return result;
 
return result;
 
}  
 
}  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Мы видим, что тип, возвращаемый функцией, а также типы аргументов и тип внутренней переменой <tt>result</tt> определены как <tt>T</tt>. Для каждого места в коде программы, где такая функция вызывается, ''выводится'' (instantiate) определенный вариант этой функции, соответствующий условиям вызова, а вместо слова <tt>T</tt> везде ставится какой-то конкретный тип:  
 
Мы видим, что тип, возвращаемый функцией, а также типы аргументов и тип внутренней переменой <tt>result</tt> определены как <tt>T</tt>. Для каждого места в коде программы, где такая функция вызывается, ''выводится'' (instantiate) определенный вариант этой функции, соответствующий условиям вызова, а вместо слова <tt>T</tt> везде ставится какой-то конкретный тип:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
void main(){
 
void main(){
 
int a, b;
 
int a, b;
 
int sum = Sum!int( a, b);
 
int sum = Sum!int( a, b);
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Чтобы определить, для какого типа мы хотим вывести функцию, этот тип пишется после восклицательного знака при вызове функции. В нашем случае будет вызвана функция такого вида:  
 
Чтобы определить, для какого типа мы хотим вывести функцию, этот тип пишется после восклицательного знака при вызове функции. В нашем случае будет вызвана функция такого вида:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
int Sum( int a, int b)
 
int Sum( int a, int b)
 
{
 
{
Line 733: Line 734:
 
return result;
 
return result;
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Которая просто вернет сумму своих аргументов. Если функция-шаблон используется в программе много раз для разных типов, то для каждого случая будет ''выведена'' (instantiated) своя функция, специализированная под конкретный тип. Таким образом, особенность шаблонов успешно борется с необходимостью перегрузки функций для разных типов аргументов, разных возвращаемых типов и разных типов внутренних переменных функций. Тип <tt>T</tt> – это и есть параметр шаблона, в этом случае параметр единственный.  
 
Которая просто вернет сумму своих аргументов. Если функция-шаблон используется в программе много раз для разных типов, то для каждого случая будет ''выведена'' (instantiated) своя функция, специализированная под конкретный тип. Таким образом, особенность шаблонов успешно борется с необходимостью перегрузки функций для разных типов аргументов, разных возвращаемых типов и разных типов внутренних переменных функций. Тип <tt>T</tt> – это и есть параметр шаблона, в этом случае параметр единственный.  
Line 740: Line 741:
 
В случае, когда шаблоном является функция, необязательно указывать конкретный тип для вывода шаблона после восклицательного знака: компилятор может вывести его сам по параметрам, поскольку их типы ему известны:  
 
В случае, когда шаблоном является функция, необязательно указывать конкретный тип для вывода шаблона после восклицательного знака: компилятор может вывести его сам по параметрам, поскольку их типы ему известны:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
int sum = Sum( a, b); // <- допустимый вызов функции
 
int sum = Sum( a, b); // <- допустимый вызов функции
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Проще всего понимать шаблоны как функции, у которых есть два списка аргументов: аргументы самой функции (это значения, данные) и аргументы шаблона. У этих списков аргументов есть много общего: оба пишутся в скобках, причем первые скобки – аргументы шаблона, а вторые – аргументы функции. В этом есть своя логика, потому что шаблоны – особенность, которая проявляется на этапе компиляции, то есть раньше, а аргументы шаблона используются уже при работе программы собственно в вычислениях, то есть позже. Действительно, вывод нескольких вариантов одной и той же функции под разные типы является ни чем иным, чем перегрузкой этой функции, с той разницей, что теперь это забота не программиста, а компилятора.  
 
Проще всего понимать шаблоны как функции, у которых есть два списка аргументов: аргументы самой функции (это значения, данные) и аргументы шаблона. У этих списков аргументов есть много общего: оба пишутся в скобках, причем первые скобки – аргументы шаблона, а вторые – аргументы функции. В этом есть своя логика, потому что шаблоны – особенность, которая проявляется на этапе компиляции, то есть раньше, а аргументы шаблона используются уже при работе программы собственно в вычислениях, то есть позже. Действительно, вывод нескольких вариантов одной и той же функции под разные типы является ни чем иным, чем перегрузкой этой функции, с той разницей, что теперь это забота не программиста, а компилятора.  
Line 749: Line 750:
 
Аргументы шаблона и аргументы функции имеют и другие схожие черты. Например, список аргументов шаблона может содержать несколько аргументов, разделенных запятыми, а также давать аргументам значение по умолчанию:  
 
Аргументы шаблона и аргументы функции имеют и другие схожие черты. Например, список аргументов шаблона может содержать несколько аргументов, разделенных запятыми, а также давать аргументам значение по умолчанию:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
T[] array( Count, T = int)( Count count)
 
T[] array( Count, T = int)( Count count)
 
{
 
{
Line 758: Line 759:
 
return result;
 
return result;
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Эта функция возвращает слайс типов <tt>T</tt>, состоящий из исходных значений этих типов, причем длина массива определяется параметром функции <tt>count</tt> типа <tt>Count</tt>, а тип слайса по умолчанию – <tt>int</tt>:  
 
Эта функция возвращает слайс типов <tt>T</tt>, состоящий из исходных значений этих типов, причем длина массива определяется параметром функции <tt>count</tt> типа <tt>Count</tt>, а тип слайса по умолчанию – <tt>int</tt>:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
assert( array!(int, int)(3) == [0, 0, 0] );
 
assert( array!(int, int)(3) == [0, 0, 0] );
 
assert( array!(int)(3) == [0, 0, 0] );
 
assert( array!(int)(3) == [0, 0, 0] );
 
assert( array(3) == [0, 0, 0] );
 
assert( array(3) == [0, 0, 0] );
</pre>
+
</syntaxhighlight>
 
===== Особые выводы=====
 
===== Особые выводы=====
 
<p>
 
<p>
 
Хотя использование шаблонов само по себе предотвращает необходимость перегружать функции, иногда в документации можно встретить повторяющиеся шаблоны-функции с одним и тем же названием. Обычно при этом в таких определениях в списке аргументов шаблона присутствует двоеточие:  
 
Хотя использование шаблонов само по себе предотвращает необходимость перегружать функции, иногда в документации можно встретить повторяющиеся шаблоны-функции с одним и тем же названием. Обычно при этом в таких определениях в списке аргументов шаблона присутствует двоеточие:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
T Sum(T)( T a, T b)
 
T Sum(T)( T a, T b)
 
{
 
{
Line 783: Line 784:
 
return result;
 
return result;
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Это так называемая ''специализация'' (specialization) шаблона для определенных типов. Это делается в том случае, когда для некоторых типов функция должна работать иначе, чем для всех остальных. Таких специализаций может быть несколько, и в случае, когда функция в программе вызывается для одной из своих особых специализаций, она выводится не по общему правилу, а по особенному.  
 
Это так называемая ''специализация'' (specialization) шаблона для определенных типов. Это делается в том случае, когда для некоторых типов функция должна работать иначе, чем для всех остальных. Таких специализаций может быть несколько, и в случае, когда функция в программе вызывается для одной из своих особых специализаций, она выводится не по общему правилу, а по особенному.  
Line 791: Line 792:
 
Мы уже увидели, что параметрами шаблона могут быть типы. Но параметры могут быть также и других видов: значения или кортежи. Например, эта функция:  
 
Мы уже увидели, что параметрами шаблона могут быть типы. Но параметры могут быть также и других видов: значения или кортежи. Например, эта функция:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
string line( int length)( char letter)
 
string line( int length)( char letter)
 
{
 
{
Line 800: Line 801:
 
return result;
 
return result;
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
возвращает строку, состоящую из символа , определяемого ее аргументом, длиной <tt>length</tt>:  
 
возвращает строку, состоящую из символа , определяемого ее аргументом, длиной <tt>length</tt>:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
assert( line!4('b') == "bbbb");
 
assert( line!4('b') == "bbbb");
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Параметры шаблона-значения также могут иметь значение по умолчанию, которое указывается после знака<tt> =</tt>.  
 
Параметры шаблона-значения также могут иметь значение по умолчанию, которое указывается после знака<tt> =</tt>.  
Line 813: Line 814:
 
На первый взгляд может показаться, что передавать значения в качестве параметров шаблона бессмысленно: значения можно передать и через параметры функции:  
 
На первый взгляд может показаться, что передавать значения в качестве параметров шаблона бессмысленно: значения можно передать и через параметры функции:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
string line( int length, char letter)  
 
string line( int length, char letter)  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Однако параметры шаблона передаются при компиляции, это особенность времени компиляции, а не выполнения программы, поэтому в некоторых случаях приходится делать значения параметрами шаблона (яркий пример такого использования будет приведен ниже для шаблонов-классов).  
 
Однако параметры шаблона передаются при компиляции, это особенность времени компиляции, а не выполнения программы, поэтому в некоторых случаях приходится делать значения параметрами шаблона (яркий пример такого использования будет приведен ниже для шаблонов-классов).  
Line 826: Line 827:
 
Еще одна особенность касается методов структур и классов:  
 
Еще одна особенность касается методов структур и классов:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
class Class
 
class Class
 
{
 
{
Line 832: Line 833:
 
{}
 
{}
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Здесь шаблоном является метод <tt>foo </tt>класса <tt>Class</tt>, и его параметр шаблона помечен как <tt>this</tt>. Через такой параметр в шаблон передается тип объекта, на котором вызван метод (в данном случае это будет <tt>Class</tt>). Этот параметр также передается автоматически, и эта особенность может в большинстве случаев не учитываться пользователем модуля.  
 
Здесь шаблоном является метод <tt>foo </tt>класса <tt>Class</tt>, и его параметр шаблона помечен как <tt>this</tt>. Через такой параметр в шаблон передается тип объекта, на котором вызван метод (в данном случае это будет <tt>Class</tt>). Этот параметр также передается автоматически, и эта особенность может в большинстве случаев не учитываться пользователем модуля.  
Line 840: Line 841:
 
Помимо слова<tt> this</tt>, перед названием параметра шаблона может следовать слово <tt>alias</tt>. В этом случае параметр не получает значения сам, но вместо того, чтобы принять то значение, которое ему будет присвоено пользователем, он становится псевдонимом для него:  
 
Помимо слова<tt> this</tt>, перед названием параметра шаблона может следовать слово <tt>alias</tt>. В этом случае параметр не получает значения сам, но вместо того, чтобы принять то значение, которое ему будет присвоено пользователем, он становится псевдонимом для него:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
void Set( alias obj)( int arg)
 
void Set( alias obj)( int arg)
 
{
 
{
Line 851: Line 852:
 
assert( num == 5);
 
assert( num == 5);
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
В этом примере функция<tt> Sum </tt>выводится для переменной <tt>num</tt>, в результате чего <tt>num </tt>получает псевдоним, через который функция может обращаться к переменной и менять ее.  
+
В этом примере функция<tt> Set </tt>выводится для переменной <tt>num</tt>, в результате чего <tt>num </tt>получает псевдоним, через который функция может обращаться к переменной и менять ее.  
 
</p>
 
</p>
 
===== Параметры-кортежи=====
 
===== Параметры-кортежи=====
Line 862: Line 863:
 
Кортежам и работе с ними посвящена отдельная глава учебника: http://ddili.org/ders/d.en/tuples.html. Здесь мы рассмотрим только один пример, демонстрирующий возможность применения кортежей в качестве параметров шаблона:  
 
Кортежам и работе с ними посвящена отдельная глава учебника: http://ddili.org/ders/d.en/tuples.html. Здесь мы рассмотрим только один пример, демонстрирующий возможность применения кортежей в качестве параметров шаблона:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
void info(T...)(T args){}  
 
void info(T...)(T args){}  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Пример взят отсюда: http://ddili.org/ders/d.en/templates_more.html.  
 
Пример взят отсюда: http://ddili.org/ders/d.en/templates_more.html.  
Line 871: Line 872:
 
Здесь из-за троеточия после слова <tt>T</tt> этот параметр шаблона становится уже не типом и не значением, а кортежем, причем кортежем вариадическим (переменной длины, variadic tuple). Поскольку аргумент <tt>args</tt> обозначен как аргумент, имеющий тип <tt>T</tt>, то он сам по себе тоже становится кортежем. Разница в том, что <tt>T</tt> выражает собой кортеж, состоящий из типов, а <tt>args</tt> –из значений. Пример использования такой функции:  
 
Здесь из-за троеточия после слова <tt>T</tt> этот параметр шаблона становится уже не типом и не значением, а кортежем, причем кортежем вариадическим (переменной длины, variadic tuple). Поскольку аргумент <tt>args</tt> обозначен как аргумент, имеющий тип <tt>T</tt>, то он сам по себе тоже становится кортежем. Разница в том, что <tt>T</tt> выражает собой кортеж, состоящий из типов, а <tt>args</tt> –из значений. Пример использования такой функции:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
info(1, "abc", 2.3);  
 
info(1, "abc", 2.3);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Поскольку мы используем шаблон-функцию, то нет надобности отдельно выводить такой шаблон: значение кортежа типов <tt>T</tt> компилятор может вывести сам, зная кортеж значений <tt>args</tt>, который указан в скобках как параметры функции.  
 
Поскольку мы используем шаблон-функцию, то нет надобности отдельно выводить такой шаблон: значение кортежа типов <tt>T</tt> компилятор может вывести сам, зная кортеж значений <tt>args</tt>, который указан в скобках как параметры функции.  
Line 881: Line 882:
 
Выше мы проиллюстрировали функции-шаблоны – наиболее простой случай использования шаблонов. Однако эта особенность может также использоваться и с другими объектами в D. Например, шаблонами могут быть классы, структуры, интерфейсы, юнионы (<tt>union</tt>) и любые другие определения. Чтобы сделать тот или иной объект шаблоном, достаточно после его названия добавить круглые скобки, внутри которых указать список параметров шаблона. Вот простой пример шаблона-класса, который одновременно поясняет синтаксис и раскрывает некоторые полезные возможности использования шаблонов такого вида:  
 
Выше мы проиллюстрировали функции-шаблоны – наиболее простой случай использования шаблонов. Однако эта особенность может также использоваться и с другими объектами в D. Например, шаблонами могут быть классы, структуры, интерфейсы, юнионы (<tt>union</tt>) и любые другие определения. Чтобы сделать тот или иной объект шаблоном, достаточно после его названия добавить круглые скобки, внутри которых указать список параметров шаблона. Вот простой пример шаблона-класса, который одновременно поясняет синтаксис и раскрывает некоторые полезные возможности использования шаблонов такого вида:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
class Vector( T, int dim = 3)
 
class Vector( T, int dim = 3)
 
{
 
{
 
T[dim] coord;
 
T[dim] coord;
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Этот класс может с равным успехом представлять вектор в одномерном, двумерном, трехмерном или любой другой мерности пространстве, причем его координаты могут быть как целочисленными, так и дробными, с любой степенью точности. По умолчанию вектор становится трехмерным. Пример использования:  
 
Этот класс может с равным успехом представлять вектор в одномерном, двумерном, трехмерном или любой другой мерности пространстве, причем его координаты могут быть как целочисленными, так и дробными, с любой степенью точности. По умолчанию вектор становится трехмерным. Пример использования:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
auto vect = new Vector!( double, 2)();  
 
auto vect = new Vector!( double, 2)();  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Теперь переменная <tt>vect</tt> представляет собой двумерный вектор с координатами типа <tt>double</tt>.  
 
Теперь переменная <tt>vect</tt> представляет собой двумерный вектор с координатами типа <tt>double</tt>.  
Line 900: Line 901:
 
На параметры шаблона можно накладывать ограничения, которые будут проверяться и иметь силу на этапе компиляции программы. Мы рассмотрим эту особенность на примере шаблонов-функций. Допустим, мы увидели где-то синтаксис по такой схеме:  
 
На параметры шаблона можно накладывать ограничения, которые будут проверяться и иметь силу на этапе компиляции программы. Мы рассмотрим эту особенность на примере шаблонов-функций. Допустим, мы увидели где-то синтаксис по такой схеме:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
type_name function_name(...)(types args)    if(...);
 
type_name function_name(...)(types args)    if(...);
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
В этой схеме мы видим, что сразу после названия функции-шаблона и списка аргументов следует оператор <tt>if</tt>. Это – ''ограничения'' (constraints) на параметры функции. Эта особенность работает только с шаблонами. <tt>if</tt>, который стоит сразу после определения и непосредственно перед телом функции, работает практически так же, как и обычный <tt>if</tt>: тело функции выполняется (на самом деле – компилируется) только в том случае, если выражение внутри <tt>if</tt> верно (<tt>true</tt>). Разница в том, что этот <tt>if</tt> – статический, то есть он выполняется на этапе компиляции, а не при работе программы. На самом деле, если этот <tt>if</tt> не будет удовлетворен, то функция не скомпилируется вообще. Как следствие, выражение в этом <tt>if</tt> не может содержать локальных или глобальных динамических переменных, оно не может зависеть от значений параметров функции, потому что эти значения доступны только в процессе работы программы. Поскольку этот <tt>if</tt> выполняется на этапе компиляции, то невыполнение условий, заключенных в скобки, влияет не на работу программы, а на процесс компиляции: компилятор выдаст ошибку. Таким образом, ограничения при шаблонах используются скорее для отладки кода программы, чем для совершения каких-то операций в процессе ее работы. Рассмотрим уже упомянутый выше пример:  
 
В этой схеме мы видим, что сразу после названия функции-шаблона и списка аргументов следует оператор <tt>if</tt>. Это – ''ограничения'' (constraints) на параметры функции. Эта особенность работает только с шаблонами. <tt>if</tt>, который стоит сразу после определения и непосредственно перед телом функции, работает практически так же, как и обычный <tt>if</tt>: тело функции выполняется (на самом деле – компилируется) только в том случае, если выражение внутри <tt>if</tt> верно (<tt>true</tt>). Разница в том, что этот <tt>if</tt> – статический, то есть он выполняется на этапе компиляции, а не при работе программы. На самом деле, если этот <tt>if</tt> не будет удовлетворен, то функция не скомпилируется вообще. Как следствие, выражение в этом <tt>if</tt> не может содержать локальных или глобальных динамических переменных, оно не может зависеть от значений параметров функции, потому что эти значения доступны только в процессе работы программы. Поскольку этот <tt>if</tt> выполняется на этапе компиляции, то невыполнение условий, заключенных в скобки, влияет не на работу программы, а на процесс компиляции: компилятор выдаст ошибку. Таким образом, ограничения при шаблонах используются скорее для отладки кода программы, чем для совершения каких-то операций в процессе ее работы. Рассмотрим уже упомянутый выше пример:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
T Sum(T)( T a, T b)
 
T Sum(T)( T a, T b)
 
{
 
{
Line 913: Line 914:
 
return result;
 
return result;
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Как уже говорилось, при различных значениях параметра шаблона<tt> T</tt>, который отражает тот или иной тип данных, эта функция может складывать два  числа любого типа.  
 
Как уже говорилось, при различных значениях параметра шаблона<tt> T</tt>, который отражает тот или иной тип данных, эта функция может складывать два  числа любого типа.  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
int a = 5;
 
int a = 5;
 
int b = 6;
 
int b = 6;
Line 927: Line 928:
 
double sumd = Sum!(double)( c, d); // для дробных чисел
 
double sumd = Sum!(double)( c, d); // для дробных чисел
 
assert( sumd == 2.75);  
 
assert( sumd == 2.75);  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Хотя оператором "<tt>+</tt>" нельзя складывать строки, функция <tt>Sum</tt>, согласно определению, может быть выведена для любого типа:  
 
Хотя оператором "<tt>+</tt>" нельзя складывать строки, функция <tt>Sum</tt>, согласно определению, может быть выведена для любого типа:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
string a = "aaa";
 
string a = "aaa";
 
string b = "bbb";
 
string b = "bbb";
 
string sum = Sum!(string)( a, b); // для строк
 
string sum = Sum!(string)( a, b); // для строк
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
При таком использовании этой функции компилятор, очевидно, выдаст ошибку компиляции, но эта ошибка будет в теле функции, то есть в готовом, проверенном и готовом к использованию модуле. Для программиста, который подключил такой модуль, это может быть неожиданно, хотя на самом деле проблема в том, что он неправильно использовал функцию. (Выше мы боролись с подобной проблемой с помощью специализации, но в таком случае кроме <tt>string</tt> все равно остается множество типов, которые нельзя складывать оператором <tt>+</tt>, например, <tt>char</tt>). Чтобы исключить  такие случаи, на параметры накладывают ограничения:  
 
При таком использовании этой функции компилятор, очевидно, выдаст ошибку компиляции, но эта ошибка будет в теле функции, то есть в готовом, проверенном и готовом к использованию модуле. Для программиста, который подключил такой модуль, это может быть неожиданно, хотя на самом деле проблема в том, что он неправильно использовал функцию. (Выше мы боролись с подобной проблемой с помощью специализации, но в таком случае кроме <tt>string</tt> все равно остается множество типов, которые нельзя складывать оператором <tt>+</tt>, например, <tt>char</tt>). Чтобы исключить  такие случаи, на параметры накладывают ограничения:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
T Sum(T)( T a, T b)
 
T Sum(T)( T a, T b)
 
if( is( T == int) || is( T == double));  
 
if( is( T == int) || is( T == double));  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Условия, указанные в ограничениях, проверяются при компиляции и при несоблюдении вызывают ошибки компиляции, которые указывают на этот раз на неверное использование функции, то есть на ошибку со стороны использования модуля, а не на ошибки в модуле. В рассмотренном примере наложенное ограничение позволит использовать функцию только с типами <tt>int</tt> или <tt>double</tt>.  
 
Условия, указанные в ограничениях, проверяются при компиляции и при несоблюдении вызывают ошибки компиляции, которые указывают на этот раз на неверное использование функции, то есть на ошибку со стороны использования модуля, а не на ошибки в модуле. В рассмотренном примере наложенное ограничение позволит использовать функцию только с типами <tt>int</tt> или <tt>double</tt>.  
Line 949: Line 950:
 
Ограничения и специализация во многом выполняют схожие функции. Можно, например, перегрузить представленную выше функцию таким образом:  
 
Ограничения и специализация во многом выполняют схожие функции. Можно, например, перегрузить представленную выше функцию таким образом:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
T Sum(T)( T a, T b) if( is( T == string) )  
 
T Sum(T)( T a, T b) if( is( T == string) )  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Разница в определении лишь в том, что на этот раз ограничение допускает использование только типа <tt>string</tt>, название же функции, ее возвращаемый тип и параметры функции и шаблона остались прежними. Таким образом, мы создали две функции-шаблона с одинаковым названием и аргументами, но с разными ограничениями, и с учетом этой перегрузки мы теперь можем вызывать функцию <tt>Sum</tt> от аргументов трех разных типов: <tt> int</tt>, <tt>double</tt> и <tt>string</tt>. В случае со специализацией мы могли использовать любой тип, но для <tt>string</tt> функция работала по-другому. Таким образом, с помощью ограничений можно полностью исключить нежелательные значения параметров шаблона, оставив только несколько, а с помощью специализаций из всего спектра возможных значений можно выделить особые и изменить тело функции для них.  
 
Разница в определении лишь в том, что на этот раз ограничение допускает использование только типа <tt>string</tt>, название же функции, ее возвращаемый тип и параметры функции и шаблона остались прежними. Таким образом, мы создали две функции-шаблона с одинаковым названием и аргументами, но с разными ограничениями, и с учетом этой перегрузки мы теперь можем вызывать функцию <tt>Sum</tt> от аргументов трех разных типов: <tt> int</tt>, <tt>double</tt> и <tt>string</tt>. В случае со специализацией мы могли использовать любой тип, но для <tt>string</tt> функция работала по-другому. Таким образом, с помощью ограничений можно полностью исключить нежелательные значения параметров шаблона, оставив только несколько, а с помощью специализаций из всего спектра возможных значений можно выделить особые и изменить тело функции для них.  
Line 958: Line 959:
 
Также отметим, что ограничения на параметры шаблона, которые были рассмотрены выше для функций, также работают и с другими видами шаблонов. Вот простой пример для ограничения на параметры шаблона-класса:  
 
Также отметим, что ограничения на параметры шаблона, которые были рассмотрены выше для функций, также работают и с другими видами шаблонов. Вот простой пример для ограничения на параметры шаблона-класса:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
class Vector( T, int dim = 3) if( (is(T == double) || is(T == int)) && dim > 0 )  
 
class Vector( T, int dim = 3) if( (is(T == double) || is(T == int)) && dim > 0 )  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Таким образом, в программе могут использоваться векторы с координатами только типов <tt>int </tt>или <tt>double</tt>, а мерность пространства естественным образом должна быть положительной. Понятно, что в противном случае (если <tt>dim </tt>будет отрицательным) компилятор даже не сможет создать массив отрицательной длины, но благодаря ограничениям это станет ошибкой того, кто пытается создать такой вектор, а не программиста, который написал этот шаблон.  
 
Таким образом, в программе могут использоваться векторы с координатами только типов <tt>int </tt>или <tt>double</tt>, а мерность пространства естественным образом должна быть положительной. Понятно, что в противном случае (если <tt>dim </tt>будет отрицательным) компилятор даже не сможет создать массив отрицательной длины, но благодаря ограничениям это станет ошибкой того, кто пытается создать такой вектор, а не программиста, который написал этот шаблон.  
Line 968: Line 969:
 
Отдельного внимания заслуживает выражение <tt>is</tt>, которое особенно часто используется в ограничениях на параметры шаблона. Это выражение нужно для выполнения логических операций на этапе компиляции. Любое выражение, помещенное в скобки оператора <tt>is(...)</tt>, вычисляется не при работе программы, а при компиляции. Поэтому все входящие в него значения должны быть известны на момент компиляции. Выражение <tt>is </tt>может использоваться многими способами, некоторые из которых можно увидеть в следующем примере:  
 
Отдельного внимания заслуживает выражение <tt>is</tt>, которое особенно часто используется в ограничениях на параметры шаблона. Это выражение нужно для выполнения логических операций на этапе компиляции. Любое выражение, помещенное в скобки оператора <tt>is(...)</tt>, вычисляется не при работе программы, а при компиляции. Поэтому все входящие в него значения должны быть известны на момент компиляции. Выражение <tt>is </tt>может использоваться многими способами, некоторые из которых можно увидеть в следующем примере:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
// выражение is принимает значение true, если:
 
// выражение is принимает значение true, если:
 
static assert( is(int) ); // аргумент имеет действительный тип
 
static assert( is(int) ); // аргумент имеет действительный тип
Line 1,013: Line 1,014:
 
// такие сложные is(...) могут содержать и большее число условий
 
// такие сложные is(...) могут содержать и большее число условий
 
// все последующие условия используют псевдонимы, определенные в первом условии (здесь - Value и Key)
 
// все последующие условия используют псевдонимы, определенные в первом условии (здесь - Value и Key)
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Подробнее об использовании выражения <tt>is()</tt> и о его значениях можно узнать здесь http://ddili.org/ders/d.en/is_expr.html и в других источниках.  
 
Подробнее об использовании выражения <tt>is()</tt> и о его значениях можно узнать здесь http://ddili.org/ders/d.en/is_expr.html и в других источниках.  
Line 1,024: Line 1,025:
 
Хотя такой синтаксис в распространяемых модулях встречается реже, вы можете наткнуться на такую схему:  
 
Хотя такой синтаксис в распространяемых модулях встречается реже, вы можете наткнуться на такую схему:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
template template_name(/*параметры шаблона*/)
 
template template_name(/*параметры шаблона*/)
 
{
 
{
 
/*определения шаблона*/
 
/*определения шаблона*/
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Ключевым словом template в языке D объявляется новый ''шаблон''. Этот шаблон пока не является ни функцией, ни классом или структурой, ничем. Все, что он делает полезного: создает внутри фигурных скобок пространство имен, дополненное параметрами шаблона. В этом пространстве имен (это статический скоуп) можно объявлять функции, классы и структуры, переменные и вообще любые другие сущности, доступные в D. При их объявлении и определении можно использовать параметры шаблона, которые могут замещать названия типов, значения или кортежи таким же образом, как они это делали в рассмотренных выше примерах для шаблонов определенного вида.  
 
Ключевым словом template в языке D объявляется новый ''шаблон''. Этот шаблон пока не является ни функцией, ни классом или структурой, ничем. Все, что он делает полезного: создает внутри фигурных скобок пространство имен, дополненное параметрами шаблона. В этом пространстве имен (это статический скоуп) можно объявлять функции, классы и структуры, переменные и вообще любые другие сущности, доступные в D. При их объявлении и определении можно использовать параметры шаблона, которые могут замещать названия типов, значения или кортежи таким же образом, как они это делали в рассмотренных выше примерах для шаблонов определенного вида.  
Line 1,036: Line 1,037:
 
Объявление переменных и функций внутри фигурных скобок шаблона может напоминать объявление полей и методов класса или структуры. Аналогично доступ к этим переменным и функциям осуществляется через точку. Вот пример:  
 
Объявление переменных и функций внутри фигурных скобок шаблона может напоминать объявление полей и методов класса или структуры. Аналогично доступ к этим переменным и функциям осуществляется через точку. Вот пример:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
template MyTemplate(T)
 
template MyTemplate(T)
 
{
 
{
Line 1,054: Line 1,055:
 
auto s = MyTemplate!double.Struct(5.6);
 
auto s = MyTemplate!double.Struct(5.6);
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Если какое-то объявление в шаблоне называется так же, как и сам шаблон, то такой шаблон называют ''одноименным'' (eponymous) и для обращения к нему точка не нужна:  
 
Если какое-то объявление в шаблоне называется так же, как и сам шаблон, то такой шаблон называют ''одноименным'' (eponymous) и для обращения к нему точка не нужна:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
template Sum(T)
 
template Sum(T)
 
{
 
{
Line 1,070: Line 1,071:
 
int sum = Sum!int( 5, 15);
 
int sum = Sum!int( 5, 15);
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
На самом деле все случаи шаблонов, рассмотренные выше: шаблоны-функции, шаблоны-классы и прочее, являются частным случаем одноименных шаблонов, которые объявляются без дополнительного скоупа и без слова <tt>template</tt>:  
 
На самом деле все случаи шаблонов, рассмотренные выше: шаблоны-функции, шаблоны-классы и прочее, являются частным случаем одноименных шаблонов, которые объявляются без дополнительного скоупа и без слова <tt>template</tt>:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
T Sum(T)( T a, T b) // то же самое, что и выше
 
T Sum(T)( T a, T b) // то же самое, что и выше
 
{}
 
{}
</pre>
+
</syntaxhighlight>
 
==== Еще несколько слов про шаблоны====
 
==== Еще несколько слов про шаблоны====
 
<p>
 
<p>
Line 1,089: Line 1,090:
 
Как уже было сказано, шаблон отдельно выводится под каждый случай его использования в программе с разными значениями параметров. Но иногда от значений параметров шаблона зависит не только алгоритм и метод обработки данных, как это часто бывает с шаблонами-функциями, но и значения некоторых переменных:  
 
Как уже было сказано, шаблон отдельно выводится под каждый случай его использования в программе с разными значениями параметров. Но иногда от значений параметров шаблона зависит не только алгоритм и метод обработки данных, как это часто бывает с шаблонами-функциями, но и значения некоторых переменных:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
int Number(T) = T.sizeof;  
 
int Number(T) = T.sizeof;  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Здесь мы объявили шаблон-переменную типа <tt>int</tt>, которая принимает значение длины в байтах того типа, от которого она выведена:  
 
Здесь мы объявили шаблон-переменную типа <tt>int</tt>, которая принимает значение длины в байтах того типа, от которого она выведена:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
assert( Number!short == 2 );  
 
assert( Number!short == 2 );  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Здесь важно отметить, что при компиляции компилятор пробегает всю программу, ищет все возможные разные выводы для шаблона <tt>Number</tt> и производит необходимое вычисление, создавая набор шаблонов (в нашем случае – переменных с определенными значениями) для их использования в программе при ее работе. Еще раз подчеркнем, что шаблоны позволяют не только "подставлять" вместо некоторых слов конкретные типы или значения, но и производить вычисления, если это необходимо, также во время компиляции.  
 
Здесь важно отметить, что при компиляции компилятор пробегает всю программу, ищет все возможные разные выводы для шаблона <tt>Number</tt> и производит необходимое вычисление, создавая набор шаблонов (в нашем случае – переменных с определенными значениями) для их использования в программе при ее работе. Еще раз подчеркнем, что шаблоны позволяют не только "подставлять" вместо некоторых слов конкретные типы или значения, но и производить вычисления, если это необходимо, также во время компиляции.  
Line 1,105: Line 1,106:
 
Набор выведенных и специализированных шаблонов, появившийся в результате компиляции, является набором разных типов:  
 
Набор выведенных и специализированных шаблонов, появившийся в результате компиляции, является набором разных типов:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
assert( Number!short != Number!int );  
 
assert( Number!short != Number!int );  
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
Хотя шаблоном являлась переменная типа <tt>int</tt>, при выводе шаблона образуются типы, которые называются, например, следующим образом:  
 
Хотя шаблоном являлась переменная типа <tt>int</tt>, при выводе шаблона образуются типы, которые называются, например, следующим образом:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
Number!short
 
Number!short
 
Number!int
 
Number!int
</pre>
+
</syntaxhighlight>
 
===== Шаблоны – особенность времени компиляции=====
 
===== Шаблоны – особенность времени компиляции=====
 
<p>
 
<p>
 
Хотя об этом уже было сказано, еще раз подчеркнем, что шаблоны выводятся при компиляции программы. Так, следующий код вызовет ошибку компиляции:  
 
Хотя об этом уже было сказано, еще раз подчеркнем, что шаблоны выводятся при компиляции программы. Так, следующий код вызовет ошибку компиляции:  
 
</p>
 
</p>
<pre>
+
<syntaxhighlight lang="D">
 
class Vector(int dim = 3)
 
class Vector(int dim = 3)
 
{
 
{
Line 1,129: Line 1,130:
 
auto vect = new Vector!dim();
 
auto vect = new Vector!dim();
 
}
 
}
</pre>
+
</syntaxhighlight>
 
<p>
 
<p>
 
потому что значение переменной <tt>dim</tt> неизвестно на момент компиляции. В языке D вообще много особенностей, позволяющих писать "код, который генериурет код". Шаблоны тоже можно отнести к такого рода особенностям. Поэтому при выводе шаблона всегда нужно следить, чтобы все его параметры были известны и доступны уже при компиляции.  
 
потому что значение переменной <tt>dim</tt> неизвестно на момент компиляции. В языке D вообще много особенностей, позволяющих писать "код, который генериурет код". Шаблоны тоже можно отнести к такого рода особенностям. Поэтому при выводе шаблона всегда нужно следить, чтобы все его параметры были известны и доступны уже при компиляции.  

Latest revision as of 19:27, 29 March 2015

Contents

Введение

Сравнительно недавно на свет появился язык программирования D: мощный инструмент, сочетающий фундаментальность C и C++ с недосягаемой ранее высокоуровневостью. Главными "фишками" языка можно считать обилие синтаксиса, предназначенного для сокращения исходного кода программы (шаблоны, миксины и прочее) и множество ключевых слов для проверки и отладки кода. Хотя язык выглядит перспективным, у многих возникают проблемы с пониманием некоторых нововведений, поэтому меня попросили написать серию тематических статей по D, направленных в первую очередь на понимание языка, а не на сухое изложение законов и синтаксиса. Статьи предназначены в первую очередь для тех, кто уже начал читать учебники или другую обучающую литературу по D, и так или иначе знаком с основной терминологией языка, но у кого остаются вопросы и кто желает найти на них понятные ответы.

Скоупы (scopes): последовательность выполнения команд, статические и динамические скоупы, пространства имен

Я не могу придумать уместного перевода слова scope на русский язык, и чтобы не вводить никого в заблуждение, введу термин "скоуп" как новый. Проще всего понять скоуп как участок кода от фигурной скобки до фигурной скобки:

import std.stdio;

void main(){				// ^ это скоуп 1
	int number = 0;			// |
	for( int i; i < 5; ++ i)	// |
	{				// |		^
		number += 7;		// |		|	это скоуп 2
	}				// |		v
	writeln( number);		// |
}					// v скоуп 1 закончился

Область кода за пределами всех скобок – это тоже скоуп, "outer", или внешний:

import std.stdio;			//^ это внешний скоуп
					//|
void main(){				//|		^ это скоуп 1
	int number = 0;			//|		|
	for( int i; i < 5; ++ i)	//|		|
	{				//|		|		^
		number += 7;		//|		|		|	это скоуп 2
	}				//|		|		v
	writeln( number);		//|		|
}					//|		v скоуп 1 закончился
					//|
struct Struct				//|
{}

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

Статические и динамические скоупы

Мы уже говорили, что все команды выполняются последовательно – одна за другой. Однако это происходит только в динамических скоупах. Существуют и другие – статические – скоупы. Например, когда мы рассматривали первые примеры, мы не говорили, для чего нужна область кода за пределами main(){…} и что означают команды там. Расписывая построчно алгоритм программы, мы эти команды не учитывали.

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

Рассмотрим пример:

import std.stdio; import std.stdio; import std.stdio;

int a = 5; int b = 10; //!!! int a = 5;
int b = 10; int a = 5; //!!! int b = 10;

void main(){ void main(){ void main(){
a = b; a = b; b /= 2; //!!!
b /= 2; b /= 2; a = b; // !!!
write( a,' ', b); write( a,' ', b); write( a,' ', b);
} // вывод: 10, 5 } // вывод: 10, 5 } // вывод: 5, 5

Обратите внимание на выделенные строчки: в них изменен порядок. В первом случае это не повлияло на работу программы, а во втором – повлияло. Почему?

Вспомните, мы говорили, что выполнение программы начинается после слов main(){ и заканчивается с закрывающей фигурной скобокой }. Таким образом, внешний скоуп оказывается статическим и команды, которые стоят в нем, компилируются, а не выполняются, их порядок следования не важен. К моменту запуска программы в ней уже есть две переменные a и b с заданными исходными значениями 5 и 10 соответственно.

Некоторые команды не могут быть написаны в статическом скоупе: например, оператор присвоения. Статические скоупы предназначены для оформления интерфейса кода программы, поэтому обычно используются для объявления глобальных переменных, формирования новых типов, классов, структур, назначения псевдонимов и прочего.

Скоуп main(){…}, помимо того, что является динамическим, то есть команды в нем выполняются по ходу работы программы, является еще и первым скоупом, с которого начинается выполнение любой программы.

Важно уметь различать динамические и статические скоупы. Динамическими скоупами являются: скоуп main(){…}, скоуп определния функции, скоупы условных операторов, циклов, ветвлений и других statement'ов, другие скоупы, которые открыты внутри динамических, если они не определены как статические.

Статические скоупы: внешний скоуп любого модуля, скоуп определения структуры или класса.

Следующий пример иллюстрирует эти скоупы. Здесь статические скоупы и команды помечены буквой s, а динамические – буквой d.

s import std.stdio;		//внешний скоуп кода программы - статический
s 	
s struct MyStruct		//далее следует определение структуры - статический
s {
s 	int number;	
s 	
s 	void memberFunction()		//определение члена-функции - динамический
d	{
d		number += 5;
d	}
s }
s 	
s int MyFunction(int arg)		//определение регулярной функции - динамический
d {
d	return arg + 10;
d }
s	
s void main(){		//main(){ открывает динамический скоуп
d	
d	int a;
d	
d	class MyCLass		//определение класса - статический
s	{
s		double number;
s		void memberFunction(int count)	//определение члена-функции
d		{
d			for( int i; i < count; ++ i)
d			{
d				number *= 2; 
d			}
d		}
s	}
d	
d	if( a == 0)		//скоуп услновного оператора if(...) - динамический
d	{
d		writeln( a);
d	
d	}
d	
d	{		//просто внутренний динамический скоуп
d		int b = a;
d		writeln( b);
d	}
d }

Обратите внимание на несколько вещей. Статические скоупы могут быть внутри динамических и наоборот, при этом они не конфликтуют: характер скоупа определяет внутренний скоуп, не влияя на внешний. Внешний скоуп продолжается после закрытия внутреннего и сохраняет свой характер. Может показаться странным, что код выполняется построчно сверху вниз, но при этом мы видим динамические скоупы до main() – это видно в члене-функции структуры и в регулярной функции. Действительно, если программа начинает выполняться с main(){, то как она попадет в эти скоупы сверху? Все просто: как только в теле main() появляется одна из этих функций, она начинает выполняться, при этом "указатель", который сопровождает выполняемые строчки, перескакивает в тело этой функции. Как только все строки внутри функции выполнены, указатель возвращается туда, откуда его вызвали. В теле функции может быть вызвана и другая функция – любое количество функций может выполняться одна внутри другой. При этом внешняя функция всегда ожидает выполнения внутренней. Подробнее об этой концепции можно узнать в главе "функции" (http://ddili.org/ders/d.en/functions.html).

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

Также заметим, что в конце main()мы свободно открыли и закрыли очередной скоуп. Этот скоуп, так же как и внешний по отношению к нему, стал динамическим. В языке D можно организовывать скоупы даже без их привязки к statement'ам, функциям, структурам или классам, однако это используется крайне редко и так можно делать только в динамическом скоупе.

Пространство имен

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

Внутреннее пространство имен включает все внешние пространства, но не наоборот. Пример:

import std.stdio;

void main(){	// здесь начинается скоуп main()
	int a;	// переменная a объявлена скоупе main()
	{
		int b;	// переменная b объявлена во внутреннем скоупе
		write( a);	// компилируется
		write( b);	// компилируется - обе переменных доступны
	}
	write( a); 	// компилируется - a доступна в main()
	write( b);	//!!! не копмилируется - b недоступна во внешнем скоупе
}

С того момента, как была объявлена переменная a, она доступна для обращения любой строчкой ниже. То же касается и переменной b. Однако переменная b была объявлена внутри вложенного скоупа, поэтому пространство имени переменной b начинается со строки ее объявления и заканчивается, как только заканчивается этот скоуп. Переменная же a доступна и во вложенном скоупе, она перестает быть доступной только по выходу из main(){...}. По этой причине последняя команда вывода на экран не может быть скомпилирована.

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

Еще одно правило касается определений функций, наследованных классов и statement'ов. Суть в том, что все имена, которые были введены в списке аргументов функции, или использовались для определения цикла, или существовали в родительском классе, входят в пространство имен образовавшегося скоупа. Пример:

void main(){
	int a;
	for( int i; i < 10; ++ i)
	{
		a += i;
	}
}

Как мы видим, переменная i была объявлена внутри определения цикла for, и она доступна в скоупе этого цикла.

Как известно, дважды вводить одно и то же имя в одном пространстве имен нельзя – такой код не скомпилируется. Есть одно исключение: при перечислении аргументов члена-функции класса или структуры может использоваться имя какого-то поля этого класса или структуры. Как говорят, оригинальное имя при этом "затирается", и по этому имени теперь можно обратиться только к аргументу функции. Но имя оригинального поля не пропадает: оно все еще доступно через ключевое слово this с точкой после него. Так, следующий код описывает класс, конструктор которого присваивает полю класса number значение своего аргумента, который называется так же:

class MyClass
{
	int number;		// объявили поле number
	this( int number)	// во вложенном скоупе второе объявление с именем 
				// number затирает его оригинальное значение
	{
		this.number = number;	// number означает то, что было объявлено 
						// в списке аргументов функции
						// старое значение number доступно через 
						// this.number
	}
}
Рекомендация

В целом при размещении нужных команд внутри динамического скоупа у программиста имеется некоторая вольность, не говоря уже о статических. Хотя синтаксис D этого не требует, существуют "правила хорошего тона" в написании программ. Так, чаще всего вводить определения и объявления рекомендуется непосредственно перед тем, как ими начинают пользоваться. Если вы объявляете переменные непосредственно перед тем, как они входят в дело, отпадает необходимость комментариев: сразу понятно, для чего нужна эта переменная.

Так же стараются держать пространства имен по возможности беднее. Чем больше имен доступно в одном скоупе, тем больше вероятность запутаться в этом обилии имен и больше возможность того, что одно имя затрет другое (хотя чаще конфликт имен приводит к ошибке компиляции. Например, такое возможно при импорте сразу нескольких библиотек, содержащих одинаковые). Для этих целей переменные объявляют так "глубоко", как это возможно, чтобы их пространства имен кончались возможно скорее. Также зачастую из библиотек импортируют только одну или несколько необходимых функций или типов, не импортируя ее целиком. Более того, если переменные объявляются только в том скоупе, где они используются, то место в памяти для них будет выделяться по мере надобности, а не сразу большим объемом, и если пространства имен уже использованных и уже ненужных переменных будут постепенно закрываться, то место будет освобождаться по мере выполнения программы и она в целом не займет много места в ОЗУ.

Существует и много других способов упростить код, сделать его читаемым, а программу – легковесной и эффективной. Язык D так богат, что в одной статье охватить все возможные советы по организации кода невозможно.

Чтение документации

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

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

Модули

Подробнее о модулях и библиотеках можно узнать здесь: http://ddili.org/ders/d.en/modules.html.

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

Язык D позволяет подключать сразу несколько модулей, импортировать только некоторые определения из модуля вместо того, чтобы импортировать его целиком, делать локальные импорты, расширяющие пространство имен только внутри какого-то скоупа, а не во всей программе, давать определениям из модуля новые имена – псевдонимы, разграничивать доступные и недоступные для импорта определения внутри модуля и делать многое другое. Следующий короткий пример пояснит некоторые из этих особенностей.

Файл mymodule.d:

/* Это модуль, который   *
 * называется "mymodule" */

module mymodule;	// название модуля идет после слова module в первой некомментированной строке
					// если название не указано, модуль называется так же, как файл, только без расширения .d

public:		// по умолчанию все определения public: они доступны для импорта и обращения 

int fastDoubling(int arg)	// импортировав этот модуль, можно будет использовать эту функцию
{
	if(arg >= 100){
		import std.stdio : writeln;		// локальный импорт: импортируемая функция writeln() доступна в этом скоупе
										// также мы импортировали только одну функцию из модуля std.stdio, а не его содержимое целиком
		writeln("Argument is too large, computing result in a long way...");	// импортированная функция доступна 
		return arg * 2;
	}	// скоуп закрылся, далее writeln(...) уже недоступна
	return doubledNumbers[arg];	// эта функция возвращает удвоенное число, не тратя время на умножение
}

private:	// ниже идет секция private: она недоступна для импорта, этот модуль пользуется ей сам

int[100] doubledNumbers;	// к этому массиву нельзя обратиться в программе, куда импортирован модуль

static this(){	// этот раздел содержит операции, которые подготавливают модуль к работе
				// это динамический скоуп, который выполняется однажды в начале работы программы, использующей модуль
	foreach( i, ref element; doubledNumbers){

		element += i * 2;
		
	}
}

static ~this()	// аналог static this() для корректного завершения работы модуля, выполняется один раз в конце работы программы
{}

/* в этом модуле нет main(), поэтому его нельзя использовать как программу     *
 * тем не менее, он компилируется в составе другого модуля, содержащего main() *
 * как его часть. */

Файл app.d:

import std.stdio;
import mymodule;	// теперь функция int fastDoubling(int arg) доступна

void main(){
	int result = fastDoubling(15);	// вызов функции, определенной в другом модуле
	writeln( result);
}

Как мы видим, функция, определенная в модуле mymodule.d, доступна в другом модуле при подключении. Файл app.d компилируется и скомпилированный код выдает верный результат:

30

Остальные особенности работы с модулями и библиотеками вы найдете в соответствующих главах учебников и других источниках.

Чтение документации

Модульность языка позволяет вынести куски кода в отдельные файлы, каждый из которых будет специализирован для решения своего круга задач. Комплексные задачи, решаемые программистом, могут быть решены путем использования нескольких готовых модулей, подключенных к основному коду программы. Первое, с чем сталкивается программист – с библиотекой http://dlang.org/phobos/ - Фобос, которая является стандартной библиотекой языка D и просто импортируется в коде программы – стандартный путь к ней уже указан (если на компьютере правильно установлен компилятор). Впоследствии придется прибегнуть к помощи других библиотек и модулей, написанных обычными программистами или организациями и размещенных в сети. В любом случае, чтобы грамотно и эффективно использовать подключенный модуль, нужно уметь читать к нему документацию. Обычно в документации указывается лишь интерфейс тех определений, которые предоставляет модуль или библиотека. Принципы работы кода модуля не раскрываются, что соответствует принципу черных ящиков. Иногда модули оказываются плохо документированы или не документированы вовсе, и приходится разбираться с ними, читая исходный код или разбирая некоторые иллюстративные примеры. Для библиотеки Фобос по указанному адресу можно найти документацию ко всем ее модулям.

В модулях можно найти разные виды определений. Это могут быть функции, структуры, классы и интерфейсы. Для них обязательно указывается их интерфейс, то есть для функций это типы входных и выходных данных, возможные сторонние эффекты. Для классов, структур и интерфейсов это доступные пользователю (public) поля и члены-функции (методы). В модуле могут быть определены и другие новые типы: enum, union и другие. Могут быть введены alias – псевдонимы для каких-то типов или переменных. Могут быть объявлены некоторые переменные, часто это immutable или const, которые используются как часто встречающиеся в выражениях константы, или массивы констант, например, табулированные функции, или слова и выражения, из которых составляются выдаваемые на экран сообщения.

Первый шаг

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

type_name function_name(type1 arg1, type2 arg2, /*...*/ typen argn)
{
	/*...*/
}

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

Однако в действительности мы можем встретить более сложный и непонятный интерфейс функции:

/* ?     ?        ?         ?    type   func_name   ?    arg_type   arg  */
pure  nothrow  @property  @safe  void   timeOfDay( in   TimeOfDay   tod);

(взято из модуля std.datetime: http://dlang.org/phobos/std_datetime.html#.DateTime.timeOfDay)

Такого кода не нужно пугаться. Определения в документации могут содержать много непонятных слов, но структура у этого определения всегда одна и та же. Первое, что нужно делать всегда – выделить самое главное. Отбросив лишнее, от нашего "страшного" определения останется:

/*type  func_name   arg_type   arg  */
void    timeOfDay( TimeOfDay   tod);

Теперь это функция, которая называется timeOfDay, не возвращает никакого значения (тип – void) и имеет один аргумет tod типа TimeOfDay. В большинстве случаев это все, что нужно знать о timeOfDay для того, чтобы использовать ее в своей программе. Поясним, по какой схеме нужно отбрасывать "лишние" слова для получения красивого и краткого определения.

Для функций

От функции должна остаться такая схема:

type_name function_name(types arguments);

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

Если перед типом какой-то переменной стоит ключевое слово in, out или ref, то имеет смысл урезать определение до такого состояния, где в списке аргументов могут встречаться не только пары, но и тройки:

type_name function_name(keywords types arguments);

Здесь вместо keywords перед некоторыми из аргументов могут стоять перечисленные выше ключевые слова. Их нельзя опускать, потому что они непосредственно связаны с тем, что и как делает функция, в частности, с ее сторонними эффектами. Так, некоторые аргументы функции могут выступать в роли результатов ее работы, а не входных данных.

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

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

void Twice( ref int arg)
{
	arg *= 2;
}

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

	twice( 5);	// <- compilation error

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

out – таким ключевым словом помечаются аргументы, которые могут служить только результатом работы. Они передаются по ссылке, то есть функция может модифицировать их значения, а кроме того, перед попаданием в функцию их значения обнуляются – им присваивается значение type.init – исходное значение для данного типа. Таким образом, функция никак не может использовать их как входные данные. Очевидно, что таким параметром не может быть литерал, так же как и для ref.

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

Итак, общая схема такова: оставляем два последних слова перед скобками (это будут тип, возвращаемый функцией, и ее название), если есть две пары скобок, то смотрим только на вторые, а в скобках оставляем только пары слов, перечисленные запятыми – это будут типы и названия аргументов. Также обращаем внимание на слова in, out, ref - такие слова образуют уже не пары, а тройки из ключевого слова, типа и названия аргумента. Все то же самое относится и к методам, входящим в состав классов, структур и интерфейсов, потому что они по своей сути – те же функции. У них ключевые слова могут появляться также и после скобок, на первом шаге их все можно игнорировать.

Для структур, классов и интерфейсов

Обычно структура, класс или интерфейс объявляются просто:

struct    Struct;
class     Class;
interface Interface;

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

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

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

class SubClass : SuperClass, Interface1, Interface2;

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

Для всего остального

Модуль может предоставлять enum, который является набором именованных констант, чаще всего используется для задания режимов работы, хранения информации и прочего. Модуль может объявлять какие-то переменные, обычно это просто константы, которые часто используются. Часто объявляются неизменные строки типа immutable (string), которые служат какими-то названиями, сообщениями или словами, которые должны выводиться на экран или просто использоваться в работе программы. Все эти вещи определяются и объявляются самым обычным образом, и в документации определения к ним обычно представлены полностью.

Второй и последующие шаги

Если минимальной информации об определениях модуля недостаточно, нужно разбираться со всем остальным. Как мы увидели раньше, наибольший интерес в этом смысле представляют функции. Базовые типы и производные от них: константы, енумы, псевдонимы и прочее – определяются обычным образом. Структуры, интерфейсы и классы содержат в своем составе обычные поля и методы, которые тоже являются функциями. Поэтому разберем наиболее общий случай функции.

Определение функции в общем виде может выглядеть так:

keywords... behavior... safety_level type_name function_name(keywords types args = default) keywords...;

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

Типы

В указанной схеме тип фигурирует дважды: как тип, который возвращает функция type_name, и как тип аргументов types. Стоит отдельно подчеркнуть, что тип необязательно состоит из одного слова. Несколько примеров:

int
int[5]
int[string]
const int
immutable int
immutable(int)[]

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

/*         keyword   |--type--|  arg */
int twice(   ref     const  int  arg)
{
	int result = arg * 2;
	return result;
}

То есть в общей схеме type_name и types может быть представлен комбинацией слов, а не только одним словом. Чтобы не запутаться, можно делать запись без пробелов:

int twice( ref  const(int)  arg);

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

Здесь же стоит отметить ключевое слово inout. Мы уже рассмотрели другие ключевые слова, которые могут характеризовать, каким образом в функции используется тот или иной аргумент. Перед каждым перечисленным аргументом в списке может быть или не быть одно ключевое слово, то есть аргументы могут следовать не парами между запятыми, а тройками. inout – еще одно ключевое слово, которое может появиться наряду с ref, in или out. Но его значение связано с типом функции и аргумента, поэтому оно вводится здесь, а не раньше.

Слово inout обязательно появляется в определении дважды: перед типом функции и перед одним из аргументов:

inout int twice( inout int arg);

Это ключевое слово означает, что модифицируемость (mutability) функции определяется модифицируемостью ее аргумента. Модифицируемость может быть трех видов: mutable (устанавливается по умолчанию), const и immutable. Со словом inout результат, который возвращает функция, имеет ту же mutability, что и аргумент. По этой причине часто пишут так:

inout(int) twice( inout(int) arg);

Эта запись может быть более понятна: вместо inout может стоять слово const, immutable или ничего не стоять в зависимости от того, от какого параметра вызывается функция. Из последнего кода можно сделать вывод, что inout можно отнести как к типу, так и к ключевому слову в перечислении аргументов. Однако стоит помнить, что нельзя одновременно использовать одно из слов ref, in или out вместе с inout.

keywords...

Рассмотрим ключевые слова, которые могут находиться перед именем функции. Это могут быть: ref, inout или auto ref. inout мы уже рассмотрели: он обязательно появляется дважды – перед названием функции и перед одним из аргументов. ref означает примерно то же, что и ключевое слово ref для аргументов. Аргументы, помеченные словом ref, передаются как псевдонимы (alias) параметров. Точно так же результат, возвращаемый функцией, помеченной как ref, возвращается как псевдоним одной из переменных в теле функции. Хороший пример:

ref int greater(ref int first, ref int second)
{
	return (first > second) ? first : second;
}

Эта функция возвращает больший из двух своих аргументов по ссылке, то есть не копируя его значение. Использование ключевого слова ref перед функцией имеет определенные ограничения и особенности, о которых можно в подробностях узнать в соответствующей главе учебника или в других источниках (http://ddili.org/ders/d.en/functions_more.html).

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

behavior...

Разные функции ведут себя по-разному, и можно выделить некоторые определенные виды функций, обладающих некоторыми свойствами. Обеспечить эти свойства – забота программиста, но если они обеспечены, то можно пометить функцию определенным образом и тем самым гарантировать, что условия соблюдены. Функция может быть помечена как pure, nothrow и @nogc. У одной функции могут быть прописаны одновременно несколько из этих свойств.

pure

"Чистые" функции, помеченные как pure, обладают следующими свойствами: они не меняют модифицируемого глобального или статического состояния программы. Простыми словами, эти функции не производят никаких сторонних эффектов, они могут только возвращать значение. Понятие чистых функций важно потому, что им отдается предпочтение: они всегда просты и понятны. Эти функции не могут ничего печатать на экран, не могут обращаться к модифицируемым глобальным переменным (то есть не внутренним, объявленным не в теле функции) , они могут только использовать свои аргументы или обращаться к глобальным немодифицируемым переменным (константам). Общий вывод из этого можно сделать такой: чистые функции для любого состояния программы возвращают одни и те же значения для одних и тех же значений параметров, то есть работа таких функций зависит только от параметров.

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

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

Очевидно, чистая функция в своем теле может вызывать только чистые же функции. Также это касается наследования у классов и интерфейсов: если в родительском классе метод был помечен как чистый, в подклассе он обязательно должен быть переписан также как чистый. Нечистые же методы можно переписывать как чистыми, так и нечистыми методами.

nothrow

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

@nogc

GC – Garbage Collector – сборщик мусора, который автоматически работает в программах, написанных на D. Сборщик мусора позволяет программисту не заботиться о возвращении занятой памяти и о распределении объектов в памяти компьютера: это происходит автоматически и по возможности наиболее оптимальным способом. Взамен сборщик мусора занимает некоторые ресурсы компьютера. В некоторых редких случаях память может быть распределена вручную, или строгий контроль за этим вовсе не требуется. В этом случае функции, не содержащие операций, вызывающих работу сборщика мусора, помечаются как @nogc, что гарантирует, что сборщик мусора в этой функции не используется. Работа сборщика мусора обычно связана с модификацией слайсов, с работой с классами и другими операциями. Для нас это означает, что если мы не хотим использовать сборщик мусора, то мы должны отдавать предпочтение функциям @nogc. Остальные особенности использования этого слова такие же, как и для pure и nothrow.

safety_level

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

@safe

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

@trusted

Если функция не может быть помечена как @safe (например, из-за использования в ее теле потенциально опасных методов и функций), но программист доверяет ей и считает, что она, несмотря на это, не может привести к некорректной работе памяти, ее можно пометить как @trusted. Для того, кто читает документацию, @safe и @trusted практически эквивалентны: разработчик модуля уже проверил функцию и он уверен, что она безопасна настолько же, насколько могла бы быть @safe.

@system

Ключевым словом @system помечаются функции, не удовлетворяющие вышеперечисленным требованиям безопасности. Уровень безопасности @system функции получают по умолчанию, так что можно сказать, что все функции, не помеченные словами @safe или @trusted, обладают уровнем безопасности @system. В документации словом @system можно только подчеркнуть, функция не является ни @safe, ни @trusted.

keywords... после функции

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

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

const

Ключевое слово const гарантирует, что функция не изменяет объекта, на котором она вызвана. Чаще всего это функции, которые предоставляют какую-то информацию об объекте (структуре или классе), но не изменяют ее полей. Например, функция, которая печатает на экран остаток топлива в баке автомобиля может быть помечена как const, а функция, которая увеличивает количество топлива – не может.

inout

Как уже было сказано, ключевое слово inout всегда появляется дважды в определении. Один раз это слово определяет модифицируемость типа результата, который возвращает функция. Случай, когда вид этой модифицируемости выводится из аргументов функции, мы уже рассмотрели. Модифицируемость также может быть выведена из модифицируемости всего объекта, на котором вызван метод: структуре или классе. Для этого второй раз ключевое слово inout употребляется не перед одним из аргументов, а после скобок списка аргументов:

struct Struct
{
	int number;
	inout(int) memberFunc() inout
	{
		return number;
	}
}

Метод memberFunc возвращает число int такой же модифицируемости, какой обладает весь объект Struct:

	immutable(Struct) S = Struct(10);
	auto num = S.memberFunc;
	assert( is( typeof( num) == immutable(int) ) );
			//  тип num это immutable(int)

keywords при аргументах функции

Мы уже рассмотрели некоторые ключевые слова, которые могут характеризовать аргументы функции: refv, in, out и inout. Рассмотрим еще три, которые меньше влияют на способ использования функции, только на ее работу, а потому не были упомянуты раньше.

lazy

Ключевое слово lazy меняет порядок вычисления функций, если параметры одной функции зависят от результатов другой. Рассмотрим пример:

	a = Div( Sum( b, c), d);

В этом примере сначала вычисляется значение функции Sum от параметров b и с, а затем этот результат используется как первый параметр функции Div: функция Sum вычисляется первой. Однако на ноль делить нельзя, поэтому если d=0, то в вычислении Sum( b, c) нет никакого смысла: это окажется пустой потерей времени. По этой причине первый аргумент функции Div может быть помечен как lazy (ленивый):

int Div( lazy int arg1, int arg2)
{
	if( arg2 == 0){
		assert( 0, "На ноль делить нельзя!");
	}
	return arg1 / arg2; 	// <- Sum начнет вычисляться здесь
}

В этом случае вычисление функции Sum будет отложено до тех пор, пока оно не потребуется в теле функции, что позволяет иногда избежать выполнения лишних операций. Если вторым параметром функции Div окажется 0, будет возвращена ошибка, а Sum не будет вычислено вовсе. В качестве такого параметра по-прежнему можно передавать переменные или литералы, это необязательно должны быть функции, хотя свойство lazy повлияет только на случай, когда параметром является результат вычисления функции.

scope

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

shared

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

Значения по умолчанию

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

int Sum( int a, int b, int c = 0)
{
	return a + b + c; 
}

В приведенном примере для аргумента c задано значение по умолчанию 0. Функция теперь может быть вызвана как от трех, так и от двух параметров, при этом она работает так, как если бы значение последнего параметра было равно 0:

	assert( Sum( 5, 5, 5) == 15);
	assert( Sum( 5, 5) == 10);

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

Значения по умолчанию можно задавать только для аргументов, которые идут в конце:

int Sum( int a, 		int b, 	int c = 0);	// <- допустимое определение
int Sum( int a, 		int b = 0, 	int c = 0);	// <- допустимое определение
int Sum( int a = 0, 	int b = 0, 	int c = 0);	// <- допустимое определение
int Sum( int a = 0, 	int b, 	int c);	// <- ошибка компиляции
int Sum( int a = 0, 	int b = 0, 	int c);	// <- ошибка компиляции

Значение по умолчанию – это необязательно литерал. Это может быть и какое-то более сложное выражение:

class Class
{}

void func( Class arg = new Class() );

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

Произвольное количество параметров

Хотя мы уже рассмотрели схему функции, представленную выше, в полном объеме, сама эта схема не полна. Существует еще три особенности, которые нужно рассмотреть.

type_name function_name(types args ...);

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

Мы видим, что на конце списка аргументов в скобках стоит троеточие. Это означает, что количество параметров, от которых вызывается функция, произвольно. Рассмотрим пример:

int Sum( int[] arg ...);

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

Эта функция может быть вызвана от произвольного количества слагаемых и возвращает их сумму:

	int sum = Sum( 1, 2, 3, 4);
	assert( sum == 10 );

Функции, у которых количество параметров может меняться, называются вариадическими (variadic functions). Известный пример такой функции – функция writeln(...) из модуля std.stdio, которая может одновременно принять и вывести на экран любое число параметров.

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

	int[] array = [1, 2, 3, 4];
	int sum = Sum( array);

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

void func( int arg1, int arg2, int[] args ...);  // <- допустимое определение

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

	int sum = Sum();
	assert( sum == 0);

Шаблоны

Иногда встречается схема:

type_name function_name(...)(types args);

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

Шаблоны-функции

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

T Sum(T)( T a, T b)
{
	T result = a + b;
	return result;
}

Мы видим, что тип, возвращаемый функцией, а также типы аргументов и тип внутренней переменой result определены как T. Для каждого места в коде программы, где такая функция вызывается, выводится (instantiate) определенный вариант этой функции, соответствующий условиям вызова, а вместо слова T везде ставится какой-то конкретный тип:

void main(){
	int a, b;
	int sum = Sum!int( a, b);
}

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

int Sum( int a, int b)
{
	int result = a + b;
	return result;
}

Которая просто вернет сумму своих аргументов. Если функция-шаблон используется в программе много раз для разных типов, то для каждого случая будет выведена (instantiated) своя функция, специализированная под конкретный тип. Таким образом, особенность шаблонов успешно борется с необходимостью перегрузки функций для разных типов аргументов, разных возвращаемых типов и разных типов внутренних переменных функций. Тип T – это и есть параметр шаблона, в этом случае параметр единственный.

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

	int sum = Sum( a, b); // <- допустимый вызов функции

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

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

T[] array( Count, T = int)( Count count)
{
	T[] result;
	for( Count i; i < count; ++ i){
		result ~= T.init;
	}
	return result;
}

Эта функция возвращает слайс типов T, состоящий из исходных значений этих типов, причем длина массива определяется параметром функции count типа Count, а тип слайса по умолчанию – int:

	assert( array!(int, int)(3) == [0, 0, 0] );
	assert( array!(int)(3) == [0, 0, 0] );
	assert( array(3) == [0, 0, 0] );
Особые выводы

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

T Sum(T)( T a, T b)
{
	T result = a + b;
	return result;
}

T Sum(T : string)( T a, T b)
{
	T result = a ~ b; // <- здесь другой оператор!!!
	return result;
}

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

Другие виды параметров шаблона. Параметры-значения

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

string line( int length)( char letter)
{
	string result;
	for( int i; i < length; ++ i){
		result ~= letter;
	}
	return result;
}

возвращает строку, состоящую из символа , определяемого ее аргументом, длиной length:

	assert( line!4('b') == "bbbb");

Параметры шаблона-значения также могут иметь значение по умолчанию, которое указывается после знака =.

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

string line( int length, char letter)

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

Ключевые слова для параметров-значений

В некоторых случаях при определении значений-параметров шаблона используются ключевые слова, в результате чего эти параметры при работе программы (вообще – при ее компиляции) самостоятельно принимают те или иные значения. Для этого одно из ключевых слов указывают после знака =, как если бы для параметра было указано значение по умолчанию. Ключевые слова могут быть следующими: __MODULE__ __FILE__ __LINE__ __FUNCTION__ __PRETTY_FUNCTION__. Через такие параметры шаблон может получать информацию о том, в каком месте исходного кода программы он был выведен и вызван: это может быть определенная строка, имя файла или название функции, внутри которой произошел вызов шаблона. Поскольку пользователю подключенного модуля не нужно вручную вписывать значение этих параметров (они определяются компилятором сами) и поскольку они влияют только на внутреннюю работу вызванной функции, пользователь в большинстве случаев может игнорировать такие параметры шаблонов.

Еще одна особенность касается методов структур и классов:

class Class
{
	void foo(this Type)()
	{}
}

Здесь шаблоном является метод foo класса Class, и его параметр шаблона помечен как this. Через такой параметр в шаблон передается тип объекта, на котором вызван метод (в данном случае это будет Class). Этот параметр также передается автоматически, и эта особенность может в большинстве случаев не учитываться пользователем модуля.

Передача параметра шаблона через псевдоним

Помимо слова this, перед названием параметра шаблона может следовать слово alias. В этом случае параметр не получает значения сам, но вместо того, чтобы принять то значение, которое ему будет присвоено пользователем, он становится псевдонимом для него:

void Set( alias obj)( int arg)
{
	obj = arg;
}

void main(){
	int num; 
	Set!num(5);
	assert( num == 5);
}

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

Параметры-кортежи

Кортежи (tuples) – еще одна интересная особенность языка D. Мы знаем, что в D есть переменные, и что из них можно составлять массивы. Мы также знаем, что есть другой вид информации, который так или иначе можно передавать или использовать – типы. Кортежи позволяют передавать значения или типы по нескольку сразу, как если бы типы можно было объединять в массивы и смешивать со значениями или названиями переменных. Одним из примеров кортежа может служить список аргументов, который перечисляется через запятую при объявлении функции. Еще одним примером кортежа может служить список параметров, который также перечисляется через запятую и передается в функцию уже при ее вызове.

Кортежам и работе с ними посвящена отдельная глава учебника: http://ddili.org/ders/d.en/tuples.html. Здесь мы рассмотрим только один пример, демонстрирующий возможность применения кортежей в качестве параметров шаблона:

void info(T...)(T args){}

Пример взят отсюда: http://ddili.org/ders/d.en/templates_more.html.

Здесь из-за троеточия после слова T этот параметр шаблона становится уже не типом и не значением, а кортежем, причем кортежем вариадическим (переменной длины, variadic tuple). Поскольку аргумент args обозначен как аргумент, имеющий тип T, то он сам по себе тоже становится кортежем. Разница в том, что T выражает собой кортеж, состоящий из типов, а args –из значений. Пример использования такой функции:

	info(1, "abc", 2.3);

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

Другие виды шаблонов

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

class Vector( T, int dim = 3)
{
	T[dim] coord;
}

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

	auto vect = new Vector!( double, 2)();

Теперь переменная vect представляет собой двумерный вектор с координатами типа double.

Ограничения на параметры шаблона

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

type_name function_name(...)(types args)    if(...);

В этой схеме мы видим, что сразу после названия функции-шаблона и списка аргументов следует оператор if. Это – ограничения (constraints) на параметры функции. Эта особенность работает только с шаблонами. if, который стоит сразу после определения и непосредственно перед телом функции, работает практически так же, как и обычный if: тело функции выполняется (на самом деле – компилируется) только в том случае, если выражение внутри if верно (true). Разница в том, что этот if – статический, то есть он выполняется на этапе компиляции, а не при работе программы. На самом деле, если этот if не будет удовлетворен, то функция не скомпилируется вообще. Как следствие, выражение в этом if не может содержать локальных или глобальных динамических переменных, оно не может зависеть от значений параметров функции, потому что эти значения доступны только в процессе работы программы. Поскольку этот if выполняется на этапе компиляции, то невыполнение условий, заключенных в скобки, влияет не на работу программы, а на процесс компиляции: компилятор выдаст ошибку. Таким образом, ограничения при шаблонах используются скорее для отладки кода программы, чем для совершения каких-то операций в процессе ее работы. Рассмотрим уже упомянутый выше пример:

T Sum(T)( T a, T b)
{
	T result;
	result = a + b;
	return result;
}

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

	int a = 5;
	int b = 6;
	int sumi = Sum!(int)( a, b); 	// для целых чисел
	assert( sumi == 11);

	double c = 1.25;
	double d = 1.5;
	double sumd = Sum!(double)( c, d);	// для дробных чисел
	assert( sumd == 2.75);

Хотя оператором "+" нельзя складывать строки, функция Sum, согласно определению, может быть выведена для любого типа:

	string a = "aaa";
	string b = "bbb";
	string sum = Sum!(string)( a, b);	// для строк

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

T Sum(T)( T a, T b)
		if( is( T == int) || is( T == double));

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

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

T Sum(T)( T a, T b) if( is( T == string) )

Разница в определении лишь в том, что на этот раз ограничение допускает использование только типа string, название же функции, ее возвращаемый тип и параметры функции и шаблона остались прежними. Таким образом, мы создали две функции-шаблона с одинаковым названием и аргументами, но с разными ограничениями, и с учетом этой перегрузки мы теперь можем вызывать функцию Sum от аргументов трех разных типов: int, double и string. В случае со специализацией мы могли использовать любой тип, но для string функция работала по-другому. Таким образом, с помощью ограничений можно полностью исключить нежелательные значения параметров шаблона, оставив только несколько, а с помощью специализаций из всего спектра возможных значений можно выделить особые и изменить тело функции для них.

Также отметим, что ограничения на параметры шаблона, которые были рассмотрены выше для функций, также работают и с другими видами шаблонов. Вот простой пример для ограничения на параметры шаблона-класса:

class Vector( T, int dim = 3) if( (is(T == double) || is(T == int)) && dim > 0 )

Таким образом, в программе могут использоваться векторы с координатами только типов int или double, а мерность пространства естественным образом должна быть положительной. Понятно, что в противном случае (если dim будет отрицательным) компилятор даже не сможет создать массив отрицательной длины, но благодаря ограничениям это станет ошибкой того, кто пытается создать такой вектор, а не программиста, который написал этот шаблон.

Выражение is()

Отдельного внимания заслуживает выражение is, которое особенно часто используется в ограничениях на параметры шаблона. Это выражение нужно для выполнения логических операций на этапе компиляции. Любое выражение, помещенное в скобки оператора is(...), вычисляется не при работе программы, а при компиляции. Поэтому все входящие в него значения должны быть известны на момент компиляции. Выражение is может использоваться многими способами, некоторые из которых можно увидеть в следующем примере:

// выражение is принимает значение true, если:
static assert( is(int) ); // аргумент имеет действительный тип
static assert( ! is(blabla) ); // тип blabla недействителен: его не существует, он не был определен ранее в программе

static if( is(int AliasForInt) ) // то же, плюс тип получает псевдоним
{
	AliasForInt a = 5;
}

static assert( is(short : int) ); // если тип может быть автоматически конвертирован в указанный после : (может быть использован как указанный)

static if( is(short AliasForShort : int) ){ // то же, плюс тип получает псевдоним
	AliasForShort b = 5;
}

int num;
static assert( is(typeof(num) == int) ); // если тип совпадает с указанным

class Class{}
static assert( is(Class == class) ); // если тип подходит под определение. Используется со словами:
// struct union class interface enum function delegate const immutable shared

static if( is(Class AliasForClass == class) ){} // То же, плюс тип получает псевдоним. Используется со словами:
// struct union class interface const immutable shared

class SubClass : Class {}
static if( is(SubClass BaseTypesTuple == super) ){} // BaseTypeTuple становится кортежем (tuple), 
//состоящим из родительских классов и интерфейсов типа (ключевое слово – super)

enum state {ready, busy, sleep}
static if( is(state AliasForImplType == enum) ){} // базовый тип enum'a получает псевдоним (ключевое слово – enum)

int func( int arg);
static if( is(func ParameterListTuple == function) ){} // ParameterListTuple становится кортежем, 
//состоящим из параметров функции (ключевое слово – function)

static if( is(func AliasForReturnType == return) ){} // тип, который возвращает функция, получает псевдоним (ключевое слово – return)

int[string] array;
static if ( is(typeof(array) == Value[Key], // если тип соответствует схеме...
			   Value,						// ...и Value - действительный тип...
			   Key : string) ){}			// ...и Key можно использовать как (автоматически конвертировать в) string
// такие сложные is(...) могут содержать и большее число условий
// все последующие условия используют псевдонимы, определенные в первом условии (здесь - Value и Key)

Подробнее об использовании выражения is() и о его значениях можно узнать здесь http://ddili.org/ders/d.en/is_expr.html и в других источниках.

Общий вид шаблона

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

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

template template_name(/*параметры шаблона*/)
{
	/*определения шаблона*/
}

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

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

template MyTemplate(T)
{
    T func(T value)
    {
        return value / 3;
    }

    struct Struct
    {
        T member;
    }
}

void main(){
	auto result = MyTemplate!int.func(42);
	auto s = MyTemplate!double.Struct(5.6);
}

Если какое-то объявление в шаблоне называется так же, как и сам шаблон, то такой шаблон называют одноименным (eponymous) и для обращения к нему точка не нужна:

template Sum(T)
{
	T Sum( T a, T b)
	{
		return a + b;
	}
}

void main(){
	int sum = Sum!int( 5, 15);
}

На самом деле все случаи шаблонов, рассмотренные выше: шаблоны-функции, шаблоны-классы и прочее, являются частным случаем одноименных шаблонов, которые объявляются без дополнительного скоупа и без слова template:

T Sum(T)( T a, T b) // то же самое, что и выше
{}

Еще несколько слов про шаблоны

Шаблоны – одно из самых удачных нововведений языка D, и оно часто используется в библиотеках. И хотя статья преследует цель научить читать документацию к библиотекам, прочитать документацию к шаблону порой оказывается недостаточно для грамотного его применения. Здесь будут упомянуты несколько отличительных особенностей шаблонов.

Шаблоны выводятся и вычисляются при компиляции

Первая особенность опять касается способности не бояться непонятного синтаксиса, умения его расшифровать и понять.

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

int Number(T) = T.sizeof;

Здесь мы объявили шаблон-переменную типа int, которая принимает значение длины в байтах того типа, от которого она выведена:

	assert( Number!short == 2 );

Здесь важно отметить, что при компиляции компилятор пробегает всю программу, ищет все возможные разные выводы для шаблона Number и производит необходимое вычисление, создавая набор шаблонов (в нашем случае – переменных с определенными значениями) для их использования в программе при ее работе. Еще раз подчеркнем, что шаблоны позволяют не только "подставлять" вместо некоторых слов конкретные типы или значения, но и производить вычисления, если это необходимо, также во время компиляции.

Каждый вывод шаблона – свой тип

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

	assert( Number!short != Number!int );

Хотя шаблоном являлась переменная типа int, при выводе шаблона образуются типы, которые называются, например, следующим образом:

Number!short
Number!int
Шаблоны – особенность времени компиляции

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

class Vector(int dim = 3)
{
	double[dim] coord;
}

void main(){
	int dim = getDim();
	auto vect = new Vector!dim();
}

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

Руслан Будаев, buday48@mail.ru