From 075f0711368859ca246f8fa4b74c59c4788422e3 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 25 Feb 2023 17:37:36 +0300 Subject: [PATCH] 5.1 --- .../README.md | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/05-данные-и-функции-функциональный-стиль/README.md b/05-данные-и-функции-функциональный-стиль/README.md index e69de29..aa3e3ac 100644 --- a/05-данные-и-функции-функциональный-стиль/README.md +++ b/05-данные-и-функции-функциональный-стиль/README.md @@ -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`). – *Прим. науч. ред.*