Перенос страниц

This commit is contained in:
Alexander Zhirov 2023-03-05 15:30:34 +03:00
parent 4d57446057
commit 4c954c9186
129 changed files with 14 additions and 15 deletions

View file

@ -0,0 +1,73 @@
# Введение
- [Кому адресована эта книга](#кому-адресована-эта-книга)
- [Структура книги](#структура-книги)
- [Краткая история](#краткая-история)
- [Благодарности](#благодарности)
Обретая силу в простоте, язык программирования порождает красоту.
Поиск компромисса при противоречивых требованиях сложная зада
ча, для решения которой создателю языка требуется не только знание теоретических принципов и практической стороны дела, но и хороший вкус. Проектирование языка программирования это последняя ступень мастерства в разработке программ.
D это язык, который последовательно старается правильно действовать в пределах выбранных им ограничений, таких как доступ систем
ного уровня к вычислительным ресурсам, высокая производительность и синтаксическая простота, к которой стремятся все произошедшие от C языки. Стараясь правильно действовать, D порой поступает традиционно как другие языки, а порой ломает традиции с помощью свежего, инновационного решения. Иногда это приводило к пересмотру принципов, которым, казалось, D никогда не изменит. Например, большие фрагменты программного кода, а то и целые программы, могут быть написаны с помощью хорошо определенного, не допускающего ошибок памяти «безопасного подмножества» D. Ценой небольшого ограничения доступа на системном уровне приобретается огромное преимущество при отладке программ. D заинтересует вас, если для вас важны следующие аспекты:
- *Производительность*. D это язык для системного программирования. Его модель памяти, несмотря на сильную типизацию, совмес
тима с моделью памяти C. Функции на D могут вызывать функции на C, а функции на C могут использовать функции D без каких-либо
промежуточных преобразований.
- *Выразительность*. D нельзя назвать небольшим, минималистичным языком, но его удельная мощность достаточно велика. Он по
зволяет определять наглядные, не требующие объяснений инструкции, точно моделирующие сложные реалии.
- *«Крутящий момент»*. Любой лихач-«самоделкин» скажет вам, что мощность еще не все было бы где ее применить. На одних языках
лучше всего пишутся маленькие программы. Синтаксические излишества других оправдываются только начиная с определенного объема программ. D одинаково эффективно помогает справляться и с короткими сценариями, и с большими программами, и для него отнюдь не редкость целый проект, органично вырастающий из простенького скрипта в единственном файле.
- *Параллельные вычисления*. Подход к параллельным вычислениям несомненное отличие D от похожих языков, отражающее разрыв ме
жду современными аппаратными решениями и архитектурой компьютеров прошлого. D покончил с проклятьем неявного разделения памяти (хотя и допускает статически проверенное, явно заданное разделение) и поощряет независимые потоки, которые «общаются» друг с другом посредством сообщений.
- *Обобщенное программирование*. Идея обобщенного кода, манипули рующего другим кодом, была впервые реализована в мощных макро
сах Лиспа, затем в шаблонах C++, обобщенных классах Java и схожих конструкциях других языков. D также предлагает невероятно мощные механизмы обобщенного и порождающего программирования.
- *Эклектизм*. D подразумевает, что каждая парадигма программирования ориентирована на свою задачу разработки. Поэтому он предполагает высокоинтегрированный объединенный стиль программирования, а не Единственно Верный Подход.
- *«Это мои принципы. А если они вам не нравятся, то у меня есть и другие»*[^1]. D старается всегда следовать своим принципам устройства языка. Иногда они идут вразрез с соображениями сложности реализации и трудностей использования и, главное, с человеческой природой, которая не всегда находит скрытую логику здравой и интуитивно понятной. В таких случаях все языки полагаются на собственное бесконечно субъективное понимание баланса, гибкости и особенно хорошего вкуса. На мой взгляд, D как минимум неплохо смотрится на фоне других языков, разработчикам которых приходилось принимать решения того же плана.
## Кому адресована эта книга
Предполагается, что вы программист. То есть знаете, как решить типичную задачу программирования с помощью языка, на котором вы пишете. Неважно, какой конкретно это язык. Если вы знаете один из языков, произошедших от Алгола (C, C++, Java или C#), то будете иметь некоторое преимущество перед другими читателями синтаксис сразу покажется знакомым, а риск встретить «мнимых друзей» (одинаковый синтаксис с разной семантикой) будет минимальным. (Особенно это касается случаев, когда вы вставляете кусок кода на C в D-файл. Он либо скомпилируется и будет делать то же самое, либо не скомпилируется вообще.)
Книга, знакомящая с языком, была бы скучной и неполной, если бы не объясняла, зачем в язык включены те или иные средства, и не показывала наиболее рациональные пути использования этих средств для решения конкретных задач. Эта книга логично обосновывает добавление в язык всех неочевидных средств и старается показать, почему не были выбраны лучшие, на первый взгляд, проектные решения. Некоторые альтернативы требуют необоснованно высоких затрат на реализацию, плохо взаимодействуют с другими средствами языка, имеющими больше прав на существование, обладают скрытыми недостатками, которые не видны в коротких и простых примерах, или просто недостаточно мощны для того, чтобы что-то значить. Важнее всего то, что разработчики языка могут совершать ошибки так же, как и все остальные люди, поэтому, пожалуй, лучшие проектные решения те, которых никто никогда не видел.
## Структура книги
Глава 1 это бодрящая прогулка с целью знакомства с основами языка. На этом этапе не все детали полностью видны, но вы сможете почувствовать язык и научиться писать на нем простейшие программы. Главы 2 и 3 необходимое перечисление выражений и инструкций языка соответственно. Я попытался скрасить неизбежную монотонность подробного описания, подчеркнув детали, отличающие D от других традиционных языков. Надеюсь, вам будет легко читать эти главы подряд, а также возвращаться к ним за справкой. Таблицы в конце этих глав это «шпаргалки», интуитивно понятные краткие справочники.
В главе 4 описаны встроенные типы: массивы, ассоциативные массивы и строки. Массив можно представить себе как указатель с аварийным выключателем. Массивы в D это средство, обеспечивающее безопасность памяти и позволяющее вам наслаждаться языком. Строки это массивы знаков Юникода в кодировке UTF. Повсеместная поддержка Юникода в языке и стандартной библиотеке позволяет корректно и эффективно обрабатывать строки.
Прочитав первые четыре главы, вы сможете на основе предоставляемых языком абстракций писать простые программы вроде сценариев. Последующие главы знакомят с абстракциями-блоками. Глава 5 объединяет описание различных видов функций: параметризированных функций режима компиляции (шаблоны функций) и функций, вычисляемых во время компиляции. Обычно такие вопросы рассматриваются в более «продвинутых» главах, но в D работать с этими средствами достаточно просто, так что раннее знакомство с ними оправданно.
В главе 6 обсуждается объектно-ориентированное программирование на основе классов. Как и раньше, здесь органично и комплексно подается информация о параметризированных классах. Глава 7 знакомит с дополнительными типами, в частности с типом `struct`, позволяющим, обычно совместно с классами, эффективно создавать абстракции.
Следующие четыре главы описывают довольно специализированные, обособленные средства. Глава 8 посвящена квалификаторам типов. Квалификаторы надежно гарантируют от ошибок, что одинаково ценно как для однопоточных, так и для многопоточных приложений. В главе 9 рассмотрены модели обработки исключительных ситуаций. В главе 10 представлен мощный инструментарий D, реализующий парадигму контрактного программирования. Этот материал намеренно вынесен в отдельную главу (а не включен в главу 9) в попытке развеять миф о том, что обработка ошибок и контрактное программирование практически одно и то же. В главе 10 как раз и объясняется, почему это не так.
В главе 11 вы найдете информацию и рекомендации по построению больших программ из компонентов, а также небольшой обзор стандартной библиотеки D. В главе 12 рассмотрены вопросы перегрузки операторов, без которой серьезно пострадали бы многие абстракции, например комплексные числа. Наконец, в главе 13 освещен оригинальный подход D к многопоточному программированию.
## Краткая история
Как бы сентиментально это ни звучало, D дитя любви. Когда-то в 1990-х Уолтер Брайт, автор компиляторов для C и C++, решил, что больше не хочет работать над ними, и задался целью определить язык, каким, по его мнению, «он должен быть». Многие из нас в тот или иной момент начинают мечтать об определении Правильного Языка; к счастью, Уолтер уже обладал значительной частью инфраструктуры: генератором кода (back-end), компоновщиком, а главное широчайшим опытом построения языковых процессоров. Благодаря этому опыту перед Уолтером открылась интересная перспектива. По какому-то таинственному закону природы плохо спроектированная функциональность языка проявляется в логически запутанной реализации компилятора, как отвратительный характер Дориана Грея проявлялся на его портрете. Проектируя свой новый язык, Уолтер планомерно старался избежать таких патологий.
Едва зарождающийся тогда язык был схож по духу с C++, поэтому программисты называли его просто D, несмотря на первоначальную попытку Уолтера даровать ему титул «Марса». По причинам, которые вскоре станут очевидными, назовем этот язык D1. Страсть и упорство, с которыми Уолтер работал над D1 несколько лет, привлекали все больше единомышленников. К 2006 году D1 достиг уровня сильного языка, технически способного на равных соперничать с такими уже признанными языками, как Java и C++. Но к тому времени уже было ясно, что D1 никогда не станет популярным, поскольку, в отличие от других языков, он не обладал функциональной индивидуальностью, оправдывавшей его существование. И тогда Уолтер совершил дерзкий маневр: решив представить D1 в качестве этакой первой сырой версии, он перевел его в режим поддержки и приступил к разработке нового проекта второй итерации языка, не обязанной поддерживать обратную совместимость. Пользователи текущей версии D1 по-прежнему выигрывали от исправления ошибок, но никаких новых возможностей D1 не предоставлял. Реализовать определение наилучшего языка было суждено языку D2, который я и называю просто D.
Маневр удался. Первая итерация показала, что достойно внимания, а чего следует избегать. Кроме того, можно было не спешить с рекламой нового языка новые члены сообщества могли спокойно работать со стабильной, активно используемой версией D1. Поскольку процесс разработки не был ограничен ни обратной совместимостью, ни сроками, можно было спокойно оценить альтернативы развития проекта и выработать правильное направление. Чтобы еще больше облегчить разработку, Уолтер призвал на помощь коллег, в том числе Бартоша Милевски и меня. Важные решения, касающиеся взглядов D на неизменяемость, обобщенное и функциональное программирование, параллельные вычисления, безопасность и многое другое, мы принимали в долгих оживленных дискуссиях на троих в одной из кофеен Киркленда (штат Вашингтон).
Тем временем D явно перерос свое прозвище «улучшенный C++», превратившись в мощный многофункциональный язык, вполне способный оставить без работы как языки для системного и прикладного (промышленного) программирования, так и языки сценариев. Оставалась одна проблема: весь этот рост и все усовершенствование прошли никем не замеченными; подходы D к программированию были документированы очень слабо.
Книга, которая сейчас перед вами, попытка восполнить это упущение. Надеюсь, читать ее вам будет так же приятно, как мне писать.
## Благодарности
У языка D было столько разработчиков, что я и не пытаюсь перечислить их всех. Особо выделяются участники новостной группы `digitalmars.D` из сети Usenet. Эта группа была для нас одновременно и рупором, и полигоном для испытаний, куда мы выносили на суд свои проектные решения. Кроме того, ребята из `digitalmars.D` сгенерировали множество идей по улучшению языка.
В разработке эталонной реализации компилятора `dmd`[^2] Уолтеру помогало сообщество, особенно Шон Келли (Sean Kelly) и Дон Клагстон (Don Clugston). Шон переписал и усовершенствовал стандартную библиотеку, подключаемую во время исполнения (включая «сборщик мусора»). Кроме того, Келли стал автором основной части реализации библиотеки, отвечающей за параллельные вычисления. Он мастер своего дела, а значит, если в ваших параллельных вычислениях появляются ошибки, то они, увы, скорее всего, ваши, а не его. Дон эксперт в математике вообще и во всех аспектах дробных вычислений в частности. Его огромный труд позволил поднять численные примитивы D на небывалую высоту. Кроме того, Дон до предела использовал способности D по генерированию кода. Как только код эталонной реализации был открыт для широкого доступа, Дон не устоял перед соблазном добавить в него что-то свое. Вот так он и занял второе место среди разработчиков компилятора `dmd`. И Шон, и Дон проявляли инициативу, выдвигая предложения по усовершенствованию спецификации D на протяжении всего процесса разработки. Последнее (но не по значению) их достоинство в том, что они чумовые хакеры. С ними очень приятно общаться как в жизни, так и виртуально. Не знаю, чем стал бы язык без них.
Что касается этой книги, я бы хотел сердечно поблагодарить всех рецензентов за отзывчивость, с которой они взялись за эту сложную и неблагодарную работу. Без них эта книга не стала бы тем, что она представляет собой сейчас (так что если она вам не нравится, пусть вас утешит то, что она могла быть гораздо хуже). Поэтому позвольте мне выразить благодарность Алехандро Арагону (Alejandro Aragón), Биллу Бакстеру (Bill Baxter), Кевину Билеру (Kevin Bealer), Тревису Бочеру (Travis Boucher), Майку Касинджино (Mike Casinghino), Альваро Кастро Кастилья (Àlvaro Castro Castilla), Ричарду Чангу (Richard Chang), Дону Клагстону, Стефану Дилли (Stephan Dilly), Кариму Филали (Karim Filali), Мишелю Фортину (Michel Fortin), Дэвиду Хелду (David B. Held), Мишелю Хелвенштейну (Michiel Helvensteijn), Бернарду Хельеру (Bernard Helyer), Джейсону Хаузу (Jason House), Сэму Ху (Sam Hu), Томасу Хьюму (Thomas Hume), Грэму Джеку (Graham St. Jack), Роберту Жаку (Robert Jacques), Кристиану Кэмму (Christian Kamm), Дэниелу Кипу (Daniel Keep), Марку Кегелу (Mark Kegel), Шону Келли, Максу Хесину (Max Khesin), Симену Хьеросу (Simen Kjærås), Коди Кёнингеру (Cody Koeninger), Денису Короскину (Denis Koroskin), Ларсу Кюллингстаду (Lars Kyllingstad), Игорю Лесику (Igor Lesik), Евгению Летучему (Eugene Letuchy), Пелле Манссону (Pelle Månsson), Миуре Масахиро (Miura Masahiro), Тиму Мэтьюсу (Tim Matthews), Скотту Мейерсу, Бартошу Милевски, Фавзи Мохамеду (Fawzi Mohamed), Эллери Ньюкамеру (Ellery Newcomer), Эрику Ниблеру (Eric Niebler), Майку Паркеру (Mike Parker), Дереку Парнеллу (Derek Parnell), Джереми Пеллетье (Jeremie Pelletier), Пабло Риполлесу (Pablo Ripolles), Брэду Робертсу (Brad Roberts), Майклу Ринну (Michael Rynn), Фою Сава (Foy Savas), Кристофу Шардту (Christof Schardt), Стиву Швайхофферу (Steve Schveighoffer), Бенджамину Шропширу (Benjamin Shropshire), Дэвиду Симше (David Simcha), Томашу Стаховяку (Tomasz Stachowiak), Роберту Стюарту (Robert Stewart), Кнуту Эрику Тайгену (Knut Erik Teigen), Кристиану Влащану (Cristian Vlăsceanu) и Леору Золману (Leor Zolman).
*Андрей Александреску*<br>
Воскресенье 2 мая 2010 г.
[^1]: Афоризм американского комика Граучо Маркса. *Прим. ред.*
[^2]: Название компилятора языка D `dmd` расшифровывается как Digital Mars D. Digital Mars организация, которая занимается разработкой этого компилятора. *Прим. пер.*

View file

@ -0,0 +1,939 @@
# 1. Знакомство с языком D
- [1.1. Числа и выражения](#1-1-числа-и-выражения)
- [1.2. Инструкции](#1-2-инструкции)
- [1.3. Основы работы с функциями](#1-3-основы-работы-с-функциями)
- [1.4. Массивы и ассоциативные массивы](#1-4-массивы-и-ассоциативные-массивы)
- [1.4.1. Работа со словарем](#1-4-1-работаем-со-словарем)
- [1.4.2. Получение среза массива. Функции с обобщенными типами параметров. Тесты модулей](#1-4-2-получение-среза-массива-функции-с-обобщенными-типами-параметров-тесты-модулей)
- [1.4.3. Подсчет частот. Лямбда-функции](#1-4-3-подсчет-частот-лямбда-функции)
- [1.5. Основные структуры данных](#1-5-основные-структуры-данных)
- [1.6. Интерфейсы и классы](#1-6-интерфейсы-и-классы)
- [1.6.1. Больше статистики. Наследование](#1-6-1-больше-статистики-наследование)
- [1.7. Значения против ссылок](#1-7-значения-против-ссылок)
- [1.8. Итоги](#1-8-итоги)
Вы ведь знаете, с чего обычно начинают, так что без лишних слов:
```d
import std.stdio;
void main()
{
writeln("Hello, world!");
}
```
В зависимости от того, какие еще языки вы знаете, у вас может возникнуть ощущение дежавю, чувство легкой благодарности за простоту, а может, и легкого разочарования из-за того, что D не пошел по стопам скриптовых языков, разрешающих использовать «корневые» (top-level) инструкции. (Такие инструкции побуждают вводить глобальные переменные, которые по мере роста программы превращаются в головную боль; на самом деле, D позволяет исполнять код не только внутри, но и вне функции `main`, хотя и более организованно.) Самые въедливые будут рады узнать, что `void main` это эквивалент функции `int main`, возвращающей операционной системе «успех» (код 0) при успешном окончании ее выполнения.
Но не будем забегать вперед. Традиционная программа типа «Hello, world!» («Здравствуй, мир!») вовсе не повод для обсуждения возможностей языка. Она здесь для того, чтобы помочь вам начать писать и запускать программы на этом языке. Если у вас нет никакой IDE, которая выполнит за вас сборку программы, то самый простой способ это командная строка. Напечатав приведенный код и сохранив его в файле с именем, скажем, `hello.d`, запустите консоль и введите следующие команды:
```sh
$ dmd hello.d
$ ./hello
Hello, world!
$ _
```
Знаком `$` обозначено приглашение консоли вашей ОС (это может быть `c:\Путь\К\Папке>` в Windows или `/путь/к/каталогу%` в системах семейства UNIX, таких как OSX, Linux, Cygwin). Применив пару известных вам приемов систем-фу, вы сможете добиться автоматической компиляции программы при ее запуске. Пользователи Windows, вероятно, захотят привязать программу `rdmd.exe` (которая устанавливается вместе с компилятором D) к команде Выполнить. UNIX-подобные системы поддерживают запуск скриптов в нотации «shebang»[^1]. D понимает такой синтаксис: добавление строки
```sh
#!/usr/bin/rdmd
```
в самое начало программы в файле `hello.d` позволяет компилировать ее автоматически перед исполнением. Внеся это изменение, просто введите в командной строке:
```sh
$ chmod u+x hello.d
$ ./hello.d
Hello, world!
$ _
```
(`chmod` нужно ввести только один раз).
Для всех операционных систем справедливо следующее: программа `rdmd` достаточно «умна», для того чтобы кэшировать сгенерированное приложение. Так что фактически компиляция выполняется только после изменения исходного кода программы, а не при каждом запуске. Эта особенность в сочетании с высокой скоростью самого компилятора позволяет экономить время на запусках программы между внесением в нее изменений, что одинаково полезно как при разработке больших систем, так и при написании маленьких скриптов.
Программа `hello.d` начинается с инструкции
```d
import std.stdio;
```
которая предписывает компилятору найти модуль с именем `std.stdio` и сделать его символы доступными для использования. Инструкция `import` напоминает препроцессорную директиву `#include`, которую можно встретить в синтаксисе C и С++, но семантически она ближе команде `import` языка Python: никакой вставки текста подключаемого модуля в текст основной программы не происходит выполняется только простое расширение таблицы символов. Если повторно применить инструкцию `import` к тому же файлу, ничего не произойдет.
По давней традиции C программа на D представляет собой набор определений, рассредоточенный по множеству файлов. В числе прочего эти определения могут обозначать типы, функции, данные. В нашей первой программе определена функция `main`. Она не принимает никаких аргументов и ничего не возвращает, что, по сути, и означает слово void. При выполнении `main` программа вызывает функцию `writeln` (разумеется, предусмотрительно определенную в модуле `std.stdio`), передавая ей строковую константу в качестве аргумента. Суффикс `ln` указывает на то, что `writeln` добавляет к выводимому тексту знак перевода строки.
Следующие разделы это стремительная поездка по Дибургу. Небольшие показательные программы дают общее представление о языке. Основная цель повествования на данном этапе обрисовать общую картину, а не дать ряд педантичных определений. Позже все аспекты языка будут рассмотрены с должным вниманием в деталях.
[Исходный код](src/chapter-1/)
[В начало ⮍](#1-знакомство-с-языком-d)
## 1.1. Числа и выражения
Интересовались ли вы когда-нибудь ростом иностранцев? Давайте напишем простую программу, которая переводит наиболее распространенные значения роста в футах и дюймах в сантиметры.
```d
/*
Рассчитать значения роста в сантиметрах для заданного диапазона значений в футах и дюймах
*/
import std.stdio;
void main()
{
// Значения, которые никогда не изменятся
immutable inchesPerFoot = 12;
immutable cmPerInch = 2.54;
// Перебираем и пишем
foreach (feet; 5 .. 7)
{
foreach (inches; 0 .. inchesPerFoot)
{
writeln(feet, "'", inches, "''\t", (feet * inchesPerFoot + inches) * cmPerInch);
}
}
}
```
В результате выполнения программы будет напечатан аккуратный список в две колонки:
```sh
5'0'' 152.4
5'1'' 154.94
5'2'' 157.48
...
6'10'' 208.28
6'11'' 210.82
```
Инструкция `foreach (feet; 5..7) {...}` это цикл, где определена целочисленная переменная `feet`, с которой последовательно связываются значения 5 и 6 (значение 7 она не принимает, так как интервал открыт справа). Как и Java, C++ и C#, D поддерживает `/* многострочные комментарии */` и `// однострочные комментарии` (и, кроме того, документирующие комментарии, о которых позже). Еще одна интересная деталь нашей маленькой программы способ объявления данных. Во-первых, введены две константы:
```d
immutable inchesPerFoot = 12;
immutable cmPerInch = 2.54;
```
Константы, значения которых никогда не изменятся, определяются с помощью ключевого слова `immutable`. Как и переменные, константы не требуют явного задания типа: тип задается значением, которым инициализируется константа или переменная. В данном случае литерал 12 говорит компилятору о том, что `inchesPerFoot` это целочисленная константа (обозначается в D с помощью знакомого `int`); точно так же литерал `2.54` заставляет `cmPerInch` стать константой с плавающей запятой (типа `double`). Далее мы обнаруживаем те же магические способности у определений `feet` и `inches`: они выглядят как «обычные» переменные, но безо всяких «украшений», свидетельствующих о каком-либо типе. Это не делает программу менее безопасной по сравнению с той, где типы переменных и констант заданы явно:
```d
immutable int inchesPerFoot = 12;
immutable double cmPerInch = 2.54;
...
foreach (int feet; 5 .. 7)
{
...
}
```
и так далее только меньше лишнего. Компилятор разрешает не указывать тип явно только в случае, когда можно недвусмысленно определить его по контексту. Раз уж зашла речь о типах, давайте остановимся и посмотрим, какие числовые типы нам доступны.
Целые типы со знаком в порядке возрастания размера: `byte`, `short`, `int` и `long`, занимающие 8, 16, 32 и 64 бита соответственно. У каждого из этих типов есть «двойник» без знака того же размера, названный в соответствии с простым правилом: `ubyte`, `ushort`, `uint` и `ulong`. (Здесь нет модификатора `unsigned`, как в C). Типы с плавающей запятой: `float` (32-битное число одинарной точности в формате IEEE 754), `double` (64-битное в формате IEEE 754) и `real` (занимает столько, сколько позволяют регистры, предназначенные для хранения чисел с плавающей запятой, но не меньше 64 бит; например, на компьютерах фирмы Intel `real` это так называемое расширенное 79-битное число двойной точности в формате IEEE 754).
Вернемся к нашим целым числам. Литералы, такие как `42`, подходят под определение любого числового типа, но заметим, что компилятор проверяет, достаточно ли вместителен «целевой» тип для этого значения. Поэтому определение
```d
immutable byte inchesPerFoot = 12;
```
ничем не хуже аналогичного без `byte`, поскольку `12` можно с таким же успехом представить 8 битами, а не 32. По умолчанию, если вывод о «целевом» типе делается по числу (как в программе-примере), целочисленные константы «воспринимаются» как `int`, а дробные как `double`.
Вы можете построить множество выражений на D, используя эти типы, арифметические операторы и функции. Операторы и их приоритеты сходны с теми, что можно найти в языках-собратьях D: `+`, `-`, `*`, `/` и `%` для базовых арифметических операций, `==`, `!=`, `<`, `>`, `<=`, `>=` для сравнений, `fun(argument1, argument2)` для вызовов функций и т.д.
Вернемся к нашей программе перевода дюймов в сантиметры и отметим две достойные внимания детали вызова функции `writeln`. Первая: во `writeln` передаются 5 аргументов (а не один, как в той программе, что установила контакт между вами и миром D). Функция `writeln` очень похожа на средства ввода-вывода, встречающиеся в языках Паскаль (`writeln`), C (`printf`) и C++ (`cout`). Все они (включая `writeln` из D) принимают переменное число аргументов (так называемые функции с переменным числом аргументов). Однако в D пользователи могут определять собственные функции с переменным числом аргументов (чего нет в Паскале), которые всегда типизированы (в отличие от C), без излишнего переопределения операторов (как это сделано в С++). Вторая деталь: наш вызов `writeln` неуклюже сваливает в кучу информацию о форматировании и форматируемые данные. Обычно желательно отделять данные от представления. Поэтому давайте используем специальную функцию `writefln`, осуществляющую форматированный вывод:
```d
writefln("%s'%s''\t%s", feet, inches, (feet * inchesPerFoot + inches) * cmPerInch);
```
По-новому организованный вызов дает тот же вывод, но первый аргумент функции `writefln` полностью описывает формат представления. Со знака `%` начинаются спецификаторы формата (по аналогии с функцией `printf` из C): например `%d` для целых чисел, `%f` для чисел с плавающей запятой и `%s` для строк.
Если вы использовали `printf` прежде, то могли бы почувствовать себя как дома, когда б не маленькая особенность: мы ведь выводим значения переменных типа `int` и `double` как же получилось, что и те и другие описаны с помощью спецификатора `%s`, обычно применяемого для вывода строк? Ответ прост. Средства D для работы с переменным количеством аргументов дают `writefln` доступ к информации об исходных типах переданных аргументов. Благодаря такому подходу программа получает ряд преимуществ: 1) значение `%s` может быть расширено до «строкового представления по умолчанию для типа переданного аргумента» и 2) если не удалось сопоставить спецификатор формата с типами переданных аргументов, вы получите ошибку в чистом виде, а не загадочное поведение, присущее вызовам `printf` с неверно заданным форматом (не говоря уже о подрыве безопасности, возможном при вызове `printf` с непроверяемыми заранее форматирующими строками).
[Исходный код](src/chapter-1-1/)
[В начало ⮍](#1-1-числа-и-выражения) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.2. Инструкции
В языке D, как и в других родственных ему языках, любое выражение, после которого стоит точка с запятой, это инструкция (например в программе «Hello, world!» сразу после вызова `writeln` есть ;). Действие инструкции сводится к вычислению выражения.
D член семейства с фигурными скобками и с блочной областью видимости». Это означает, что вы можете объединять несколько команд в одну, помещая их в `{` и `}`, что порой обязательно, например при желании сделать сразу несколько вещей в цикле `foreach`. В случае единственной команды вы вправе смело опустить фигурные скобки. На самом деле, весь наш двойной цикл, вычисляющий значения роста, можно переписать так:
```d
import std.stdio;
void main()
{
// Значения, которые никогда не изменятся
immutable inchesPerFoot = 12;
immutable cmPerInch = 2.54;
// Перебираем и пишем
foreach (feet; 5 .. 7)
foreach (inches; 0 .. inchesPerFoot)
writeln(feet, "'", inches, "''\t", (feet * inchesPerFoot + inches) * cmPerInch);
}
```
У пропуска фигурных скобок для одиночных инструкций есть как преимущество (более короткий код), так и недостаток редактирование кода становится более утомительным (в процессе отладки придется повозиться с инструкциями, то добавляя, то удаляя скобки). Когда речь заходит о правилах расстановки отступов и фигурных скобок, мнения сильно расходятся. На самом деле, пока вы последовательны в своем выборе, все это не так важно, как может показаться. В качестве доказательства: стиль, предлагаемый в этой книге (обязательное заключение в операторные скобки даже одиночных инструкций, открывающая скобка на одной строке с соответствующим оператором, закрывающие скобки на отдельных строках), по типографским причинам отличается от реально применяемого автором. А раз он мог спокойно это пережить, не превратившись в оборотня, то и любой сможет.
Благодаря языку Python стал популярен иной способ отражения блочной структуры программы с помощью отступов (чудесное воплощение принципа «форма соответствует содержанию»). Для программистов на других языках утверждение, что пробел имеет значение, всего лишь нелепая фраза, но для тех, кто пишет на Python, это зарок. D обычно игнорирует пробелы, но он разработан с прицелом на легкость синтаксического разбора (т. е. чтобы при разборе не приходилось выяснять значения символов). А это подразумевает, что в рамках скромного «комнатного» проекта можно реализовать простой препроцессор, позволяющий использовать для выделения блоков инструкций отступы (как в Python) без каких-либо неудобств во время компиляции, исполнения и отладки программ.
Кроме того, вам должна быть хорошо знакома инструкция if:
```d
if (‹выражение›) инструкция1 else инструкция2
```
Чисто теоретический вывод, известный как принцип структурного программирования, гласит, что все алгоритмы можно реализовать с помощью составных инструкций, `if`-проверок и циклов а-ля `for` и `foreach`. Разумеется, любой адекватный язык (как и D) предлагает гораздо больше, но мы пока постановим, что с нас довольно и этих инструкций, и двинемся дальше.
[Исходный код](src/chapter-1-2/)
[В начало ⮍](#1-2-инструкции) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.3. Основы работы с функциями
Оставим пока в стороне обязательное определение функции `main` и посмотрим, как определяются другие функции на D. Определение функции соответствует модели, характерной и для других Алгол-подобных языков: сначала пишется возвращаемый тип, потом имя функции и, наконец, заключенный в круглые скобки список формальных аргументов, разделенных запятыми. Например, определение функции с именем `pow`, которая принимает значения типа `double` и `int`, а возвращает `double`, записывается так:
```d
double pow(double base, int exponent)
{
...
}
```
Каждый параметр функции (`base` и `exponent` в данном примере) кроме типа может иметь необязательный *класс памяти* (*storage class*), определяющий способ передачи аргумента в функцию при ее вызове[^2].
По умолчанию аргументы передаются в `pow` по значению. Если перед типом параметра указан класс памяти `ref`, то параметр привязывается напрямую к входному аргументу, так что изменение параметра непосредственно отражается на значении, полученном извне. Например:
```d
import std.stdio;
void fun(ref uint x, double y)
{
x = 42;
y = 3.14;
}
void main()
{
uint a = 1;
double b = 2;
fun(a, b);
writeln(a, " ", b);
}
```
Эта программа печатает `42 2`, потому что `x` определен как `ref uint`, то есть когда значение присваивается x, на самом деле операция проводится с `a`. С другой стороны, присваивание значения переменной `y` никак не скажется на `b`, поскольку `y` это внутренняя копия в распоряжении функции `fun`.
Последние «украшения», которые мы обсудим в этом кратком введении, это `in` и `out`. Попросту говоря, `in` данное функцией «обещание» только смотреть на параметр, не «трогая» его. Указание `out` в определении параметра функции действует сходно с `ref`, с той поправкой, что параметр принудительно инициализируется своим значением по умолчанию при «входе» в функцию. (Для каждого типа `T` определено начальное значение, обозначаемое как `T.init`. Пользовательские типы могут определять собственное значение по умолчанию.)
О функциях можно еще долго рассказывать. Можно передавать функции другим функциям, встраивать одну в другую, разрешать функции сохранять свою локальную среду (полнофункциональная синтаксическая клауза), создавать анонимные функции (лямбда-функции), с удобством манипулировать ими и еще множество дополнительных «вкусностей». Со временем мы доберемся до каждой из них.
[Исходный код](src/chapter-1-3/)
[В начало ⮍](#1-3-основы-работы-с-функциями) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.4. Массивы и ассоциативные массивы
Массивы и ассоциативные массивы (которые обычно называют хеш-таблицами, или хешами) пожалуй, наиболее часто используемые сложные структуры данных за всю историю машинных вычислений, завистливо преследуемые списками языка Лисп. Множество полезных программ не требуют ничего, кроме массива или ассоциативного массива. Так что пришло время посмотреть, как D их реализует.
[В начало ⮍](#1-4-массивы-и-ассоциативные-массивы) [Наверх ⮍](#1-знакомство-с-языком-d)
### 1.4.1. Работаем со словарем
Для примера напишем простенькую программку, следуя такой спецификации:
> Читать текст, состоящий из слов, разделенных пробелами, и сопоставлять каждому не встречавшемуся до сих пор при чтении слову уникальное число. Вывод организовать в виде строк формата:
```sh
идентификатор слово
```
Такой маленький скрипт вполне может пригодиться, когда вы захотите обработать какой-нибудь текст. Построив словарь, вы получите возможность манипулировать только числами (что дешевле), а не полновесными словами. Один из вариантов построения такого словаря накапливать уже прочитанные слова в ассоциативном массиве, отображающем слова на целые числа. При добавлении нового соответствия достаточно убедиться, что число, связываемое со словом, уникально («железная» гарантия просто использовать текущую длину массива, в результате чего получится последовательность идентификаторов 0, 1, 2, ...). Посмотрим, как это можно реализовать на D.
```d
import std.stdio, std.string;
import std.algorithm;
void main()
{
size_t [string] dictionary;
foreach (line; stdin.byLine())
{
// Разбить строку на слова
// Добавить каждое слово строки в словарь
foreach (word; line.strip.splitter)
{
if (word in dictionary) continue; // Ничего не делать
auto newID = dictionary.length;
dictionary[word.idup] = newID;
writeln(newID, '\t', word);
}
}
}
```
В языке D ассоциативный массив (хеш-таблица), который значениям типа `K` ставит в соответствие значения типа `V`, обозначается как `V[K]`. Итак, переменная `dictionary` типа `size_t[string]` сопоставляет строкам целые числа без знака как раз то, что нам нужно для хранения соответствий слов идентификаторам. Выражение `word in dictionary` истинно, если ключевое слово `word` можно найти в ассоциативном массиве `dictionary`. Наконец, вставка в словарь выполняется так: `dictionary[word.idup] = newID`[^3].
Хотя в рассмотренном сценарии не отражается явно тот факт, что тип `string` на самом деле массив знаков, это так. В общем виде динамический массив элементов типа `T` обозначается как `T[]` и может определяться различными способами:
```d
int[] a = new int[20]; // 20 целых чисел, инициализированных нулями
int[] b = [ 1, 2, 3 ]; // Массив, содержащий 1, 2, и 3
```
В отличие от массивов C, массивы D «знают» собственную длину. Для любого массива `arr` это значение доступно как `arr.length`. Присваивание значения `arr.length` перераспределяет память, выделенную под массив. При попытке обращения к элементам массива проверяется, не выходит ли запрашиваемый индекс за границу массива. Любители рискнуть переполнением буфера могут «выдрать» указатель из массива (используя `arr.ptr`) и затем выполнять непроверенные арифметические операции над ним. Кроме того, если вам действительно нужно все, что может дать кремниевая пластина, есть опция компилятора, отменяющая проверку границ. Можно сказать, что к безопасности ведет путь наименьшего сопротивления. Код безопасен по умолчанию, а если поработать, можно сделать его чуть более быстрым.
Вот как можно проходить по массиву с помощью новой формы уже знакомой инструкции `foreach`:
```d
int[] arr = new int[20];
foreach (elem; arr)
{
/* ... использовать elem... */
}
```
Этот цикл по очереди связывает переменную `elem` с каждым элементом массива `arr`. Присваивание `elem` не влияет на элементы `arr`. Чтобы изменить массив таким способом, просто используйте ключевое слово `ref`:
```d
// Обнулить все элементы arr
foreach (ref elem; arr)
{
elem = 0;
}
```
Теперь, когда мы знаем, как `foreach` работает с массивами, рассмотрим еще один полезный прием. Если в теле цикла вам потребуется индекс элемента массива, `foreach` может рассчитать его для вас:
```d
int[] months = new int[12];
foreach (i, ref e; months)
{
e = i + 1;
}
```
Этот код заполняет массив числами от 1 до 12. Такой цикл эквивалентен чуть более многословному определению (см. ниже), использующему `foreach` для просмотра диапазона чисел:
```d
foreach (i; 0 .. months.length)
{
months[i] = i + 1;
}
```
D также предлагает массивы фиксированного размера, обозначаемые, например, как `int[5]`. За исключением отдельных специализированных приложений, предпочтительнее использовать динамические массивы, поскольку обычно размер массива вам заранее неизвестен.
Семантика копирования массивов неочевидна: копирование одной переменной типа массив в другую не копирует весь массив; эта операция порождает лишь новую ссылку на ту же область памяти. Если вам действительно хочется заполучить копию, просто используйте свойство массива `.dup`:
```d
int[] a = new int[100];
int[] b = a;
// ++x увеличивает на 1 значение x
++b[10]; // В b[10] теперь 1, в a[10] то же
b = a.dup; // Полностью скопировать a в b
++b[10]; // В b[10] теперь 2, а в a[10] остается 1
```
[Исходный код](src/chapter-1-4-1/)
[В начало ⮍](#1-4-1-работаем-со-словарем) [Наверх ⮍](#1-знакомство-с-языком-d)
### 1.4.2. Получение среза массива. Функции с обобщенными типами параметров. Тесты модулей
Получение среза массива это мощное средство, позволяющее ссылаться на часть массива, в действительности не копируя данные массива. В качестве иллюстрации напишем функцию двоичного поиска, реализующую одноименный алгоритм: получив упорядоченный массив и значение, двоичный поиск быстро возвращает логический результат, сообщающий, есть ли заданное значение в массиве. Функция из стандартной библиотеки D возвращает более информативный ответ, чем просто булево значение, но знакомство с ней придется отложить, так как для этого необходимо более глубокое знание языка. Позволим себе, однако, приподнять планку, задавшись целью написать функцию, которая будет работать не только с массивами целых чисел, но с массивами элементов любого типа, допускающего сравнение с помощью операции `<`. Оказывается, реализовать эту задумку можно без особого труда. Вот как выглядит функция обобщенного двоичного поиска `binarySearch`:
```d
import std.array;
bool binarySearch(T)(T[] input, T value)
{
while (!input.empty)
{
auto i = input.length / 2;
auto mid = input[i];
if (mid > value) input = input[0 .. i];
else if (mid < value) input = input[i + 1 .. $];
else return true;
}
return false;
}
unittest
{
assert(binarySearch([ 1, 3, 6, 7, 9, 15 ], 6));
assert(!binarySearch([ 1, 3, 6, 7, 9, 15 ], 5));
}
```
Знаки `(T)` в сигнатуре функции `binarySearch` обозначают *параметр типа* с именем `T`. `T` становится псевдонимом переданного типа в теле этой функции. Затем параметр типа можно использовать в обычном списке параметров функции. При вызове `binarySearch` компилятор определит значение `T` по фактическим аргументам. Если вы хотите указать `T` явно (например, для надежности), то можете написать:
```d
assert(binarySearch!(int)([ 1, 3, 6, 7, 9, 15 ], 6));
```
что обнаруживает возможность вызова обобщенной функции с двумя заключенными в круглые скобки последовательностями аргументов. Сначала следуют заданные во время компиляции аргументы, заключенные в `!(...)`, а за ними получаемые во время исполнения программы аргументы в `(...)`. Объединение этих двух «владений» в одно обдумывалось, но эксперименты показали, что такая унификация создает больше проблем, чем решает.
Если вы знакомы с аналогичными средствами Java, C# и C++, то, вероятно, вам сразу бросилось в глаза то, что D сделал шаг в сторону от этих языков, отказавшись применять угловые скобки `<` и `>` для обозначения аргументов, заданных во время компиляции. Это осознанное решение. Его цель избежать горького опыта C++ (возросшее количество трудностей при синтаксическом разборе, гекатомба в виде множества специальных правил и тай-брейков плюс ко всему неясный синтаксис, осложняющий жизнь пользователя своей двусмысленностью[^4]). Проблема в том, что знаки `<` и `>` являются операторами сравнения[^5]]. Это делает их использование в качестве разделителей очень двусмысленным, учитывая тот факт, что *внутри* этих разделителей разрешены выражения. Таким «кандидатам в разделители» очень сложно подыскать замену. Языкам Java и C# живется легче: они запрещают писать выражения внутри `<` и `>`. Однако этим они ограничивают свою расширяемость ради сомнительного преимущества. D разрешает использовать выражения в качестве аргументов, заданных во время компиляции. Было решено упростить жизнь как человеку, так и компьютеру, наделив дополнительным смыслом традиционный унарный оператор `!` (используемый в логических операциях) и задействовав классические круглые скобки (которые, уверен, вы всегда сможете верно сопоставить друг другу).
Другая любопытная деталь нашей реализации бинарного поиска употребление `auto` для запуска алгоритма, строящего предположения о типах по контексту программы: типы переменных `i` и `mid` определены из выражений, которыми они инициализируются.
В стремлении придерживаться хорошего тона при написании программ к `binarySearch` был добавлен тест модуля. Тесты модулей вводятся в виде блоков, озаглавленных ключевым словом `unittest` (файл может содержать сколько угодно конструкций `unittest`, поскольку, как известно, проверок много не бывает). Чтобы перед входом в функцию `main` запустить тест, передайте компилятору флаг `-unittest`. Хотя `unittest` кажется незначительной деталью, такая конструкция помогает соблюдать хороший стиль программирования: с ее помощью вставлять тесты так легко, что было бы странно не делать этого. Кроме того, если вы привыкли создавать программы «сверху вниз» и предпочитаете видеть сначала тест модуля, а реализацию потом, смело вставляйте `unittest` до `binarySearch`; в D семантика символов на уровне модуля никогда не зависит от их расположения относительно других символов на том же уровне.
Операция получения среза `input[a .. b]` возвращает срез массива `input` от `a` до `b`, исключая индекс `b`. Если `a == b`, будет возвращен пустой срез, а если `a > b`, генерируется исключительная ситуация. Операция получения среза не влечет за собой динамическое выделение памяти; это всего лишь создание новой ссылки на часть массива. Символ `$` внутри выражения индексации или получения среза обозначает длину массива, к которому осуществляется доступ; например, `input[0 .. $]` это то же самое, что и просто `input`.
Повторимся: несмотря на кажущееся обилие перемещений, производимых функцией `binarySearch`, память под новые массивы не была выделена ни разу. Предложенную реализацию алгоритма ни в коей мере нельзя назвать менее эффективной по сравнению с традиционной реализацией, которую характеризует постоянное вычисление индексов. Однако, без сомнения, новая реализация проще для понимания, поскольку она оперирует меньшим количеством состояний. В свете разговора о состояниях напишем рекурсивную реализацию `binarySearch`, которая вообще не переопределяет `input`:
```d
import std.array;
bool binarySearchR(T)(T[] input, T value)
{
if (input.empty) return false;
auto i = input.length / 2;
auto mid = input[i];
if (mid > value) return binarySearch(input[0 .. i], value);
if (mid < value) return binarySearch(input[i + 1 .. $], value);
return true;
}
```
Рекурсивная реализация явно проще и концентрированнее по сравнению со своим итеративным собратом. Кроме того, она ничуть не менее эффективна, так как рекурсивные вызовы оптимизируются благодаря популярной среди компиляторов технике, известной как *оптимизация хвостовой рекурсии*. В двух словах: если функция возвращает просто вызов самой себя (но с другими аргументами), компилятор модифицирует аргументы и инициирует переход к началу функции.
[Исходный код](src/chapter-1-4-2/)
[В начало ⮍](#1-4-2-получение-среза-массива-функции-с-обобщенными-типами-параметров-тесты-модулей) [Наверх ⮍](#1-знакомство-с-языком-d)
### 1.4.3. Подсчет частот. Лямбда-функции
Поставим себе задачу написать еще одну полезную программу, которая будет подсчитывать частоту употребления слов в заданном тексте. Хотите знать, какие слова употребляются в «Гамлете» чаще всего? Тогда вы как раз там, где надо.
Следующая программа использует ассоциативный массив, сопоставляющий строки переменным типа `uint`, и имеет структуру, напоминающую структуру программы построения словаря, рассмотренной в предыдущем примере. Чтобы сделать программу подсчета частот полностью полезной, в нее добавлен простой цикл печати:
```d
import std.algorithm, std.stdio, std.string;
void main()
{
// Рассчитать таблицу частот
uint[string] freqs;
foreach (line; stdin.byLine())
{
foreach (word; line.strip.splitter)
{
++freqs[word.idup];
}
}
// Напечатать таблицу частот
foreach (key, value; freqs)
{
writefln("%6u\t%s", value, key);
}
// Напечатать таблицу частот с сортировкой
string[] words = freqs.keys;
sort!((a, b) { return freqs[a] > freqs[b]; })(words);
foreach (word; words)
{
writefln("%6u\t%s", freqs[word], word);
}
}
```
А теперь, скачав из Сети файл `hamlet.txt`[^6] (который вы найдете по прямой [ссылке](src/hamlet.txt)) и запустив нашу маленькую программу с шекспировским шедевром в качестве аргумента, вы получите:
```sh
1 outface
1 come?
1 blanket,
1 operant
1 reckon
2 liest
1 Unhand
1 dear,
1 parley.
1 share.
...
```
И, к сожалению, обнаружите, что вывод неупорядочен: слова, которые напечатаны в первых строках, далеко не самые часто встречающиеся. Что неудивительно для ускорения реализации примитивов ассоциативных массивов элементы в них могут храниться в любом порядке.
Для того чтобы отсортировать вывод по убыванию частоты употребления слов, вы можете просто передать вывод программы утилите `sort` с флажком `-nr` (от numerically отсортировать по числам и reversed в обратном порядке), но это своего рода хитрость. Чтобы добавить сортировку непосредственно в нашу программу, заменим последний цикл следующим кодом:
```d
// Напечатать таблицу частот
string[] words = freqs.keys;
sort!((a, b) { return freqs[a] > freqs[b]; })(words);
foreach (word; words)
{
writefln("%6u\t%s", freqs[word], word);
}
```
Свойство `.keys` позволяет получить только ключи ассоциативного массива `freqs` в виде массива строк, под который выделяется новая область памяти. Этого не избежать, ведь нам требуется делать перестановки. Мы получили код
```d
sort!((a, b) { return freqs[a] > freqs[b]; })(words);
```
который соответствует недавно рассмотренной нотации:
```d
sort!(‹аргументы времени компиляции›)(‹аргументы времени исполнения›);
```
Взяв текст, заключенный в первые круглые скобки `!(...)`, мы получим форму записи, напоминающую незаконченную функцию как будто ее автор забыл о типах параметров, возвращаемом типе и о самом имени функции:
```d
(a, b) { return freqs[a] > freqs[b]; }
```
Это *лямбда-функция* небольшая анонимная функция, которая обычно создается для того, чтобы потом передавать ее другим функциям в качестве аргумента. Лямбда-функции используются постоянно и повсеместно, поэтому разработчики D сделали все возможное, чтобы избавить программиста от синтаксической нагрузки, прежде неизбежной при определении таких функций: типы параметров и возвращаемый тип выясняются из контекста. Это действительно имеет смысл, потому что тело лямбда-функции по определению там, где нужно автору, читателю и компилятору, то есть здесь нет места разночтениям и принципы модульности не нарушаются.
В связи с лямбда-функцией, определенной в этом примере, стоит упомянуть еще одну деталь. Лямбда-функция осуществляет доступ к переменной `freqs`, локальной переменной функции `main`, а значит, лямбда-функция не является ни глобальной, ни статической. Это больше напоминает подход Лиспа, а не C, и позволяет работать с очень мощными лямбда-конструкциями. И хотя обычно за такое преимущество приходится платить неявными вызовами функций во время исполнения программы, D гарантирует отсутствие таких вызовов (и, следовательно, ничем не ограниченные возможности реализации инлайнинга).
Вывод измененной программы:
```sh
929 the
680 and
625 of
608 to
523 I
453 a
444 my
382 in
361 you
358 Ham.
...
```
Что и ожидалось: самые часто употребляемые слова набрали больше всего «очков». Настораживает лишь «Ham»[^7]. Это слово в пьесе вовсе не отражает кулинарные предпочтения героев. «Ham» всего лишь сокращение от «Hamlet» (Гамлет), которым помечена каждая из его реплик. Явно у него был повод высказаться 358 раз больше, чем любой другой герой пьесы. Далее по списку следует король всего 116 реплик, меньше трети сказанного Гамлетом. А Офелия с ее 58 репликами просто молчунья.
[Исходный код](src/chapter-1-4-3/)
[В начало ⮍](#1-4-3-подсчет-частот-лямбда-функции) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.5. Основные структуры данных
Раз уж мы взялись за «Гамлета», проанализируем этот текст чуть глубже. Например, соберем кое-какую информацию о главных героях: сколько всего слов было произнесено каждым персонажем и насколько богат его (ее) словарный запас. Для этого с каждым действующим лицом понадобится связать несколько фактов. Чтобы сосредоточить эту информацию в одном месте, определим такую структуру данных:
```d
struct PersonaData
{
uint totalWordsSpoken;
uint[string] wordCount;
}
```
В языке D понятия структуры (`struct`) и классы (`class`) четко разделены. С точки зрения удобства они во многом схожи, но устанавливают разные правила: структуры это типы значений, а классы были задуманы для реализации динамического полиморфизма, поэтому экземпляры классов могут быть доступны исключительно по ссылке. Упразднены связанное с этим непонимание, ошибки при копировании экземпляров классов потомков в переменные классов-предков и комментарии а-ля `// Нет! НЕ наследуй!`. Разрабатывая тип, вы с самого начала должны решить, будет ли это мономорфный тип-значение или полиморфная ссылка. Общеизвестно, что C++ разрешает определять типы, принадлежность которых к тому или иному разряду неочевидна, но эти типы редко используются и чреваты ошибками. В целом достаточно оснований сознательно отказаться от них.
В нашем случае требуется просто собрать немного данных, и мы не планируем использовать полиморфные типы, поэтому тип `struct` хороший выбор. Теперь определим ассоциативный массив, отображающий имена персонажей на дополнительную информацию о них (значения типа `PersonaData`):
```d
PersonaData[string] info;
```
Все, что нам требуется, это правильно заполнить `info` данными из `hamlet.txt`. Придется немного потрудиться: реплика героя может простираться на несколько строк, и нам понадобится простая обработка, сцепляющая эти физические строки в одну логическую. Чтобы понять, как это сделать, обратимся к небольшому фрагменту файла `hamlet.txt`, познаково представленному ниже (предшествующие тексту пробелы для наглядности отображаются видимыми знаками):
```sh
˽˽Pol. Marry, I will teach you! Think yourself a baby
˽˽˽˽That you have ta'en these tenders for true pay,
˽˽˽˽Which are not sterling. Tender yourself more dearly,
˽˽˽˽Or (not to crack the wind of the poor phrase,
˽˽˽˽Running it thus) you'll tender me a fool.
˽˽Oph. My lord, he hath importun'd me with love
˽˽˽˽In honourable fashion.
˽˽Pol. Ay, fashion you may call it. Go to, go to!
```
До сих пор гадают, не было ли истинной причиной гибели Полония злоупотребление инструкцией `goto`. Но нас больше интересует другое: заметим, что реплике каждого персонажа предшествуют ровно два пробела, за ними следует имя, после которого стоит точка, потом пробел, за которым наконец-то начинается само высказывание. Если логическая строка занимает несколько физических строк, то каждая следующая строка всегда начинается с четырех пробелов. Можно осуществить простое сопоставление шаблону, воспользовавшись регулярными выражениями (для работы с которыми предназначен модуль `std.regex`), но мы хотим научиться работать с массивами, поэтому выполним сопоставление «вручную». Призовем на помощь лишь логическую функцию `a.startsWith(b)`, определенную в модуле `std.algorithm`, которая сообщает, начинается ли `a` с `b`.
Управляющая функция `main` читает входную последовательность физических строк, сцепляет их в логические строки (игнорируя все, что не подходит под наш шаблон), передает полученные полные реплики в функцию-накопитель и в конце печатает требуемую информацию:
```d
import std.algorithm, std.conv, std.regex, std.range, std.stdio, std.string, std.ascii;
struct PersonaData
{
uint totalWordsSpoken;
uint[string] wordCount;
}
void main()
{
// Накапливает информацию о главных героях
PersonaData[string] info;
// Заполнить info
string currentParagraph;
foreach (line; stdin.byLine())
{
// 4 символа отступа
if (line.startsWith(" ") && line.length > 4 && isAlpha(line[4]))
{
// Персонаж продолжает высказывание
currentParagraph ~= line[3 .. $];
}
// 2 символа отступа
else if (line.startsWith(" ") && line.length > 2 && isAlpha(line[2]))
{
// Персонаж только что начал говорить
addParagraph(currentParagraph, info);
currentParagraph = to!string(line[2 .. $]);
}
}
// Закончили, теперь напечатаем собранную информацию
printResults(info);
}
```
Зная, как работают массивы, мы без труда читаем этот код, за исключением конструкции `to!string(line[2 .. $])`. Зачем она нужна и что будет, если о ней забыть?
Цикл `foreach`, последовательно считывая из стандартного потока ввода строки текста, размещает их в переменной `line`. Поскольку не имеет смысла выделять память под новый буфер при чтении следующей строки, в каждой итерации `byLine` заново использует место, выделенное для `line`. Тип самой переменной `line char[]`, массив знаков.
Если вы всего лишь, считав, «обследуете» каждую строчку, а потом забываете о ней, в любом случае (как с `to!string(line[2 .. $])`, так и без нее) все будет работать гладко. Но если вы желаете создать код, который будет где-то накапливать содержание читаемых строк, лучше позаботиться о том, чтобы он их действительно копировал. Очевидно, было задумано реально хранить текст в переменной `currentParagraph`, а не использовать ее как временное пристанище, так что необходимо получать дубликаты; отсюда и присутствие конструкции `to!string`, которая преобразует любое выражение в строку. Переменные типа `string` неизменяемы, а `to` гарантирует приведение к этому типу созданием дубликата.
Если забыть написать `to!string` и впоследствии код все же скомпилируется, в результате получится бессмыслица, и ошибку будет довольно-таки сложно обнаружить. Очень неприятно отлаживать программу, одна часть которой изменяет данные, находящиеся в другой части программы, потому что это уже не локальные изменения (трудно представить, сколько вызовов `to` можно забыть при написании большой программы). К счастью, это не причина для беспокойства: типы переменных `line` и `currentParagraph` соответствуют роли этих переменных в программе. Переменная `line` имеет тип `char[]`, представляющий собой массив знаков, которые можно перезаписывать в любой момент; переменная `currentParagraph` имеет тип `string` массив знаков, которые нельзя изменять по отдельности. (Для самых любопытных: полное имя типа `string` `immutable(char)[]`, что дословно означает «непрерывный диапазон неизменяемых знаков». Мы вернемся к разговору о строках в главе 4.) Эти переменные не могут ссылаться на одну и ту же область памяти, поскольку `line` нарушает обязательство `currentParagraph` не изменять знаки по отдельности. Поэтому компилятор отказывает в компиляции ошибочного кода и требует копию, которую вы и предоставляете благодаря преобразованию в строку с помощью конструкции `to!string`. И все счастливы.
С другой стороны, если постоянно копировать строковые значения, то нет необходимости дублировать данные на нижнем уровне их представления переменные просто могут ссылаться на одну и ту же область памяти, которая наверняка не будет перезаписана. Это делает копирование переменных типа `string` безопасным и эффективным одновременно. Но это еще не все плюсы. Строки можно без проблем разделять между потоками, потому что данные типа `string` неизменяемы, так что возможность конфликта при обращении к памяти попросту отсутствует. Неизменяемость это действительно здорово. С другой стороны, если вам потребуется интенсивно изменять знаки по отдельности, возможно, вы предпочтете использовать тип `char[]`, хотя бы временно.
Структура `PersonData` в том виде, в каком она задана выше, очень проста. Однако в общем случае структуры могут определять не только данные, но и другие сущности, такие как частные (приватные, закрытые) разделы (обозначаются ключевым словом `private`), функции-члены, тесты модулей, операторы, конструкторы и деструкторы. По умолчанию любой элемент структуры инициализируется значением по умолчанию (ноль для целых чисел, NaN для чисел с плавающей запятой[^8] и `null` для массивов и других типов, доступ к которым не осуществляется напрямую. А теперь реализуем функцию `addParagraph`, которая разбивает строку текста на слова и распределяет их по ассоциативному массиву.
Строка, которую обрабатывает `main`, имеет вид: `"Ham. To be, or not to be, that is the question."`. Для того чтобы отделить имя персонажа от слов, которые он произносит, нам требуется найти первый разделитель `". "`. Для этого используем функцию `find`. Выражение `haystack.find(needle)` возвращает правую часть `haystack`, начинающуюся с первого вхождения `needle`. (Если `needle` в `haystack` отсутствует, то вызов `find` с такими аргументами вернет пустую строку.) Пока мы формируем словарь, не мешает немного прибраться. Во-первых, нужно преобразовать фразу к нижнему регистру, чтобы слово с заглавной и со строчной буквы воспринималось как одна и та же словарная единица. Об этом легко позаботиться с помощью вызова функции `tolower`. Второе, что необходимо сделать, удалить мощный источник шума знаки пунктуации, которые превращают, к примеру, `«him.`» и «`him`» в разные слова. Для того чтобы очистить словарь, достаточно передать функции `split` единственный дополнительный параметр. Имеется в виду регулярное выражение, которое уничтожит всю «шелуху»: `regex("[ \t,.;:?]+")`. Получив такой аргумент, функция `split` сочтет любую последовательность знаков, упомянутых между `[` и `]`, одним из разделителей слов. Теперь мы готовы, как говорится, приносить большую пользу с помощью всего лишь маленького кусочка кода:
```d
void addParagraph(string line, ref PersonaData[string] info)
{
// Выделить имя персонажа и его реплику
line = strip(line);
// auto sentence = std.algorithm.find(line, ". ");
auto sentence = line.find(". ");
if (sentence.empty)
{
return;
}
auto persona = line[0 .. $ - sentence.length];
sentence = toLower(strip(sentence[2 .. $]));
// Выделить произнесенные слова
auto words = split(sentence, regex("[ \t,.;:?]+"));
// Вставка или обновление информации
if (!(persona in info))
{
// Первая реплика персонажа
info[persona] = PersonaData();
}
info[persona].totalWordsSpoken += words.length;
foreach (word; words)
{
++info[persona].wordCount[word];
}
}
```
Функция `addParagraph` отвечает за обновление ассоциативного массива. В случае если персонаж еще не высказывался, код вставляет «пустой» объект типа `PersonaData`, инициализированный значениями по умолчанию. Поскольку значение по умолчанию для типа `uint` ноль, а созданный в соответствии с правилами по умолчанию ассоциативный массив пуст, только что вставленный слот готов к приему осмысленной информации.
Наконец, для того чтобы напечатать краткую сводку по каждому персонажу, реализуем функцию `printResults`:
```d
void printResults(PersonaData[string] info)
{
foreach (persona, data; info)
{
writefln("%20s %6u %6u", persona, data.totalWordsSpoken, data.wordCount.length);
}
}
```
Готовы к тест-драйву? Тогда сохраните и запустите!
```sh
Queen 1104 500
Ros 738 338
For 55 45
Fort 74 61
Gentlemen 4 3
Other 105 75
Guil 349 176
Mar 423 231
Capt 92 66
Lord 70 49
Both 44 24
Oph 998 401
Ghost 683 350
All 20 17
Player 16 14
Laer 1507 606
Pol 2626 870
Priest 92 66
Hor 2129 763
King 4153 1251
Cor., Volt 11 11
Both [Mar 8 8
Osr 379 179
Mess 110 79
Sailor 42 36
Servant 11 10
Ambassador 41 34
Fran 64 47
Clown 665 298
Gent 101 77
Ham 11901 2822
Ber 220 135
Volt 150 112
Rey 80 37
```
Тут есть чем позабавиться. Как и ожидалось, наш дружок «Ham» с большим отрывом выигрывает у всех остальных, получив львиную долю слов. Довольно интересна роль Вольтиманда («Volt»): он немногословен, но при скромном количестве реплик виртуозно демонстрирует солидный словарный запас. Еще любопытнее в этом плане роль матроса («Sailor»), который вообще почти не повторяется. Также сравните красноречивую королеву («Queen») с Офелией («Oph»): королева произносит всего на 10% слов больше, чем Офелия, но ее лексикон богаче как минимум на 25%.
В выводе есть немного шума (например, `"Both [Mar"`), который прилежный программист легко устранит и который вряд ли статистически влияет на то, что действительно представляет для нас интерес. Тем не менее исправление последних огрехов поучительное (и рекомендуемое) упражнение.
[Исходный код](src/chapter-1-5/)
[В начало ⮍](#1-5-основные-структуры-данных) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.6. Интерфейсы и классы
Объектно-ориентированные средства важны для больших проектов; так что, знакомя вас с ними на примере маленьких программ, я рискую выставить себя недоумком. Прибавьте к этому большое желание избежать заезженных примеров с животными и работниками, и сложится довольно неприятная картина. Да, забыл еще кое-что: в маленьких примерах обычно не видны проблемы создания полиморфных объектов, а это очень важно. Что делать бедному автору! К счастью, реальный мир снабдил меня полезным примером в виде относительно небольшой задачи, которая в то же время не имеет удовлетворительного процедурного решения. Обсуждаемый ниже код это переработка небольшого полезного скрипта на языке awk, который вышел далеко за рамки задуманного. Мы вместе пройдем путь до объектно-ориентированного решения одновременно компактного, полного и изящного.
Как насчет небольшой программы, собирающей статистику (в связи с этим назовем ее `stats`)? Пускай ее интерфейс будет простым: имена статистических функций, используемых для вычислений, передаются в `stats` как параметры командной строки, последовательность чисел для анализа поступает в стандартный поток ввода в виде списка (разделитель пробел), статистические результаты печатаются один за другим по одному на строке. Вот пример работы программы:
```sh
$ echo 3 5 1.3 4 10 4.5 1 5 | stats Min Max Average
1
10
4.225
$ _
```
Написанный на скорую руку «непричесанный» скрипт без проблем решит эту задачу. Но в данном случае при увеличении количества статистических функций «лохматость» кода уничтожит преимущества от быстроты его создания. Так что поищем решение получше. Для начала остановимся на простейших статистических функциях: получение минимума, максимума и среднего арифметического. Нащупав легко расширяемый вариант кода, мы получим простор для неограниченной реализации более сложных статистических функций.
Простейший подход к решению задачи в цикле пройтись по входным данным и вычислить всю необходимую статистику. Но выбрать такой путь значит отказаться от идеи масштабируемости программы. Ведь всякий раз, когда нам потребуется добавить новую статистическую функцию, придется подвергать готовый код хирургическому вмешательству. Если мы хотим выполнять только те вычисления, о которых попросили в командной строке, необходимы серьезные изменения. В идеале мы должны заключить все статистические функции в последовательные куски кода. Таким образом, мы расширяем функциональность программы, просто добавляя новый код; это принцип открытости/закрытости во всей красе.
При таком подходе необходимо выяснить, что общего у всех (или хотя бы у большинства) статистических функций. Ведь наша цель обращаться ко всем функциям из одной точки программы, причем унифицированно. Для начала отметим, что `Min` и `Max` отбирают аргументы из входной последовательности по одному, а результат будет готов, как только закончится ввод. Конечный результат одно-единственное число. Также функция `Average` по окончании чтения всех своих аргументов должна выполнить завершающий шаг (разделить накопившуюся сумму на число слагаемых). Кроме того, у каждого алгоритма есть собственное состояние. Если разные вычисления должны предоставлять одинаковый интерфейс для работы с ними и при этом «запоминать» свое состояние, разумный шаг сделать их объектами и определить формальный интерфейс для управления всеми этими объектами и каждым из них в отдельности.
```d
interface Stat
{
void accumulate(double x);
void postprocess();
double result();
}
```
Интерфейс определяет требуемое поведение в виде набора функций. Разумеется, тот, кто замахнется на реализацию интерфейса, должен будет определить все функции в том виде, в каком они заявлены. Раз уж мы заговорили о реализации, давайте посмотрим, как можно определить класс `Min`, так чтобы он повиновался указаниям железной руки интерфейса `Stat`.
```d
class Min : Stat
{
private double min = double.max;
void accumulate(double x)
{
if (x < min)
{
min = x;
}
}
void postprocess() {} // Ничего не делать
double result()
{
return min;
}
}
```
`Min` это *класс*, пользовательский тип, привносящий в D преимущества ООП. С помощью синтаксиса `class Min: Stat` класс `Min` во всеуслышание объявляет, что он реализует интерфейс `Stat`. И `Min` действительно определяет все три функции, продиктованные волей `Stat`, в точности с теми же аргументами и возвращаемыми типами (иначе компилятор не дал бы `Min` просто так проскочить). `Min` содержит всего лишь один закрытый элемент (тот, что помечен директивой `private`) переменную `min` (наименьшее из прочитанных значений) и обновляет ее внутри функции `accumulate`. Начальное значение `Min` *самое большое* число (которое можно представить типом `double`), так что первое же число из входной последовательности заместит его.
Перед тем как определить другие статистические функции, реализуем основной алгоритм нашей программы `stats`, предусматривающий чтение параметров командной строки, создание соответствующих объектов, производящих вычисления (таких как экземпляр класса `Min`, когда через консоль передан аргумент *`Min`*), и манипулирование ими с помощью интерфейса `Stat`.
```d
import std.stdio : writeln, stdin;
import std.exception : enforce;
import stats;
void main(string[] args)
{
Stat[] stats;
foreach (arg; args[1 .. $])
{
auto newStat = cast(Stat) Object.factory("stats." ~ arg);
enforce(newStat, "Invalid statistics function: " ~ arg);
stats ~= newStat;
}
for (double x; stdin.readf(" %s ", &x) == 1;)
{
foreach (s; stats)
{
s.accumulate(x);
}
}
foreach (s; stats)
{
s.postprocess();
writeln(s.result());
}
}
```
Эта небольшая программа творит чудеса. Для начала список параметров `main` отличается от того, что мы видели до сих пор: на этот раз в функцию передается массив строк. Средства библиотеки времени исполнения D инициализируют этот массив параметрами, переданными компилятору из командной строки вместе с именем скрипта для запуска. Первый цикл инициализирует массив `stats` исходя из значений массива `args`. Учитывая, что в D (как и в других языках) первый аргумент это имя самой программы, мы пропускаем первую позицию: нас интересует срез `args[1 .. $]`. Теперь разберемся с командой
```d
auto newStat = cast(Stat) Object.factory("stats." ~ arg);
```
Тут много непонятного, но, как говорят в ситкомах, я все могу объяснить. Во-первых, здесь знак `~` служит бинарным оператором, то есть осуществляет конкатенацию строк. Поэтому если аргумент командной строки `Min`, то результат конкатенации строка `"stats.Min"`, которая и будет передана функции `Object.factory`. `Object` предок всех классов, создаваемых в программах на D. Он определяет статический метод `factory`, который принимает строку, ищет соответствующий тип в небольшой базе данных (которая строится во время компиляции), магическим образом создает объект типа, указанного в переданной строке, и возвращает его. Если запрошенный класс отсутствует в упомянутой базе данных, `Object.factory` возвращает `null`. Чтобы этого не произошло, достаточно определить класс `Min` где-нибудь в том же файле, что и вызов `Object.factory`. Возможность создавать объект по имени его типа это важное средство, востребованное во множестве полезных приложений. На самом деле, оно настолько важно, что является «сердцем» некоторых языков с динамической типизацией. Языки со статической типизацией (такие как D и Java) вынуждены полагаться на средства своих библиотек времени исполнения или предоставлять программисту самостоятельно изобретать механизмы регистрации и распознавания типов.
Почему `stats.Min`, а не просто `Min`? D серьезно относится к принципу модульности, поэтому в этом языке отсутствует глобальное пространство имен, где кто угодно может складировать что угодно. Каждый символ обитает в рамках модуля со своим именем, и по умолчанию имя модуля совпадает с именем его исходного файла без расширения. Таким образом, при условии что наш файл назван `stats.d`, D полагает, что всякое имя, определенное в этом файле, принадлежит модулю `stats`.
Осталась последняя загвоздка. Статический тип только что полученного объекта типа `Min` на самом деле не `Min`. Это звучит странно, но легко объясняется тем, что, вызвав `Object.factory("что угодно")`, вы можете создать *любой* объект, поэтому возвращаемый тип должен быть неким общим знаменателем для всех возможных объектных типов и это `Object`. Для того чтобы получить ссылку, соответствующую типу объекта, который вы задумали, необходимо преобразовать объект, возвращенный `Object.factory`, в объект типа `State`. Эта операция называется *приведением типов (type casting)*. В языке D выражение `cast(T) expr` приводит выражение `expr` к типу `T`. Операции приведения типов, в которых участвуют классы или интерфейсы, всегда проверяются, поэтому код надежно защищен от дураков.
Оглянувшись назад, мы заметим, что львиная доля того, что делает скрипт, выполняется в первых пяти его строках. Эта самая сложная часть, которая полностью определяет весь остальной код. Второй цикл читает по одному числу за раз (об этом заботится функция `readf`) и вызывает `accumulate` для всех объектов, собирающих статистику. Функция `readf` возвращает число объектов, успешно прочитанных согласно заданной строке формата. В нашем случае формат задан в виде строки `" %s "`, что означает «один элемент, окруженный любым количеством пробелов». (Тип элемента определяется типом считанного элемента, в нашем случае `x` принимает значение типа `double`.) Последнее, что делает программа, выводит результаты вычислений на печать.
[Исходный код](src/chapter-1-6/)
[В начало ⮍](#1-6-интерфейсы-и-классы) [Наверх ⮍](#1-знакомство-с-языком-d)
### 1.6.1. Больше статистики. Наследование
Реализация `Max` так же тривиальна, как и реализация `Min`; за исключением небольших изменений в `accumulate`, эти классы ничем не отличаются друг от друга[^9]. Даже если новое задание до боли напоминает предыдущее, в голову должна приходить мысль «интересно», а не «о боже, какая скука». Рутинные задачи это возможность для повторного использования, и «правильные» языки, способные лучше эксплуатировать различные преимущества подобия, по некоторой абстрактной шкале качества должны оцениваться выше. Нам придется выяснить, что именно общего у функций `Min` и `Max` (и, в идеале, у прочих статистических функций). Присмотревшись к ним, можно заметить, что обе принадлежат к разряду статистических функций, результат которых вычисляется шаг за шагом и может быть вычислен всего по одному числу. Назовем такую категорию статистических функций *пошаговыми функциями*.
```d
class IncrementalStat : Stat
{
protected double _result;
abstract void accumulate(double x);
void postprocess() {}
double result()
{
return _result;
}
}
```
Абстрактный класс можно воспринимать как частичное обязательство: он реализует некоторые методы, но не все, так что «самостоятельно» такой код работать не может. Материализуется абстрактный класс тогда, когда от него наследуют и в теле потомков завершают реализацию. Класс `IncrementalStat` обслуживает повторяющийся код классов, реализующих интерфейс `Stat`, но оставляет реализацию метода `accumulate` своим потомкам. Вот как выглядит новая версия класса `Min`:
```d
class Min : IncrementalStat
{
this()
{
_result = double.max;
}
override void accumulate(double x)
{
if (x < _result)
{
_result = x;
}
}
}
```
Кроме того, в классе `Min` определен конструктор в виде специальной функции `this()`, необходимый для корректной инициализации результата. Даже несмотря на добавление конструктора, полученный код значительно улучшил ситуацию относительно исходного положения дел, особенно с учетом того факта, что множество других статистических функций также соответствуют этому шаблону (например, сумма, дисперсия, среднее арифметическое, стандартное отклонение). Посмотрим на реализацию функции получения среднего арифметического, поскольку это прекрасный повод представить еще пару концепций:
```d
class Average : IncrementalStat
{
private uint items = 0;
this()
{
_result = 0;
}
override void accumulate(double x)
{
_result += x;
++items;
}
override void postprocess()
{
if (items)
{
_result /= items;
}
}
}
```
Начнем с того, что в `Average` вводится еще одно поле, `items`, которое инициализируется нулем с помощью синтаксиса `items = 0` (только для того, чтобы показать, как надо инициализировать переменные, но, как отмечалось выше, целые числа и так инициализируются нулем по умолчанию). Второе, что необходимо отметить: `Average` определяет конструктор, который присваивает переменной `_result` ноль. Так сделано, потому что, в отличие от минимума или максимума, при отсутствии аргументов среднее арифметическое считается равным нулю. И хотя может показаться, что инициализировать `_result` значением NaN только для того, чтобы тут же записать в эту переменную ноль, бессмысленное действие, уход от так называемого «мертвого присваивания» представляет собой легкую добычу для любого оптимизатора. Наконец, `Average` переопределяет метод `postprocess`, несмотря на то что в классе `IncrementalStat` он уже определен. В языке D по умолчанию можно переопределить (унаследовать и заново определить) методы любого класса, но надо обязательно добавлять директиву `override`, чтобы избежать всевозможных несчастных случаев (таких как неудача переопределения в связи с какой-нибудь опечаткой или изменением в базовом типе, либо переопределение чего-нибудь по ошибке). Если вы поставите перед методом класса ключевое слово `final`, то запретите классам-потомкам переопределять эту функцию (что эффективно останавливает механизм динамического поиска методов по дереву классов).
[Исходный код](src/chapter-1-6-1/)
[В начало ⮍](#1-6-1-больше-статистики-наследование) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.7. Значения против ссылок
Проведем небольшой эксперимент:
```d
import std.stdio;
struct MyStruct
{
int data;
}
class MyClass
{
int data;
}
void main()
{
// Играем с объектом типа MyStruct
MyStruct s1;
MyStruct s2 = s1;
++s2.data;
writeln(s1.data); // Печатает 0
// Играем с объектом типа MyClass
MyClass c1 = new MyClass;
MyClass c2 = c1;
++c2.data;
writeln(c1.data); // Печатает 1
}
```
Похоже, игры с объектом типа `MyStruct` сильно отличаются от игр с объектом типа `MyObject`. И в том и в другом случае мы создаем переменную, которую затем копируем в другую переменную, после чего изменяем копию (вспомните, что `++` это унарный оператор, прибавляющий единицу к своему аргументу). Этот эксперимент показывает, что после копирования `c1` и `c2` ссылаются на одну и ту же область памяти с информацией, а `s1` и `s2`, напротив, «живут врозь».
Поведение `MyStruct` свидетельствует о том, что этот объект подчиняется *семантике значений*: каждая переменная ссылается на собственное единственное значение, и присваивание одной переменной другой означает, что значение одной переменной реально копируется в значение другой переменной. Исходное значение, по образу и подобию которого изменяли вторую переменную, остается нетронутым, и обе переменные далее продолжают развиваться независимо друг от друга. Поведение `MyClass` говорит, что объект этого типа подчиняется *ссылочной семантике*: значения создаются явно (в нашем случае с помощью вызова `new MyClass`), и присваивание одного экземпляра класса другому означает лишь то, что обе переменные будут ссылаться на одно и то же значение в памяти.
Со значениями легко работать, о них просто рассуждать, и они позволяют производить эффективные вычисления с переменными небольшого размера. С другой стороны, нетривиальные программы сложно реализовать, не обладая средствами доступа к переменным без их копирования. Отсутствие возможности работать со ссылками препятствует, например, работе с типами, ссылающимися на себя же (списки или деревья), или структурами, ссылающимися друг на друга (такими как дочернее окно, знающее о своем родительском окне). Любой уважающий себя язык реализует работу со ссылками в том или ином виде; спорят только о необходимых умолчаниях. В C в общем случае переменные трактуются как значения, но если пользователь захочет, он будет работать со ссылками с помощью указателей. В дополнение к указателям, C++ определяет ссылочные типы. Любопытно, что чисто функциональные языки могут использовать ссылки или значения, когда сочтут нужным, потому что при написании кода между ними нет разницы. Ведь чисто функциональные языки запрещают изменения, поэтому невозможно сказать, когда они порождают копию значения, а когда просто используют ссылку на него значения «заморожены», поэтому вы не сможете проверить, разделяется ли значение между несколькими переменными, изменив одну из них. Чисто объектно-ориентированные языки, напротив, традиционно поощряют изменения. Для них общий случай ссылочная семантика. Некоторые такие языки достигают умопомрачительной гибкости, допуская, например, динамическое изменение системных переменных. Наконец, некоторые языки избрали гибридный подход, включая как типы-значения, так и ссылочные типы, с разной долей предпочтения тем или другим.
Язык D систематически реализует гибридный подход. Для определения ссылочных типов используйте классы. Для определения типов-значений или гибридных типов используйте структуры. В главах 6 и 7 соответственно описаны конструкторы этих типов, снабженные средствами для реализации соответствующего подхода. Например, структуры не поддерживают динамическое наследование и полиморфизм (такой как в рассмотренной нами программе `stats`), поскольку такое поведение не согласуется с семантикой значений. Динамический полиморфизм объектов это характеристика ссылочной семантики, и любая попытка смешать эти два подхода приведет лишь к жутким последствиям. (Например, классическая опасность, подстерегающая программистов на C++, slicing, неожиданное лишение объекта его полиморфных способностей в результате невнимательного использования этого объекта в качестве значения. В языке D slicing невозможен.)
В завершение хочется сказать, что структуры пожалуй, наиболее гибкое проектное решение. Определив структуру, вы можете вдохнуть в нее любую семантику. Вы можете сделать так, что значение будет копироваться постоянно, реализовать ленивое копирование, а-ля копирование при записи, или подсчитывать ссылки, или выбрать что-то среднее между этими способами. Вы даже можете определить ссылочную семантику, используя классы или указатели *внутри* своей структуры. С другой стороны, некоторые из этих альтернатив требуют подкованности в техническом плане; использование классов, напротив, подразумевает простоту и унифицированность.
[Исходный код](src/chapter-1-7/)
[В начало ⮍](#1-7-значения-против-ссылок) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.8. Итоги
Эта глава вводная, поэтому какие-то детали отдельных примеров и концепций остались за кадром или были рассмотрены вскользь. При этом опытный программист легко поймет, как можно завершить и усовершенствовать код примеров.
Надеюсь, что-то интересное нашлось для каждого. Кодера-практика, противника любых излишеств, могла порадовать чистота синтаксиса массивов и ассоциативных массивов. Уже эти две концепции сильно упрощают ежедневное кодирование и полезны как для малых, так и для больших проектов. Поклонник объектно-ориентированного программирования, хорошо знакомый с интерфейсами и классами, мог отметить хорошую масштабируемость языка для крупных проектов. А те, кто хочет писать на D короткие скрипты, увидели, как легко пишутся и запускаются сценарии, манипулирующие файлами.
Как водится, полный рассказ гораздо длиннее. И все же полезно время от времени вернуться к основам и удостовериться, что простые вещи остаются простыми.
[В начало ⮍](#1-8-итоги) [Наверх ⮍](#1-знакомство-с-языком-d)
[^1]: «Shebang» (от shell bang: shell консоль, bang восклицательный знак), или «shabang» (# sharp) обозначение пути к компилятору или интерпретатору в виде `#!/путь/к/программе`. *Прим. пер.*
[^2]: В этой книге под «параметром» понимается значение, используемое внутри функции, а под «аргументом» значение, передаваемое в функцию извне.
[^3]: `.idup` свойство любого массива, возвращающее неизменяемую (immutable) копию массива. Про неизменяемость будет рассказано позже, пока же следует знать, что ключ ассоциативного массива должен быть неизменяемым. *Прим. науч. ред.*
[^4]: Если кто-то из ваших коллег прокачал самоуверенность до уровня Супермена, спросите его, что делает код `object.template fun<arg>()`, и вы увидите криптонит в действии.
[^5]: Усугубляет ситуацию с угловыми скобками то, что `<<` и `>>` тоже операторы.
[^6]: Этот файл содержит текст пьесы «Гамлет». *Прим. пер.*
[^7]: Ham (англ.) ветчина. *Прим. пер.*
[^8]: NaN (Not a Number, нечисло) хорошее начальное значение по умолчанию для чисел с плавающей запятой. К сожалению, для целых чисел не существует эквивалентного начального значения.
[^9]: Это не совсем так. Переменная-`аккумулятор` должна быть инициализирована значением `double.max` и соответственно переименована. *Прим. науч. ред.*

View file

@ -0,0 +1,16 @@
import std.stdio;
void main()
{
// Зна­че­ния, ко­то­рые ни­ко­гда не из­ме­нят­ся
immutable inchesPerFoot = 12;
immutable cmPerInch = 2.54;
// Пе­ре­би­ра­ем и пи­шем
foreach (feet; 5 .. 7)
{
foreach (inches; 0 .. inchesPerFoot)
{
writeln(feet, "'", inches, "''\t", (feet * inchesPerFoot + inches) * cmPerInch);
}
}
}

View file

@ -0,0 +1,12 @@
import std.stdio;
void main()
{
// Зна­че­ния, ко­то­рые ни­ко­гда не из­ме­нят­ся
immutable inchesPerFoot = 12;
immutable cmPerInch = 2.54;
// Пе­ре­би­ра­ем и пи­шем
foreach (feet; 5 .. 7)
foreach (inches; 0 .. inchesPerFoot)
writeln(feet, "'", inches, "''\t", (feet * inchesPerFoot + inches) * cmPerInch);
}

View file

@ -0,0 +1,15 @@
import std.stdio;
void fun(ref uint x, double y)
{
x = 42;
y = 3.14;
}
void main()
{
uint a = 1;
double b = 2;
fun(a, b);
writeln(a, " ", b);
}

View file

@ -0,0 +1,19 @@
import std.stdio, std.string;
import std.algorithm;
void main()
{
size_t [string] dictionary;
foreach (line; stdin.byLine())
{
// Раз­бить стро­ку на сло­ва
// До­ба­вить ка­ж­дое сло­во стро­ки в сло­варь
foreach (word; line.strip.splitter)
{
if (word in dictionary) continue; // Ни­че­го не де­лать
auto newID = dictionary.length;
dictionary[word.idup] = newID;
writeln(newID, '\t', word);
}
}
}

View file

@ -0,0 +1,35 @@
import std.array;
bool binarySearch(T)(T[] input, T value)
{
while (!input.empty)
{
auto i = input.length / 2;
auto mid = input[i];
if (mid > value) input = input[0 .. i];
else if (mid < value) input = input[i + 1 .. $];
else return true;
}
return false;
}
// рекурсивная версия
bool binarySearchR(T)(T[] input, T value)
{
if (input.empty) return false;
auto i = input.length / 2;
auto mid = input[i];
if (mid > value) return binarySearch(input[0 .. i], value);
if (mid < value) return binarySearch(input[i + 1 .. $], value);
return true;
}
unittest
{
assert(binarySearch([ 1, 3, 6, 7, 9, 15 ], 6));
assert(!binarySearch([ 1, 3, 6, 7, 9, 15 ], 5));
assert(binarySearchR([ 1, 3, 6, 7, 9, 15 ], 6));
assert(!binarySearchR([ 1, 3, 6, 7, 9, 15 ], 5));
// Ука­зать T яв­но (на­при­мер, для на­деж­но­сти)
assert(binarySearch!(int)([ 1, 3, 6, 7, 9, 15 ], 6));
}

View file

@ -0,0 +1,28 @@
import std.algorithm, std.stdio, std.string;
void main()
{
// Рас­счи­тать таб­ли­цу час­тот
uint[string] freqs;
foreach (line; stdin.byLine())
{
foreach (word; line.strip.splitter)
{
++freqs[word.idup];
}
}
// На­пе­ча­тать таб­ли­цу час­тот
foreach (key, value; freqs)
{
writefln("%6u\t%s", value, key);
}
// На­пе­ча­тать таб­ли­цу час­тот с сортировкой
string[] words = freqs.keys;
sort!((a, b) { return freqs[a] > freqs[b]; })(words);
foreach (word; words)
{
writefln("%6u\t%s", freqs[word], word);
}
}

View file

@ -0,0 +1,68 @@
import std.algorithm, std.conv, std.regex, std.range, std.stdio, std.string, std.ascii;
struct PersonaData
{
uint totalWordsSpoken;
uint[string] wordCount;
}
void addParagraph(string line, ref PersonaData[string] info)
{
// Вы­де­лить имя пер­со­на­жа и его ре­п­ли­ку
line = strip(line);
// auto sentence = std.algorithm.find(line, ". ");
auto sentence = line.find(". ");
if (sentence.empty)
{
return;
}
auto persona = line[0 .. $ - sentence.length];
sentence = toLower(strip(sentence[2 .. $]));
// Вы­де­лить про­из­не­сен­ные сло­ва
auto words = split(sentence, regex("[ \t,.;:?]+"));
// Вставка или обновление информации
if (!(persona in info))
{
// Пер­вая ре­п­ли­ка пер­со­на­жа
info[persona] = PersonaData();
}
info[persona].totalWordsSpoken += words.length;
foreach (word; words)
{
++info[persona].wordCount[word];
}
}
void printResults(PersonaData[string] info)
{
foreach (persona, data; info)
{
writefln("%20s %6u %6u", persona, data.totalWordsSpoken, data.wordCount.length);
}
}
void main()
{
// На­ка­пл­и­ва­ет ин­фор­ма­цию о глав­ных ге­ро­ях
PersonaData[string] info;
// За­пол­нить info
string currentParagraph;
foreach (line; stdin.byLine())
{
// 4 символа отступа
if (line.startsWith(" ") && line.length > 4 && isAlpha(line[4]))
{
// Пер­со­наж про­дол­жа­ет вы­ска­зы­ва­ние
currentParagraph ~= line[3 .. $];
}
// 2 символа отступа
else if (line.startsWith(" ") && line.length > 2 && isAlpha(line[2]))
{
// Пер­со­наж толь­ко что на­чал го­во­рить
addParagraph(currentParagraph, info);
currentParagraph = to!string(line[2 .. $]);
}
}
// За­кон­чи­ли, те­перь на­пе­ча­та­ем со­б­ран­ную ин­фор­ма­цию
printResults(info);
}

View file

@ -0,0 +1,26 @@
import std.stdio : writeln, stdin;
import std.exception : enforce;
import stats;
void main(string[] args)
{
Stat[] stats;
foreach (arg; args[1 .. $])
{
auto newStat = cast(Stat) Object.factory("stats." ~ arg);
enforce(newStat, "Invalid statistics function: " ~ arg);
stats ~= newStat;
}
for (double x; stdin.readf(" %s ", &x) == 1;)
{
foreach (s; stats)
{
s.accumulate(x);
}
}
foreach (s; stats)
{
s.postprocess();
writeln(s.result());
}
}

View file

@ -0,0 +1,76 @@
module stats;
interface Stat
{
void accumulate(double x);
void postprocess();
double result();
}
class IncrementalStat : Stat
{
protected double _result;
abstract void accumulate(double x);
void postprocess() {}
double result()
{
return _result;
}
}
class Min : IncrementalStat
{
this()
{
_result = double.max;
}
override void accumulate(double x)
{
if (x < _result)
{
_result = x;
}
}
}
class Max : IncrementalStat
{
this()
{
_result = double.min_normal;
}
override void accumulate(double x)
{
if (x > _result)
{
_result = x;
}
}
}
class Average : IncrementalStat
{
private uint items = 0;
this()
{
_result = 0;
}
override void accumulate(double x)
{
_result += x;
++items;
}
override void postprocess()
{
if (items)
{
_result /= items;
}
}
}

View file

@ -0,0 +1,26 @@
import std.stdio : writeln, stdin;
import std.exception : enforce;
import stats;
void main(string[] args)
{
Stat[] stats;
foreach (arg; args[1 .. $])
{
auto newStat = cast(Stat) Object.factory("stats." ~ arg);
enforce(newStat, "Invalid statistics function: " ~ arg);
stats ~= newStat;
}
for (double x; stdin.readf(" %s ", &x) == 1;)
{
foreach (s; stats)
{
s.accumulate(x);
}
}
foreach (s; stats)
{
s.postprocess();
writeln(s.result());
}
}

View file

@ -0,0 +1,48 @@
module stats;
interface Stat
{
void accumulate(double x);
void postprocess();
double result();
}
class Min : Stat
{
private double min = double.max;
void accumulate(double x)
{
if (x < min)
{
min = x;
}
}
void postprocess() {} // Ни­че­го не де­лать
double result()
{
return min;
}
}
class Max : Stat
{
private double max = double.min_normal;
void accumulate(double x)
{
if (x > max)
{
max = x;
}
}
void postprocess() {} // Ни­че­го не де­лать
double result()
{
return max;
}
}

View file

@ -0,0 +1,25 @@
import std.stdio;
struct MyStruct
{
int data;
}
class MyClass
{
int data;
}
void main()
{
// Иг­ра­ем с объ­ек­том ти­па MyStruct
MyStruct s1;
MyStruct s2 = s1;
++s2.data;
writeln(s1.data); // Пе­ча­та­ет 0
// Иг­ра­ем с объ­ек­том ти­па MyClass
MyClass c1 = new MyClass;
MyClass c2 = c1;
++c2.data;
writeln(c1.data); // Пе­ча­та­ет 1
}

View file

@ -0,0 +1,6 @@
import std.stdio;
void main()
{
writeln("Hello, world!");
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -0,0 +1,20 @@
import std.stdio;
void main()
{
auto a = r"Строка с \ и " ~ `"` ~ " внутри.";
auto b = r"c:\games\Sudoku.exe";
auto c = r"ab\n";
auto d = q"[Какая-то строка с "кавычками", `обратными апострофами` и [квадратными скобками]]";
auto e = q"/Просто строка/";
auto f = q"EOS
This
is a multi-line
heredoc string
EOS";
auto g = q{ foo(q{hello}); };
// auto h = q{ № };
// auto i = q{ __EOF__ };
}

View file

@ -0,0 +1,19 @@
import std.stdio;
void main()
{
writeln(typeid(typeof("Hello, world!"))); // immutable(char)[]
immutable(char)[] str = "One";
str = "Two";
writeln(typeid(typeof(str))); // immutable(char)[]
immutable(char)[3] a = "Hi!";
immutable(char)[] b = a;
writeln(a.length, " ", b.length); // 3 3
wstring x = "Здрав­ст­вуй, ши­ро­кий мир!";
writeln(typeid(typeof(x))); // immutable(wchar)[]
dstring y = "Здрав­ст­вуй, еще бо­лее ши­ро­кий мир!";
writeln(typeid(typeof(y))); // immutable(dchar)[]
}

View file

@ -0,0 +1,8 @@
import std.stdio;
void main()
{
auto crlf = "\r\n";
auto a = "В этой стро­ке есть \"двой­ные ка­выч­ки\", а так­же
пе­ре­вод стро­ки, да­же два" ~ "\n";
}

View file

@ -0,0 +1,21 @@
import std.stdio;
void main()
{
auto somePrimes = [ 2u, 3, 5, 7, 11, 13 ];
writeln(somePrimes);
auto someDoubles = [ 1.5, 3, 4.5 ];
writeln(someDoubles);
auto constants = [ 2.71, 3.14, 6.023e22 ];
writeln(constants);
constants[0] = 2.21953167;
writeln(constants);
auto salutations = [ "привет", "здравствуйте", "здорово" ];
writeln(salutations);
salutations[2] = "Да здравствует Цезарь";
writeln(salutations);
auto famousNamedConstants = [ "пи" : 3.14, "e" : 2.71, "константа дивана" : 2.22 ];
writeln(famousNamedConstants);
}

View file

@ -0,0 +1,20 @@
import std.stdio;
void main()
{
auto f = function double(int x) { return x / 10.; };
auto a = f(5);
assert(a == 0.5);
double function(int) e = function double(int x) { return x / 10.; };
auto b = e(5);
assert(b == 0.5);
int h = 2;
auto g = delegate double(int x) { return h * x / 10.; };
auto i = g(5);
assert(i == 1);
h = 3;
auto j = g(5);
assert(j == 1.5);
}

View file

@ -0,0 +1,15 @@
import std.stdio;
void main()
{
int a = -1; // То есть 0xFFFF_FFFF
int b = a << 1;
assert(b == -2); // 0xFFFF_FFFE
writeln("a = ", a, "; b = ", b, ';');
int c = a >> 1;
assert(c == -1); // 0xFFFF_FFFF
writeln("a = ", a, "; c = ", c, ';');
int d = a >>> 1;
assert(d == +2147483647); // 0x7FFF_FFFF
writeln("a = ", a, "; d = ", d, ';');
}

View file

@ -0,0 +1,20 @@
import std.stdio;
void main()
{
double[string] table = [
"one": 1.0,
"two": 2.0
];
writeln(table);
auto p = "three" in table;
if (p)
{
++*p;
}
else
{
table["three"] = 3.0;
}
writeln(table);
}

View file

@ -0,0 +1,10 @@
import std.stdio;
void main()
{
auto a = "ка­кая-то стро­ка";
auto b = a; // a и b ссы­ла­ют­ся на один и тот же мас­сив
a is b && writeln("Ага, это действительно одно и то же.");
auto c = "какая-то (другая) строка";
a is c || writeln("Действительно... не одно и то же.");
}

View file

@ -0,0 +1,7 @@
import std.stdio;
void main()
{
string line;
line == "#\n" && writeln("Успешно принята строка #. ");
}

View file

@ -0,0 +1,7 @@
import std.stdio;
void main()
{
string line;
line.length > 0 || line = "\n";
}

View file

@ -0,0 +1,9 @@
import std.stdio;
void main()
{
int x = 5, y = 5;
bool which = true;
(which ? x : y) += 5;
assert(x == 10);
}

View file

@ -0,0 +1,8 @@
import std.stdio;
void main()
{
int a = 5;
int b = 10;
int c = (a = b, b = 7, 8);
}

View file

@ -0,0 +1,22 @@
import std.stdio;
void main()
{
bool
a = is(int[]), // True, int[] до­пус­ти­мый тип
b = is(int[5]), // True, int[5] так­же до­пус­ти­мый тип
c = is(int[-3]), // False, раз­мер мас­си­ва за­дан не­вер­но
d = is(Blah); // False (ес­ли тип с име­нем Blah не был оп­ре­де­лен)
writeln("a = ", a, "; b = ", b, "; c = ", c, "; d = ", d, ';');
alias uint UInt;
assert(is(uint == UInt));
a = is(int[5] : int[]), // true, int[5] мо­жет быть пре­об­ра­зо­ван к int[]
b = is(int[5] == int[]), // false; это раз­ные ти­пы
c = is(uint : long), // true
d = is(ulong : long); // true
writeln("a = ", a, "; b = ", b, "; c = ", c, "; d = ", d, ';');
}

View file

@ -0,0 +1,10 @@
import std.stdio;
void main()
{
int[] a = new int[5]; // Соз­дать мас­сив из пя­ти це­лых чи­сел
int[] b = a[3 .. 5]; // b ссы­ла­ет­ся на два по­след­них эле­мен­та a
b[0] = 1;
b[1] = 3;
assert(a == [ 0, 0, 0, 1, 3 ]); // a был из­ме­нен
}

View file

@ -0,0 +1,16 @@
import std.stdio;
void main()
{
auto arr1 = new int[4];
assert(arr1.length == 4);
assert(arr1 == [ 0, 0, 0, 0 ]); // Ини­циа­ли­зи­ро­ван по умол­ча­нию
auto arr2 = new int[](4);
assert(arr2.length == 4);
assert(arr2 == [ 0, 0, 0, 0 ]); // Ини­циа­ли­зи­ро­ван по умол­ча­нию
auto matrix = new int[][](4, 8);
assert(matrix.length == 4);
assert(matrix[0].length == 8);
}

View file

@ -0,0 +1,7 @@
import std.stdio;
void main()
{
auto a = 2 ^^ 3;
writeln(a); // 8
}

View file

@ -0,0 +1,7 @@
import std.stdio;
void main()
{
auto a = 2 ^^ 3;
writeln(a); // 8
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
/**
* 1. Объявите класс, который наследуется от Exception.
* 2. Создайте конструктор, который принимает, как минимум, два параметра: string file и
* size_t line, со значениями по умолчанию __FILE__ и __LINE__ соответственно.
* 3. Попросите конструктора переслать аргументы конструктору исключения (super).
* 4. Используйте свое исключение.
*/
class MyException : Exception
{
this (string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
{
super(message, file, line, next);
}
}
void main()
{
import std.stdio;
try
{
throw new MyException("message here");
}
catch(MyException e)
{
writeln("caught ", e);
}
}

View file

@ -0,0 +1,29 @@
/**
* 1. Объявите класс, который наследуется от Exception.
* 2. Создайте конструктор, который принимает, как минимум, два параметра: string file и
* size_t line, со значениями по умолчанию __FILE__ и __LINE__ соответственно.
* 3. Попросите конструктора переслать аргументы конструктору исключения (super).
* 4. Используйте свое исключение.
*/
class MyException : Exception
{
this (string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
{
super(message, file, line, next);
}
}
void main()
{
import std.stdio;
try
{
throw new MyException("message here");
}
catch(MyException e)
{
writeln("caught ", e);
}
}

View file

@ -0,0 +1,9 @@
void main()
{
import std.stdio;
scope(exit) writeln("Running scope exit"); // Выполнится самая последняя
scope(success) writeln("Running scope success"); // Выполнится самая первая
return;
scope(exit) writeln("This is never run since the function returned before it was registered."); // Не выполнится
}

View file

@ -0,0 +1,34 @@
import std.stdio;
import std.conv;
enum Berries { strawberry, blackberry, blueberry }
string[] names = [
"Клубника",
"Ежевика",
"Черника"
];
void printBerry(Berries b)
{
final switch (b)
{
case b.strawberry:
writeln(names[b.strawberry]);
break;
case b.blackberry:
writeln(names[b.blackberry]);
break;
case b.blueberry:
writeln(names[b.blueberry]);
break;
}
}
void main()
{
int berry;
readf("%s\n", berry);
printBerry(to!Berries(berry));
}

View file

@ -0,0 +1,17 @@
import std.math, std.stdio;
void main()
{
foreach (float elem; 1.0 .. 100.0)
{
writeln(log(elem)); // По­лу­ча­ет ло­га­рифм с оди­нар­ной точ­но­стью
}
foreach (double elem; 1.0 .. 100.0)
{
writeln(log(elem)); // Двой­ная точ­ность
}
foreach (elem; 1.0 .. 100.0)
{
writeln(log(elem)); // То же са­мое
}
}

View file

@ -0,0 +1,23 @@
import std.stdio;
void print(double[string] map)
{
foreach (i, e; map)
{
writefln("array['%s'] = %s;", i, e);
}
}
void main()
{
float[] arr = [ 1.0, 2.5, 4.0 ];
foreach (ref float elem; arr)
{
elem *= 2; // Без про­блем
}
writeln(arr);
print(["Луна": 1.283, "Солнце": 499.307, "Проксима Центавра": 133_814_298.759]);
}

View file

@ -0,0 +1,24 @@
import std.math, std.stdio;
struct Point
{
double x, y;
double norm()
{
return sqrt(x * x + y * y);
}
}
void main()
{
Point p;
int z;
with (p)
{
x = 3; // При­сваи­ва­ет зна­че­ние по­лю p.x
p.y = 4; // Хо­ро­шо, что все еще мож­но яв­но ис­поль­зо­вать p
writeln(norm()); // Вы­во­дит зна­че­ние по­ля p.norm, то есть 5
z = 1; // По­ле z ос­та­лось ви­ди­мым
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,13 @@
import std.stdio;
void main()
{
auto array1 = new short[55];
assert(array1.length == 55);
writeln(array1);
auto array2 = new int[10];
array2[9] = 42;
assert(array2[$ - 1] == 42);
writeln(array2);
}

View file

@ -0,0 +1,8 @@
void main()
{
auto array = new int[10];
array.length += 1000; // Расширяется
assert(array.length == 1010);
array.length /= 10; // Су­жа­ет­ся
assert(array.length == 101);
}

View file

@ -0,0 +1,8 @@
import std.stdio;
void main()
{
auto array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
// На­пе­ча­тать толь­ко вто­рую по­ло­ви­ну
writeln(array[$ / 2 .. $]);
}

View file

@ -0,0 +1,8 @@
void main()
{
int[] array = [0, 1, 2];
int[] subarray = array[1 .. $];
assert(subarray.length == 2);
subarray[1] = 33;
assert(array[2] == 33);
}

View file

@ -0,0 +1,10 @@
void main()
{
auto a = ["hello", "world"];
auto b = a;
assert(a is b); // Тест прой­ден, у a и b од­ни те же гра­ни­цы
assert(a == b); // Ес­те­ст­вен­но, тест прой­ден
b = a.dup;
assert(a == b); // Тест прой­ден, a и b рав­ны, хо­тя за­ни­ма­ют раз­ные об­лас­ти па­мя­ти
assert(a !is b); // Тест прой­ден, a и b раз­лич­ны, хо­тя име­ют оди­на­ко­вое со­дер­жи­мое
}

View file

@ -0,0 +1,8 @@
void main()
{
int[] a = [0, 10, 20];
int[] b = a ~ 42;
assert(b == [0, 10, 20, 42]);
a = b ~ a ~ 15;
assert(a.length == 8);
}

View file

@ -0,0 +1,22 @@
import std.stdio;
void main()
{
auto a = [ 0.5, -0.5, 1.5, 2 ];
auto b = [ 3.5, 5.5, 4.5, -1 ];
auto c = new double[4]; // Па­мять под мас­сив долж­на быть уже вы­де­ле­на
c[] = (a[] + b[]) / 2; // Рас­счи­тать сред­нее ариф­ме­ти­че­ское a и b
assert(c == [ 2.0, 2.5, 3.0, 0.5 ]);
auto d = [1.0, 2.5, 3.6];
auto e = [4.5, 5.5, 1.4];
auto f = new double[3];
f[] += 4 * d[] + e[];
int[] g = new int[5];
int[] h = new int[5];
g[] = -1; // За­пол­нить все ячей­ки b зна­че­ни­ем -1
h[] = g[]; // Ско­пи­ро­вать все дан­ные из b в a
writeln(g);
writeln(h);
}

View file

@ -0,0 +1,15 @@
import std.stdio;
void main()
{
auto array = [0, 2, 4, 6, 8, 10];
array = array[0 .. $ - 2];
// Су­же­ние спра­ва на два эле­мен­та
assert(array == [0, 2, 4, 6]);
array = array[1 .. $];
// Су­же­ние сле­ва на один эле­мент
assert(array == [2, 4, 6]);
array = array[1 .. $ - 1];
// Су­же­ние с обе­их сто­рон
assert(array == [4]);
}

View file

@ -0,0 +1,18 @@
import std.conv, std.stdio;
int main(string[] args)
{
// Избавиться от имени программы
args = args[1 .. $];
while (args.length >= 2)
{
if (to!int(args[0]) != to!int(args[$ - 1]))
{
writeln("не палиндром");
return 1;
}
args = args[1 .. $ - 1];
}
writeln("палиндром");
return 0;
}

View file

@ -0,0 +1,9 @@
void main()
{
int[] a = [0, 10, 20, 30, 40, 50, 60, 70];
auto b = a[4 .. $];
a = a[0 .. 4];
// Сей­час a и b при­мы­ка­ют друг к дру­гу
a ~= [0, 0, 0, 0];
assert(b == [40, 50, 60, 70]); // Тест прой­ден; мас­сив a был пе­ре­не­сен в но­вую об­ласть па­мя­ти
}

View file

@ -0,0 +1,26 @@
import std.random;
import std.stdio;
void main()
{
// От 1 до 127 эле­мен­тов
auto array = new double[uniform(1, 128)];
foreach (i; 0 .. array.length)
{
array[i] = uniform(0.0, 1.0);
}
writeln(array);
foreach (ref element; array)
{
element = uniform(0.0, 1.0);
}
writeln(array);
auto copy = array.dup;
assert(array !is copy);
assert(array == copy);
}

View file

@ -0,0 +1,15 @@
import std.stdio;
void main()
{
int[5] array = [40, 30, 20, 10, 0];
auto slice1 = array[2 .. $]; // slice1 име­ет тип int[]
assert(slice1 == [20, 10, 0]);
auto slice2 = array[]; // Та­кой же, как array[0 .. $]
assert(slice2 == array);
int[10] a;
int[] b = a[1 .. 7]; // Все в по­ряд­ке
auto c = a[1 .. 7]; // Все в по­ряд­ке, c так­же име­ет тип int[]
int[6] d = a[1 .. 7]; // Все в по­ряд­ке, срез a[1 .. 7] ско­пи­ро­ван в d
}

View file

@ -0,0 +1,34 @@
import std.stdio;
int[3] fun(int[3] x, int[3] y)
{
// x и y ко­пии пе­ре­дан­ных ар­гу­мен­тов
x[0] = y[0] = 100;
return x;
}
double[3] fun2(double[] x)
{
double[3] result;
result[] = 2 * x[]; // Опе­ра­ция над мас­си­вом в це­лом
return result;
}
void main()
{
int[3] a = [1, 2, 3];
int[3] b = a;
a[1] = 42;
assert(b[1] == 2); // b не­за­ви­си­мая ко­пия a
auto c = fun(a, b); // c име­ет тип int[3]
assert(c == [100, 42, 3]);
writeln(c);
assert(b == [1, 2, 3]); // Вы­зов fun ни­как на от­ра­зил­ся на b
writeln(b);
double[3] point = [0, 0, 0];
double[] test = point; // Все в по­ряд­ке
auto r = fun2(point); // Все в по­ряд­ке, те­перь r име­ет тип double[3]
}

View file

@ -0,0 +1,15 @@
import std.stdio;
void main()
{
int[4] fixed = [1, 2, 3, 4];
auto anotherFixed = fixed;
assert(anotherFixed !is fixed); // Не то же са­мое (ко­пи­ро­ва­ние по зна­че­нию)
assert(anotherFixed == fixed); // Те же дан­ные
auto dynamic = fixed[]; // По­лу­ча­ет гра­ни­цы мас­си­ва fixed
assert(dynamic is fixed);
assert(dynamic == fixed); // Ес­те­ст­вен­но
dynamic = dynamic.dup; // Соз­да­ет ко­пию
assert(dynamic !is fixed);
assert(dynamic == fixed);
}

View file

@ -0,0 +1,10 @@
import std.stdio;
void main()
{
double[2] a;
double[] b = a ~ 0.5; // При­сое­ди­нить к double[2] зна­че­ние, по­лу­чить double[]
auto c = a ~ 0.5; // То же са­мое
double[3] d = a ~ 1.5; // Все в по­ряд­ке, яв­ный за­прос мас­си­ва фик­си­ро­ван­ной дли­ны
double[5] e = a ~ d; // Все в по­ряд­ке, яв­ный за­прос мас­си­ва фик­си­ро­ван­ной дли­ны
}

View file

@ -0,0 +1,29 @@
import std.stdio, std.random;
void main()
{
int[128] someInts;
int[3] a;
assert(a == [0, 0, 0]);
int[3] b = [1, 2, 3];
assert(b == [1, 2, 3]);
int[4] c = -1;
assert(c == [-1, -1, -1, -1]);
int[1024] d = void;
double[10] array;
foreach (i; 0 .. array.length)
{
array[i] = uniform(0.0, 1.0);
}
writeln(array);
foreach (ref element; array)
{
element = uniform(0.0, 1.0);
}
writeln(array);
}

View file

@ -0,0 +1,10 @@
import std.stdio;
void main()
{
string[int] aa;
assert(aa == null);
assert(aa.length == 0);
aa = [0:"zero", 1:"not zero"];
assert(aa.length == 2);
}

View file

@ -0,0 +1,21 @@
import std.stdio;
void main()
{
// Создать ассоциативный массив с соответствием строка/строка
auto aa = [ "здравствуй":"salve", "мир":"mundi" ];
// Перезаписать значения
aa["здравствуй"] = "ciao";
aa["мир"] = "mondo";
// Создать несколько новых пар ключ–значение
aa["капуста"] = "cavolo";
aa["моцарелла"] = "mozzarella";
writeln(aa);
assert(aa["здравствуй"] == "ciao");
// Ключ "здравствуй" существует, поэтому второй аргумент игнорируется
assert(aa.get("здравствуй", "salute") == "ciao");
// Ключ "здорово" не существует, возвратить второй аргумент
assert(aa.get("здорово", "buongiorno") == "buongiorno");
}

View file

@ -0,0 +1,11 @@
import std.stdio;
void main()
{
auto a1 = [ "Jane":10.0, "Jack":20, "Bob":15 ];
auto a2 = a1; // a1 и a2 ссылаются на одни данные
a1["Bob"] = 100; // Изменяя a1,...
assert(a2["Bob"] == 100); // ...мы изменяем a2...
a2["Sam"] = 3.5; // ...и
assert(a1["Sam"] == 3.5); // наоборот
}

View file

@ -0,0 +1,11 @@
import std.stdio;
void main()
{
auto a1 = [ "Jane":10.0, "Jack":20, "Bob":15 ];
auto a2 = [ "Jane":10.0, "Jack":20, "Bob":15 ];
assert(a1 !is a2);
assert(a1 == a2);
a2["Bob"] = 18;
assert(a1 != a2);
}

View file

@ -0,0 +1,26 @@
import std.stdio;
void main()
{
auto coffeePrices = [
"французская ваниль" : 262,
"ява" : 239,
"французская обжарка" : 224
];
foreach (kind, price; coffeePrices)
{
writefln("%s стоит %s руб. за 100 г", kind, price);
}
auto gammaFunc = [-1.5:2.363, -0.5:-3.545, 0.5:1.772];
double[] keys = gammaFunc.keys;
assert(keys == [ -1.5, -0.5, 0.5 ]);
writeln(keys);
foreach (k; gammaFunc.byKey())
{
writeln(k);
}
}

View file

@ -0,0 +1,9 @@
import std.stdio;
void main()
{
int[string] aa = ["здравсвтуй":42, "мир":75];
writeln(aa);
auto bb = ["здравсвтуй":42, "мир":75];
writeln(bb);
}

View file

@ -0,0 +1,20 @@
import std.stdio;
void main()
{
string str = "Hall\u00E5, V\u00E4rld!";
foreach (c; str)
{
write('[', c, ']');
}
writeln();
foreach (dchar c; str)
{
write('[', c, ']');
}
writeln();
}

View file

@ -0,0 +1,23 @@
import std.stdio;
import std.utf;
void main()
{
{
string a = "hello";
string b = a; // Переменная b теперь тоже указывает на значение "hello"
string c = b[0 .. 4]; // Переменная c указывает на строку "hell"
// Если бы такое присваивание было разрешено, это изменило бы a, b, и c:
// a[0] = 'H';
// Конкатенация оставляет переменные b и c нетронутыми:
a = 'H' ~ a[stride(a, 0) .. $];
assert(a == "Hello" && b == "hello" && c == "hell");
}
{
string a = "Независимо от представления \u03bb стоит \u20AC20.";
wstring b = "Независимо от представления \u03bb стоит \u20AC20.";
dstring c = "Независимо от представления \u03bb стоит \u20AC20.";
writeln(a, '\n', b, '\n', c);
}
}

View file

@ -0,0 +1,15 @@
import std.stdio;
void main()
{
auto arr = [ 5, 10, 20, 30 ];
auto p = arr.ptr;
assert(*p == 5);
++p;
assert(*p == 10);
++*p;
assert(*p == 11);
p += 2;
assert(*p == 30);
assert(p - arr.ptr == 3);
}

View file

@ -0,0 +1,33 @@
bool find1(int[] haystack, int needle)
{
foreach (v; haystack)
{
if (v == needle)
{
return true;
}
}
return false;
}
int[] find2(int[] haystack, int needle)
{
while (haystack.length > 0 && haystack[0] != needle)
{
haystack = haystack[1 .. $];
}
return haystack;
}
unittest
{
int[] a = [];
assert(find2(a, 5) == []);
a = [ 1, 2, 3 ];
assert(find2(a, 0) == []);
assert(find2(a, 1).length == 3);
assert(find2(a, 2).length == 2);
assert(a[0 .. $ - find2(a, 3).length] == [ 1, 2 ]);
}

View file

@ -0,0 +1,21 @@
import std.conv;
import std.stdio : stdout;
void write(T...)(T args)
{
foreach (arg; args)
{
stdout.rawWrite(to!string(arg));
}
}
void writeln(T...)(T args)
{
write(args, '\n');
stdout.flush();
}
void main()
{
writeln(5, "здравствуй", 4.2);
}

View file

@ -0,0 +1,19 @@
import std.stdio, std.typecons;
void fun(T...)(T args)
{
// Создать кортеж, чтобы "упаковать" все аргументы в одно значение
gun(tuple(args));
}
void gun(T)(T value)
{
// Расширить кортеж и получить исходное множество параметров
writeln(value.expand);
}
void main()
{
fun(1); // Все в порядке
fun(1, 2.2); // Все в порядке
}

View file

@ -0,0 +1,16 @@
import std.stdio;
void testing(T...)(T values)
{
writeln("Переданных аргументов: ", values.length, ".");
// Обращение к каждому индексу и каждому значению
foreach (i, value; values)
{
writeln(i, ": ", typeid(T[i]), " ", value);
}
}
void main()
{
testing(5, "здравствуй", 4.2);
}

View file

@ -0,0 +1,34 @@
import core.stdc.stdarg, std.conv;
extern(C) string cToString(string type, ...)
{
va_list args_list;
va_start(args_list, type);
scope(exit) va_end(args_list);
switch (type)
{
case "int":
auto int_val = va_arg!int(args_list);
return to!string(int_val);
case "double":
auto double_val = va_arg!double(args_list);
return to!string(double_val);
case "complex":
auto re_val = va_arg!double(args_list);
auto im_val = va_arg!double(args_list);
return to!string(re_val) ~ " + " ~ to!string(im_val) ~ "i";
case "string":
return va_arg!string(args_list);
default:
assert(0, "Незнакомый тип");
}
}
unittest
{
assert(cToString("int", 5) == "5");
assert(cToString("double", 2.0) == "2");
assert(cToString("string", "Test string") == "Test string");
assert(cToString("complex", 3.5, 2.7) == "3.5 + 2.7i");
}

View file

@ -0,0 +1,69 @@
import core.stdc.stdarg, std.conv;
string dToString(string type, ...)
{
va_list args_list;
va_copy(args_list, _argptr);
scope(exit) va_end(args_list);
switch (type) {
case "int":
assert(_arguments.length == 1 && _arguments[0] is typeid(int), "Аргумент должен иметь тип int.");
auto int_val = va_arg!int(args_list);
return to!string(int_val);
case "double":
assert(_arguments.length == 1 && _arguments[0] is typeid(double), "Аргумент должен иметь тип double.");
auto double_val = va_arg!double(args_list);
return to!string(double_val);
case "complex":
assert(_arguments.length == 2 && _arguments[0] is typeid(double) && _arguments[1] is typeid(double),
"Для типа complex должны быть переданы два аргумента типа double.");
auto re_val = va_arg!double(args_list);
auto im_val = va_arg!double(args_list);
return to!string(re_val) ~ " + " ~ to!string(im_val) ~ "i";
case "string":
assert(_arguments.length == 1 && _arguments[0] is typeid(string), "Аргумент должен иметь тип string.");
return va_arg!string(args_list).idup;
default:
assert(0);
}
}
unittest
{
assert(dToString("int", 5) == "5");
assert(dToString("double", 2.0) == "2");
assert(dToString("string", "Test string") == "Test string");
assert(dToString("complex", 3.5, 2.7) == "3.5 + 2.7i");
}
import std.stdio, std.variant;
void pseudoVariadic(Variant[] vars)
{
foreach (var; vars)
{
if (var.type == typeid(string))
{
writeln("Строка: ", var.get!string);
}
else if (var.type == typeid(int))
{
writeln("Целое число: ", var.get!int);
}
else
{
writeln("Незнакомый тип: ", var.type);
}
}
}
void templatedVariadic(T...)(T args)
{
pseudoVariadic(variantArray(args));
}
void main()
{
templatedVariadic("Здравствуй, мир!", 42);
}

View file

@ -0,0 +1,32 @@
import std.stdio;
ulong fib1(uint n)
{
return n < 2 ? n : fib1(n - 1) + fib1(n - 2);
}
ulong fib2(uint n)
{
ulong iter(uint i, ulong fib_1, ulong fib_2)
{
return i == n ? fib_2 : iter(i + 1, fib_1 + fib_2, fib_1);
}
return iter(0, 1, 0);
}
pure ulong fib3(uint n)
{
ulong fib_1 = 1, fib_2 = 0;
foreach (i; 0 .. n)
{
auto t = fib_1;
fib_1 += fib_2;
fib_2 = t;
}
return fib_2;
}
void main()
{
writeln(fib3(40));
}

View file

@ -0,0 +1,16 @@
import std.stdio;
pure bool leapYear(uint y)
{
return (y % 4) == 0 && (y % 100 || (y % 400) == 0);
}
pure uint daysInYear(uint y)
{
return 365 + leapYear(y);
}
void main()
{
writeln(daysInYear(2022));
}

View file

@ -0,0 +1,11 @@
ref int bump(ref int x)
{
return ++x;
}
unittest
{
int x = 1;
bump(bump(x)); // Два увеличения на 1
assert(x == 3);
}

View file

@ -0,0 +1,16 @@
// Вы­чис­ля­ет ча­ст­ное и ос­та­ток от де­ле­ния для ар­гу­мен­тов a и b.
// Воз­вра­ща­ет ча­ст­ное по зна­че­нию, а ос­та­ток в па­ра­мет­ре rem.
int divrem(int a, int b, out int rem)
{
assert(b != 0);
rem = a % b;
return a / b;
}
unittest
{
int r;
int d = divrem(5, 2, r);
assert(d == 2 && r == 1);
}

View file

@ -0,0 +1,26 @@
void fun(int x)
{
x += 42;
}
void gun(int[] x)
{
x = [ 1, 2, 3 ];
}
void hun(int[] x)
{
x[0] = x[1];
}
unittest
{
int x = 10;
fun(x);
assert(x == 10); // Ничего не изменилось
int[] y = [ 10, 20, 30 ];
gun(y);
assert(y == [ 10, 20, 30 ]); // Ничего не изменилось
hun(y);
assert(y == [ 20, 20, 30 ]); // Изменилось!
}

View file

@ -0,0 +1,18 @@
T[] find(T)(T[] haystack, T needle)
{
while (haystack.length > 0 && haystack[0] != needle)
{
haystack = haystack[1 .. $];
}
return haystack;
}
unittest
{
// Проверка способностей к обобщению
double[] d = [ 1.5, 2.4 ];
assert(find(d, 1.0) == null);
assert(find(d, 1.5) == d);
string[] s = [ "one", "two" ];
assert(find(s, "two") == [ "two" ]);
}

View file

@ -0,0 +1,15 @@
T[] find(T, E)(T[] haystack, E needle)
if (is(typeof(haystack[0] != needle) == bool))
{
while (haystack.length > 0 && haystack[0] != needle)
{
haystack = haystack[1 .. $];
}
return haystack;
}
unittest
{
// assert(find([1, 2, 3], "Hello"));
assert(find([1, 2, 3], 1.0));
}

View file

@ -0,0 +1,27 @@
import std.stdio;
void transmogrify(uint value)
{
writeln("Вызов функции с uint: ", value);
}
void transmogrify(long value)
{
writeln("Вызов функции с long: ", value);
}
void transmogrify(T)(T value)
{
writeln("Вызов функции с T: ", value);
}
unittest
{
transmogrify(42); // Вы­зы­ва­ет transmogrify(uint)
transmogrify("hello"); // Вы­зы­ва­ет transmogrify(T), T=string
transmogrify(1.1); // Вы­зы­ва­ет transmogrify(T), T=double
// Вызов функции с uint: 42
// Вызов функции с T: hello
// Вызов функции с T: 1.1
}

View file

@ -0,0 +1,20 @@
T1[] find(T1, T2)(T1[] longer, T2[] shorter)
if (is(typeof(longer[0 .. 1] == shorter) : bool))
{
while (longer.length >= shorter.length)
{
if (longer[0 .. shorter.length] == shorter)
{
break;
}
longer = longer[1 .. $];
}
return longer;
}
unittest
{
double[] d1 = [ 6.0, 1.5, 2.25, 3 ];
float[] d2 = [ 1.5, 2.25 ];
assert(find(d1, d2) == d1[1 .. $]);
}

View file

@ -0,0 +1,24 @@
import std.stdio;
T[] find(alias pred, T)(T[] input)
if (is(typeof(pred(input[0])) == bool))
{
for (; input.length > 0; input = input[1 .. $])
{
if (pred(input[0])) break;
}
return input;
}
unittest
{
int[] a = [ 1, 2, 3, 4, -5, 3, -4 ];
// Найти первое отрицательное число
auto b = find!(function bool(int x) { return x < 0; })(a).dup;
writeln(b);
auto c = find!((x) { return x > 0; })(b);
writeln(c);
string[] str = ["one", "two", "ab", "three", "four"];
auto d = find!((x) { return x.length == 2; })(str);
writeln(d);
}

View file

@ -0,0 +1,34 @@
import std.stdio;
T[] find(alias pred, T)(T[] input)
if (is(typeof(pred(input[0])) == bool))
{
for (; input.length > 0; input = input[1 .. $])
{
if (pred(input[0])) break;
}
return input;
}
unittest
{
int zero = 0;
bool isTypeLow(int x)
{
return x < zero;
}
static bool isTypeBig(int x)
{
// Из-за static zero не будет видно внутри функции
return x > zero;
}
int[] a = [ 1, 2, 3, 4, -5, 3, -4 ];
// Найти первое отрицательное число
auto b = find!(isTypeLow)(a).dup;
writeln(b);
auto c = find!(isTypeBig)(b);
writeln(c);
}

View file

@ -0,0 +1,15 @@
import std.algorithm;
T[] delegate(T[]) finder(T)(T x)
if (is(typeof(x == x) == bool))
{
return delegate(T[] a) { return find(a, x); };
}
unittest
{
auto d = finder(5);
assert(d([1, 3, 5, 7, 9]) == [ 5, 7, 9 ]);
d = finder(10);
assert(d([1, 3, 5, 7, 9]) == []);
}

View file

@ -0,0 +1,21 @@
import std.stdio;
@property bool empty(T)(T[] a) { return a.length == 0; }
@property ref T front(T)(T[] a) { return a[0]; }
void popFront(T)(ref T[] a) { a = a[1 .. $]; }
R find(R, T)(R haystack, T needle)
if (is(typeof(haystack.front != needle) == bool))
{
while (!haystack.empty && haystack.front != needle)
{
haystack.popFront();
}
return haystack;
}
unittest
{
writeln(find([1, 2, 3], 2.0));
assert(find([1, 2, 3], 1.0));
}

View file

@ -0,0 +1,40 @@
// import std.range;
// V reduce(alias fun, V, R)(V x, R range)
// if (isInputRange!R && is(typeof(x = fun(x, range.front))))
// {
// for (; !range.empty; range.popFront())
// {
// x = fun(x, range.front);
// }
// return x;
// }
@property bool empty(T)(T[] a) { return a.length == 0; }
@property ref T front(T)(T[] a) { return a[0]; }
void popFront(T)(ref T[] a) { a = a[1 .. $]; }
V reduce(alias fun, V, R)(V x, R range)
if (is(typeof(x = fun(x, range.front)))
&& is(typeof(range.empty) == bool)
&& is(typeof(range.popFront())))
{
for (; !range.empty; range.popFront())
{
x = fun(x, range.front);
}
return x;
}
unittest
{
int[] r = [ 10, 14, 3, 5, 23 ];
// Вычислить сумму всех элементов
int sum = reduce!((a, b) { return a + b; })(0, r);
assert(sum == 55);
// Вычислить минимум
int min = reduce!((a, b) { return a < b ? a : b; })(r[0], r);
assert(min == 3);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Some files were not shown because too many files have changed in this diff Show more