5.1
This commit is contained in:
parent
e57718386b
commit
075f071136
|
@ -0,0 +1,102 @@
|
||||||
|
# 5. Данные и функции. Функциональный стиль
|
||||||
|
|
||||||
|
- [5.1. Написание и модульное тестирование простой функции](#5-1-написание-и-модульное-тестирование-простой-функции)
|
||||||
|
- [5.2. Соглашения о передаче аргументов и классы памяти]()
|
||||||
|
- [5.2.1. Параметры и возвращаемые значения, переданные по ссылке (с ключевым словом ref)]()
|
||||||
|
- [5.2.2. Входные параметры (с ключевым словом in)]()
|
||||||
|
- [5.2.3. Выходные параметры (с ключевым словом out)]()
|
||||||
|
- [5.2.4. Ленивые аргументы (с ключевым словом lazy)]()
|
||||||
|
- [5.2.5. Статические данные (с ключевым словом static)]()
|
||||||
|
- [5.3. Параметры типов]()
|
||||||
|
- [5.4. Ограничения сигнатуры]()
|
||||||
|
- [5.5. Перегрузка]()
|
||||||
|
- [5.5.1. Отношение частичного порядка на множестве функций]()
|
||||||
|
- [5.5.2. Кроссмодульная перегрузка]()
|
||||||
|
- [5.6. Функции высокого порядка. Функциональные литералы]()
|
||||||
|
- [5.6.1. Функциональные литералы против литералов делегатов]()
|
||||||
|
- [5.7. Вложенные функции]()
|
||||||
|
- [5.8. Замыкания]()
|
||||||
|
- [5.8.1. Так, это работает... Стоп, не должно... Нет, все же работает!]()
|
||||||
|
- [5.9. Не только массивы. Диапазоны. Псевдочлены]()
|
||||||
|
- [5.9.1. Псевдочлены и атрибут @property]()
|
||||||
|
- [5.9.2. Свести – но не к абсурду]()
|
||||||
|
- [5.10. Функции с переменным числом аргументов]()
|
||||||
|
- [5.10.1. Гомогенные функции с переменным числом аргументов]()
|
||||||
|
- [5.10.2. Гетерогенные функции с переменным числом аргументов]()
|
||||||
|
- [5.10.2.1. Тип без имени]()
|
||||||
|
- [5.10.2.2. Тип данных Tuple и функция tuple]()
|
||||||
|
- [5.10.3. Гетерогенные функции с переменным числом аргументов. Альтернативный подход]()
|
||||||
|
- [5.10.3.1. Функции с переменным числом аргументов в стиле C]()
|
||||||
|
- [5.10.3.2. Функции с переменным числом аргументов в стиле D]()
|
||||||
|
- [5.11. Атрибуты функций]()
|
||||||
|
- [5.11.1. Чистые функции]()
|
||||||
|
- [5.11.1.1. «Чист тот, кто чисто поступает»]()
|
||||||
|
- [5.11.2. Атрибут nothrow]()
|
||||||
|
- [5.12. Вычисления во время компиляции]()
|
||||||
|
|
||||||
|
Обсуждать данные и функции сегодня, когда даже разговоры об объектах устарели, – это как вернуться в 1970-е. Но, к сожалению, все еще за горами день, когда говоришь компьютеру, что нужно сделать, и он сам выясняет, как это сделать. А пока этот день не настал, функции – обязательный компонент всех основных направлений программирования. По большому счету, любая программа состоит из вычислений, гоняющих данные туда-сюда; возводимые нами замысловатые строительные леса – типы, объекты, модули, фреймворки, шаблоны проектирования – только придают вычислениям нужные нам свойства, такие как модульность, изоляция ошибок или легкость сопровождения. Правильный язык программирования позволяет своему пользователю держаться золотой середины между кодом «для действия» и кодом «для существования». Идеальное соотношение зависит от множества факторов, из которых самый очевидный – размер программы: основная задача короткого скрипта – действовать, тогда как большое приложение вынуждено заниматься поддержкой неисполняемых вещей вроде интерфейсов, протоколов и модульных ограничений.
|
||||||
|
|
||||||
|
Благодаря своим мощным средствам моделирования D позволяет создавать объемистые программы; при этом он старается сократить до разумных пределов код «для существования», позволяя сосредоточиться на том, что нужно «для действия». Хорошо написанные функции на D, как правило, соединяют в себе компактность и универсальность, достигая порой ошеломляющей удельной мощности. Так что пристегнитесь, будем жечь резину.
|
||||||
|
|
||||||
|
[В начало ⮍](#5-данные-и-функции-функциональный-стиль)
|
||||||
|
|
||||||
|
## 5.1. Написание и модульное тестирование простой функции
|
||||||
|
|
||||||
|
Можно с полным основанием утверждать: главное, чем занимаются компьютеры (помимо скучных дел вроде ожидания ввода данных), – это *поиск*. Серверы и клиенты баз данных – ищут. Программы искусственного интеллекта – ищут. (А надоедливый болтун – банковский автоответчик? Тоже ищет.) Интернет-поисковики… ну, с этими ясно. Да и собственный опыт наверняка говорит вам, что, по сути, многие программы, якобы не имеющие с поиском ничего общего, на самом деле тоже довольно-таки много ищут. Какую бы задачу ни требовалось решить, всегда задействуется поиск. В свою очередь, многие оригинальные решения зависят от интеллектуальности и удобства программного поиска. Как и следовало ожидать, в мире вычислений полно понятий, имеющих отношение к поиску: сопоставление c шаблоном, реляционная алгебра, бинарный поиск, хеш-таблицы, бинарные деревья, префиксные деревья, красно-черные деревья, списки с пропусками… все это нам здесь никак не охватить, так что сейчас поставим цель поскромнее – определим несколько простых функций поиска на D, начав с простейшей из них – функции линейного поиска. Итак, без лишних слов напишем функцию, которая сообщает, содержит ли срез значений типа `int` определенное значение типа `int`.
|
||||||
|
|
||||||
|
```d
|
||||||
|
bool find(int[] haystack, int needle)
|
||||||
|
{
|
||||||
|
foreach (v; haystack)
|
||||||
|
{
|
||||||
|
if (v == needle) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Отлично. Поскольку это первое наше определение функции на D, опишем во всех подробностях, что именно она делает. Встретив определение функции `find`, компилятор приведет ее к более низкоуровневому представлению – скомпилирует в двоичный код. Во время исполнения программы при вызове функции `find` параметры `haystack` и `needle`[^1] передаются в нее по значению. Это вовсе не означает, что если вы передадите в функцию массив из миллиона элементов, то он будет полностью скопирован; как отмечалось в главе 4, тип `int[]` (срез массива элементов типа `int`), который также называют *толстым указателем* (*fat pointer*), – это на самом деле пара «указатель + длина» или пара «указатель + указатель», которая хранит только границы указанного фрагмента массива. Из раздела 4.1.4 понятно, что передать в функцию `find` срез миллионного массива на самом деле означает передать в нее информацию, достаточную для получения адреса начала и конца этого среза. (Язык D и его стандартная библиотека широко поддерживают работу с контейнером через его маленького, ограниченного представителя, который знает, как перемещаться по контейнеру. Обычно такой представитель называется *диапазоном*.) Так что в итоге в функцию `find` из вызывающего ее кода передаются только три машинных слова. Как только управление передано функции `find`, она делает свое дело и возвращает логическое значение (обычно в регистре процессора), которое вызвавший ее код уже готов получить. Что ж, как ободряюще говорят в конце телешоу «Большой ремонт», завершив какую-то неимоверно сложную работу: «Вот и все, что нужно сделать».
|
||||||
|
|
||||||
|
Если честно, в устройстве `find` есть кое-какие недостатки. Возвращаемое значение имеет тип `bool`, это очень неинформативно; также требуется информация о позиции найденного элемента, например, для продолжения поиска. Можно было бы возвращать целое число (и какое-нибудь особое значение, например `-1`, для случаев «элемент не найден»). Но хотя целые числа отлично подходят для доступа к элементам массива, занимающего непрерывную область памяти, они ужасно неэффективны с большинством других контейнеров (таких как связные списки). Чтобы добраться до `n`-го элемента связного списка после того, как функция `find` вернула `n`, понадобится пройти по списку элемент за элементом, начиная с его головы – то есть проделать почти ту же работу, что и сама операция поиска! Так что возвращать целое число в качестве результата – плохая идея в случае любой структуры данных, кроме массива.
|
||||||
|
|
||||||
|
Есть один способ, который сработает с разнообразными контейнерами – массивами, связными списками и даже с файлами и сокетами. Надо сделать так, чтобы функция `find` просто отщипывала по одному элементу («соломинке»?) от «стога сена» (`haystack`), пока не обнаружит искомое значение, и возвращала то, что останется от `haystack`. (Соответственно, если значение не найдено, функция `find` вернет опустошенный `haystack`.) Вот простая и обобщенная спецификация: «функция `find(haystack, needle)` сужает структуру данных `haystack` слева до тех пор, пока значение `needle` не станет началом, или до тех пор, пока не закончатся элементы в `haystack`, и затем возвращает остаток `haystack»`. Давайте реализуем эту идею для типа `int[]`.
|
||||||
|
|
||||||
|
```d
|
||||||
|
int[] find(int[] haystack, int needle)
|
||||||
|
{
|
||||||
|
while (haystack.length > 0 && haystack[0] != needle)
|
||||||
|
{
|
||||||
|
haystack = haystack[1 .. $];
|
||||||
|
}
|
||||||
|
return haystack;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Обратите внимание: функция `find` обращается только к первому элементу массива `haystack` и последовательно присваивает исходному массиву более узкое подмножество его самого. Эти примитивы потом легко можно заменить, скажем, специфичными для списков примитивами, но обобщением мы займемся чуть позже. А пока проверим, насколько хорошо работает полученная функция `find`.
|
||||||
|
|
||||||
|
В последние годы большинство методологий разработки уделяют все больше внимания правильному тестированию программного обеспечения. Это верное направление, поскольку тщательное тестирование действительно помогает отслеживать гораздо больше ошибок. В духе времени напишем короткий тест модуля, проверяющий работу нашей функции `find`. Просто вставьте следующий код после (как это сделал я) или до (как сделал бы фанат разработки через тестирование) определения функции `find`:
|
||||||
|
|
||||||
|
```d
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
int[] a = [];
|
||||||
|
assert(find(a, 5) == []);
|
||||||
|
a = [ 1, 2, 3 ];
|
||||||
|
assert(find(a, 0) == []);
|
||||||
|
assert(find(a, 1).length == 3);
|
||||||
|
assert(find(a, 2).length == 2);
|
||||||
|
assert(a[0 .. $ - find(a, 3).length] == [ 1, 2 ]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Все, что нужно сделать, чтобы получить работающий модуль, – это поместить функцию и тест модуля в файл `searching.d`, а затем ввести в командной строке:
|
||||||
|
|
||||||
|
```d
|
||||||
|
$ rdmd --main -unittest searching.d
|
||||||
|
```
|
||||||
|
|
||||||
|
Если вы запустите компилятор с флагом `-unittest`, тесты модулей будут скомпилированы и подготовлены к запуску перед исполнением основной программы. Иначе компилятор проигнорирует все блоки `unittest`, что может быть полезно, если требуется запустить уже оттестированный код без задержек на начальном этапе. Флаг `--main` предписывает `rdmd` добавить ничего не делающую функцию `main`. (Если вы забыли написать `--main`, не волнуйтесь; компоновщик тут же витиевато напомнит вам об этом на своем родном языке – зашифрованном клингонском.) Заменитель функции `main` нужен нам, так как мы хотим запустить только тест модуля, а не саму программу. Ведь наш маленький файл может заинтересовать массу программистов, и они станут использовать его в своих проектах, в каждом из которых определена своя функция `main`.
|
||||||
|
|
||||||
|
[В начало ⮍](#5-1-написание-и-модульное-тестирование-простой-функции) [Наверх ⮍](#5-данные-и-функции-функциональный-стиль)
|
||||||
|
|
||||||
|
[^1]: Функция `find` ищет «иголку» (`needle`) в «стоге сена» (`haystack`). – *Прим. науч. ред.*
|
Loading…
Reference in New Issue