«Язык программирования D 16 лет вместе с профессионалами The D Programming Language Andrei Alexandrescu HIGH T EС H Язык программирования D Андрей Александреску Серия High tech А н д р е й А лександреску Язык ...»
АНДРЕЙ
АЛЕКСАНДРЕСКУ
Язык
программирования
D
16 лет вместе
с профессионалами
The D Programming
Language
Andrei Alexandrescu
HIGH T EС H
Язык программирования
D
Андрей Александреску
Серия «High tech»
А н д р е й А лександреску Язык программирования D Перевод H. Данилиной Главный редактор А. Галунов Зав. редакцией H. Макарова Научные редакторы И. Степанов В. Пантелеев Редактор Т. Темкина Корректор О. Макарова Верстка Д. Орлова Александреску А.
Язык программирования D. - Пер. с англ. - СПб.: Символ-Плюс, 2012. c., ил.
ISBN 978-5-93286-205- D - это язык программирования, цель которого - помочь программистам спра виться с непростыми современными проблемами разработки программного обеспечения. Он создает все условия для организации взаимодействия моду лей через точные интерфейсы, поддерживает целую федерацию тесно взаимо связанных парадигм программирования (императивное, объектно-ориентированное, функциональное и метапрограммирование), обеспечивает изоляцию потоков, модульную безопасность типов, предоставляет рациональную модель памяти и многое другое.
«Язык программирования D» - это введение в D, автору которого можно до верять. Это книга в фирменном стиле Александреску - она написана нефор мальным языком, но без лишних слов и не в ущерб точности. Андрей рас сказывает о выражениях и инструкциях, о функциях, контрактах, модулях и о многом другом, что есть в языке D. В книге вы найдете полный перечень средств языка с объяснениями и наглядными примерами; описание поддержки разных парадигм программирования конкретными средствами языка D; ин формацию о том, почему в язык включено то или иное средство, и советы по их использованию; обсуждение злободневных вопросов, таких как обработка ошибок, контрактное программирование и параллельные вычисления.
Книга написана для практикующего программиста, причем она не просто зна комит с языком - это настоящий справочник полезных методик и идиом, кото рые облегчат жизнь не только программиста на D, но и программиста вообще.
ISBN 978-5-93286-205- ISBN 978-0-321-63536-5 (англ) © Издательство Символ-Плюс, Authorized translation of the English edition © 2010 Pearson Education, Inc. This translation is published and sold by permission of Pearson Education, Inc., the owner of all rights to publish and sell the same.
Все права на данное издание защищены Законодательством РФ, включая право на полное или час тичное воспроизведение в любой форме. Все товарные энаки или зарегистрированные товарные зна ки, упоминаемые в настоящем издании, являются собственностью соответствующих фирм.
Издательство «Символ-Плюс». 199034, Санкт-Петербург, 16 линия, 7, тел. (812) 380-5007, www.symbol.ru. Лицензия ЛП N 000054 от 25.12.98.
Подписано в печать 16.04.2012. Формат 70x100 У 1е»
Печать офсетная. Объем 33,5 печ. л.
Отпечатано в цифровом типографии «Буки Всдн» на оборудовании Konica MinolLi ООО «Ваш полиграфический партнер», ул. Ильмснский пр-д. д. 1, корп. Тел.: (495) 926-63-96, w w w.bukivedi.com,[email protected]. Заказ № 1298.
Оглавление О бавторе. П редисловие Уолтера Брайта..
П редисловие Скотта М ей ер са.. П редисловие научных р ед акто р о в п ер ево д а.. Введение.................... 1.3 н а к о м с т в о с я зы к о м 0
1.1. Числа и вы ражения............ 1.2. Инструкции.. . 1.3.0сновы работы сф ункциями. 1.4.Массивыиассоциативныемассивы. 1.4.1.Работаемсословарем.............. 1.4.2. Получение среза массива. Функции с обобщенными типами параметров. Тесты модулей... 1.4.3.Подсчетчастот.Лямбда-функции. 1.5.0сновны еструктуры данны х.. 1.6.Интерфейсы иклассы. 1.6.1.Больш естатистики.Наследование.................. 1.7. Значения против ссылок.......... 4.1.2.П роверкаграни ц.
Оглавление 5.2.1. Параметры и возвращаемые значения, переданные 5.6.1. Функциональные литералы 5.8.1. Так, это работает... Стоп, не должно...
5.10.1. Гомогенныефункции 5.10.2. Гетерогенные функции 5.10.3. Гетерогенные функции с переменным числом 6.4.2. Наследование - это порождение подтипа.
6.5. Инкапсуляция на уровне классов с помощью 6.6. Сдерживание расширяемости с помощью Оглавление 6.13.1. Переопределение методов в сценариях 7.1.10. Порождение подтипов в случае структур.
7.5. Параметризированные контексты (конструкция template). 8.8. Распространение квалификатора с параметра на результат.. Оглавление 11.3.2. Порядок выполнения 12.2.1. Объединение определений операторов с помощью 12.2.2. Постфиксный вариант операторов увеличения 12.10. Определение перегруженных операторов в классах.. 12.11.1. Динамическое диспетчирование с opDispatch. 13.2. Краткая история механизмов разделения данных.. 13.3. Смотри, мам, никакого разделения (по умолчанию)..... 13.6. Сопоставление по шаблону с помощью receive..... 13.11.1. Сюжет усложняется:
13.12.1. Последовательная целостность разделяемых данных.. 13.13. Синхронизация на основе блокировок через 13.14. Типизация полей в синхронизированных классах.... 13.15. Взаимоблокировки и инструкция synchronized.. 13.16. Кодирование без блокировок с помощью Андрей Александреску - автор неофициального термина «современ ный С++». Сегодня под этим понимают множ ество полезны х стилей и идей программирования на С++. Книга А лександреску «Современное проектирование на С++: обобщ енное программирование и прикладны е шаблоны проектирования» (Вильямс, 2004) полностью изм енила мето дику программирования на С ++ и оказала огромное влияние не только на непосредственную работу на С++, но и на другие язы ки и системы.
В соавторстве с Гербом Саттером А ндрей написал «Стандарты програм мирования на С++: 101 правило и рекомендация» (Вильямс, 2005).
Благодаря разработанным им многочисленным библиотекам и прило жениям, а такж е исследовательской работе в области маш инного обуче ния и обработки естественных языков, А ндрей снискал уваж ение как практиков, так и теоретиков.
С 2006 года он стал правой рукой Уолтера Брайта - автора язы ка про граммирования D и первого, кто взялся за его реализацию. И менно Андрей А лександреску предлож ил многие важные средства D и создал большую часть стандартной библиотеки D. Все это позволило ем у напи сать эту авторитетную книгу о новом языке. Ваш ингтонский универси тет присудил Андрею степень доктора философии компьютерных наук, а университет «Политехника» в Б ухаресте - степень бакалавра элект ротехники. Он такж е является научным сотрудником Facebook.
Предисловие Уолтера Брайта Когда-то в научно-фантастическом романе мне встретилась строка, где было сказано: ученый бесстраш но заглянет во врата ада, если думает, что это позволит расширить познания в интересующ ей его области.
В одной-единственной фразе заключена сущность того, что значит быть ученым. Радость открытия, ж а ж д а познания хорошо видны на видео зап исях и в работах ф изика Ричарда Ф ейнмана, чей энтузиазм заража ет и завораживает.
Не будучи сам ученым, я понимаю, что движ ет такими людьми. Мне, инженеру, тож е знакома радость творения, создания чего-то из ничего.
Одна из моих любимых книг - «Братья Райт как инженеры» Уольда1.
Это хроника пути, который ш аг за шагом прошли братья Райт, одну за другой разреш ая проблемы полета, чтобы затем отдать все эти знания созданию летательной маш ины.
Мои ранние увлечения, суть которых отражают слова с первых страниц «Руководствадля любителей фейерверков» Бринли2 - «восхищение и зачарованность всем, что горит и взрывается», —позж е выросли в желание создавать то, что работает быстрее и лучше.
Но производство мощ ны х маш ин - дорогое удовольствие. И тогда я от крыл для себя компьютеры. Чудесное и притягательное свойство ком пьютеров - это легкость, с которой можно творить. Не нуж ен ни супер завод за миллиард долларов, ни мастерская, ни даж е отвертка. Имея всего лиш ь недорогой компьютер, мож но создавать целые миры.
Вот я и начал создавать воображаемые миры на компьютере. Первым стала игра Empire: W argam e of the Century3. Компьютеры тогда были недостаточно мощны, чтобы можно было нормально играть в нее, и я за интересовался оптим изацией программ. Это привело к изучению ком пиляторов, которые генерируют код, и, естественно, к высокомерному 1 Quentin R. Wald «The Wright Brothers as Engineers: an Appraisal and Flying with the Wright Brothers, one Man’s Experience», 1999.
2 Bertrand R. Brinley «Rocket Manual for Amateurs*, Ballantine, 1968.
3 Одна из первых графических компьютерных стратегических игр, оказав шая большое влияние на дальнейшее развитие игр этого жанра, в частно сти Civilisation. См. http://www.classicempire.com/. - Прим. пер.
Предисловие Уолтера Брайта «я могу написать компилятор получш е этого». Влюбившись в язы к С, я захотел создать компилятор и для него. И за пару лет, работая непол ный день, без труда справился с этим. Затем мое внимание привлек язык Бьёрна Страуструпа С++, и я реш ил, что смогу дополнить компилятор С соответствующими возмож ностями за пару месяцев (!).
Спустя десять лет я все ещ е работал над этим. В процессе реализации я изучил язык во всех тонкостях. Д ля поддерж ки обш ирной пользова тельской базы необходимо было хорош о понимать, как воспринимают язык другие люди, и знать, что работает, а что нет. Я не могу пользо ваться чем-то и не думать, как м ож но это улучш ить. В 1999 году я ре шил воплотить свои идеи. Началось с язы ка программирования M ars.
Однако мои коллеги стали называть его D - сначала в ш утку, но потом имя прижилось. Так и появился на свет язы к программирования D.
К моменту написания этого текста язы ку D исполнилось у ж е десять лет и у него появилось новое, более значительное воплощение, которое ино гда называют D2. Вместо единственного человека, не отрывающ егося от клавиатуры, над языком D теперь трудится целое всемирное сообщ ест во разработчиков, которые заним аю тся всеми аспектами языка и под держивают экосистему библиотек и инструментов.
Сам язык (которому посвящ ена эта книга) эволюционировал от скром ных основ до очень мощного язы ка, виртуозно реш аю щ его задачи про граммирования разными способами. Насколько мне известно, в D остро умно и оригинально сочетаются несколько парадигм программирова ния: императивное, объектно-ориентированное, ф ункциональное и ме тапрограммирование.
Первое, что приходит в голову после подобного заявления: такой язы к не может быть простым. И в самом деле, D - непростой язык. Но, д у маю, не стоит судить о язы ке по его слож ности. Гораздо полезнее задать ся вопросом о том, как выглядят программные решения на этом язы ке.
Просты ли, изящны ли программы на D - или сложны и бестолковы?
Один мой коллега с богатым производственным опытом зам етил, что IDE1 - необходимый для программирования инструмент, потому что по зволяет одним щелчком мыши сгенерировать сотни строк стандартного кода. При использовании язы ка D нет острой потребности в ГОЕ, по скольку, вместо того чтобы полагаться на фокусы генерации «загото вок» разного рода «помощниками», D исключает сам у идею стандарт ных заготовок, применяя интроспекцию и собственные возмож ности генерации кода. Программист у ж е не увидит стандартный код. О при сущей программам слож ности заботится язы к, а не IDE.
Предположим, кто-то хочет написать программу в стиле объектно-ори ентированного программирования (ООП) на простом язы ке без встро енной поддержки этой парадигмы. Он м ож ет сделать это ценой у ж а с 1 IDE (Integrated Development Environment) - интегрированная среда разра ных и почти всегда напрасны х усилий. Но если более сложны й язык у ж е поддерживает ООП, писать ООП-программы легко и изящ но. Язык слож нее, а код пользователя - проще. Вот куда стоит двигаться.
Чтобы легко и изящ но писать код, реализую щ ий широкий спектр за дач, необходим язы к с поддерж кой нескольких разных парадигм про граммирования. Грамотно написанный код должен красиво смотреть ся, и, как ни странно, красивый код - это зачастую правильный код. Не знаю, чем обусловлена эта взаимосвязь, но обычно так и есть. Это как с самолетами: тот, что хорошо выглядит, как правило, и летает хорошо.
То есть средства язы ка, позволяющие выражать алгоритмы красиво, скорее всего, хорош ие средства.
Однако только простоты и изящ ества написания кода мало для того, чтобы назвать язы к программирования хорошим. Сегодня программы быстро растут в объеме, и конда этому не видно. С такими объемами для обеспечения корректности программ все менее целесообразно пола гаться на знания и опыт программиста и традиционные способы про верки работоспособности кода. Все более стоящ им каж ется подход, ко гда выявление ош ибок гарантирует маш ина. Здесь D мож ет похвастать ся множеством стратегий, применяя которые, программист получит такие гарантии. Эти средства включают контракты, безопасность памя ти, различные атрибуты ф ункций, свойство неизменности, защ иту от «угона имен» (hijack)1, ограничители области видимости, чистые функ ции, юнит-тесты и изоляцию данны х при многопоточном программи ровании.
Нет, мы не забыли о производительности! Несмотря на многочисленные высказывания о том, что вопрос быстродействия больше не актуален, и на то, что компьютеры работают в тысячу раз быстрее, чем когда я пи сал свой первый компилятор, потребность в более быстрых программах, очевидно, не снизится никогда. D - это язык для системного програм мирования. Что это значит? В двух словах, это значит, что на D можно писать операционную систему, равно как и код приложений и драйве ров устройств. С более технической точки зрения это значит, что про граммы на D имеют доступ ко всем возможностям машины. То есть м ож но использовать указатели, совмещать указатели и выполнять над ними арифметические операции, обходить систему типизации и даж е писать код прямо на ассемблере. Нет ничего, что программисту на D бы ло бы полностью недоступно. Например, реализация сборщика мусора для самого язы ка D написана полностью на D.
1 Суть проблемы следующая: программист обращается к какому-то символу (функции, классу и т..) по имени, но вследствие перегрузки функций по ти пам аргументов (из-за переопределения методов в дочерних классах) возни кает спорная ситуация, и компилятор неявно обращается к некоторому символу - возможно, не тому, который нужен программисту. Компилятор D в неоднозначных ситуациях генерирует ошибку. См. http:// dlang.org/hijack.
html. - Прим. науч.ред.
Предисловие Уолтера Брайта Минуточку. Разве такое возможно? Каким образом язы к м ож ет одно временно предоставлять и немыслимые гарантии безопасности, и не подвластные никакому контролю операции с указателями? Ответ в том, что гарантии этого типа основаны на используемы х конструкциях язы ка. Например, с помощью атрибутов ф ункций и конструкторов типов можно предотвратить ош ибки в реж им е компиляции. Контракты и ин варианты предоставляют гарантии корректности работы программы во время исполнения.
Большинство качеств D в той или иной форме когда-то у ж е появлялись в других язы ках. Взяты е по отдельности, они не оправдывают появле ние нового языка. Но их ком бинация - это больше, чем просто сум м а частей. И комбинация D позволяет ем у претендовать на звание привле кательного языка с изящ ны ми и эффективными средствами дл я реш е ния необычайно широкого круга задач программирования.
Андрей Александреску известен оригинальными идеями, сформиро вавшими основное направление программистской мысли (см. его нова торскую книгу «Современное проектирование на С++»). А ндрей примк нул к команде разработчиков язы ка D в 2006 году. Его вклад - серьез ная теоретическая база по программированию, а такж е неиссякаемый поток инновационных решений проблем программного проектирова ния. D2 сформировался в основном благодаря ему, а эта книга во многом развивалась совместно с D. Одно из замечательных свойств написанного им о D в том, что это сочинение - не простое перечисление фактов. Это ответы на вопросы, почему были выбраны те или иные проектные ре шения. Зная, по каким причинам язы к стал именно таким, гораздо лег че и быстрее понять его и начать программировать на нем.
В кпиге Андрей иллюстрирует эти причины, решая с помощью D мно жество фундаментальных задач программирования. Этим он показы вает не только как работает D, но и почему он работает, и как его ис пользовать.
Надеюсь, вы получите столько ж е удовольствия от программирования на D, сколько я получил от работы над ним. Страницы книги А н дрея просто излучают восхищ ение языком. Д умаю, вам понравится!
Предисловие Скотта Мейерса Как ни крути, С ++ невероятно успешен. Но д а ж е самые страстные по клонники язы ка не будут отрицать, что управлять этим зверем непро сто. Сложность С ++ повлияла на структуру самых популярных его пре емников —Java и C #. Оба стремились избежать сложности своего пред ш ественника, предоставив его основные возможности в более удобной для использования форме.
Снижение слож ности велось по двум главным направлениям. Одно из них - отказ от «сложных» средств языка. Например, управление памя тью вручную (единственный способ, доступный пользователям С++) было заменено «сбором мусора». Считалось, что выгоды от применения шаблонов никогда не оправдают соответствующ их затрат. Поэтому ран ние версии Java и C # не включали ничего похож его на поддержку обоб щенного программирования в С++.
Второе направление сниж ения сложности подразумевало замену «сложных» средств С ++ сходными, но более простыми для понимания конструкциями. М ножественное наследование С++ превратилось в про стое наследование плюс интерфейсы. Современные версии Java и C# поддерживаю т шаблоноподобные обобщенные классы, которые проще шаблонов С++.
Эти языки-потомки претендовали на нечто большее, чем просто менее слож но делать то ж е, что и С++. Оба определяли виртуальные машины, добавляли поддерж ку рефлексии и предоставляли обширные библио теки, позволяю щ ие многим программистам сосредоточиться не на на писании нового кода, а на «склеивании» у ж е имеющ ихся компонентов.
Результат - С-подобные язы ки для продуктивного программирования.
Если требуется быстро создать программный продукт, более или менее соответствующ ий комбинации готовых элементов - а большинство про граммного обеспечения попадает в эту категорию, - Java и C # будут лучш им выбором по сравнению с С++.
Но С++ и не предназначен для скоростной разработки - это язык для сис т емного программирования. Он создавался как альтернатива С по воз можностям «общения» с аппаратным обеспечением (таким как драйверы и встроенные системы), напрямую использующая библиотеки и струк туры данны х С (например в унаследованных системах) и работающая на пределе производительности аппаратного обеспечения. На самом де Предисловие Скотта Мейерса ле, нет ничего парадоксального в том, что узк ие места виртуальны х машин Java и C # написаны на С++. Реализация высокопроизводитель ных виртуальных маш ин - задача языка для сист емного программи рования, а не прикладного.
Цель D - стать наследником С ++ в области системного программирова ния. Как и Java с C #, D стремится избеж ать слож ности С++, поэтому он отчасти задействует те ж е техники. Сборке мусора - «добро пож ало вать», ручному управлению памятью - «до свиданья»1. Простому насле дованию и интерфейсам - да, множественному наследованию - нет. Вот и все сходство, дальш е D идет у ж е собственной дорогой.
Она начинается с выявления функциональны х изъянов С++ и их вос полнения. Текущая версия С ++ не поддерживает Ю никод, его новая версия (C++11), находящ аяся в стадии разработки, так ж е предоставля ет очень ограниченную поддерж ку этой кодировки. D поддерживает Юникод с момента своего появления. Как современный С++, так и C++Ox не предоставляют ни средства для работы с модулями (в том числе для их тестирования), ни инструментарий для реализации парадигмы кон трактного программирования, ни «безопасные» подмнож ества (где не возможны ошибки при работе с памятью). D предлагает все вышепере численное, не ж ертвуя при этом способностью генерировать высокока чественный маш инный код.
Там, где С++ одновременно и мощный, и слож ны й, D пы тается быть не менее мощным, но более простым. Лю бители шаблонного метапрограм мирования на С++ продемонстрировали, насколько важ на технология вычислений на этапе компиляции, но, для того чтобы использовать их, им пришлось прыгать через горящ ие обручи синтаксиса. D предлагает те ж е возможности, избавляя от лингвистических мучений. В С ++ вы знаете, как написать функцию, но при этом не имеете ни малейш его понятия о том, как написать соответствующ ую функцию, вычисляе мую на этапе компиляции. А в язы ке D, зная, как написать функцию, вы у ж е точно знаете, как написать ее вариант времени ком пиляции, поскольку код тот ж е самый.
Один из самых интересных моментов, где D расходится со своими coбратьями-наследниками С++, - подход к параллельным вычислениям при многопоточном программировании. Ввиду того что неверно си нхро низированный доступ к разделяемым данным («гонки за данными») это западня, угодить в которую легко, а выбраться слож но, D перевора чивает традиционные представления с ног на голову: по умолчанию данные не разделяются м еж ду потоками. По мнению разработчиков D, благодаря глубоким иерархиям кэшей в современном аппаратном обес печении память все равно зачастую реально не разделяется м еж ду ядра 1 На самом деле, тут все зависит от желания. Как и подобает языку для сис темного программирования, D позволяет вручную управлять памятью, ес ли вы действительно этого хотите.
ми и процессорами, так зачем по умолчанию предлагать разработчикам абстракцию, которая не просто фиктивна, но еще и чревата ошибками, с трудом поддаю щ имися отладке?
Все это (и не только) превращает D в достойный внимания экземпляр среди наследников С и является веским доводом к прочтению этой кни ги. Тот факт, что ее автор - А ндрей Александреску, только усиливает доводы «за». Как один из проектировщиков D, реализовавший значи тельную часть его библиотеки, Андрей знает D лучш е кого бы то ни бы ло. И, конечно, он м ож ет описать этот язык программирования, а кроме того, ещ е и объяснить, почему D стал именно таким. Актуальные сред ства язы ка были включены в него намеренно, а те, которых пока нет, отсутствуют тож е не без причины. Андрей - один из немногих, кто спо собен осветить все эти вопросы.
И освещает он их на редкость увлекательно. Посреди ненужного, каза лось бы, «лирического отступления* (которое на самом деле является станцией на пути к тому пункту назначения, куда вас хотят доставить) Андрей успокаивает: «Догадываюсь, что сейчас вы задаетесь вопросом, имеет ли все это отношение к вычислениям во время компиляции. От вет: имеет. Прошу немного терпения». Понимая, что диагностические сообщения сборщика далеко не интуитивно понятны, Андрей замечает:
«Если вы забыли написать --main, не волнуйтесь: компоновщик тут ж е витиевато напомнит вам об этом на своем родном языке —зашифрован ном клингонском1». Д аж е ссылки на другие источники у Александре ску выглядят по-особому. Вам не просто дают номер, под которым рабо та Уодлера «Доказательства —это программы» числится в списке лите ратуры, а предлагают «прочесть увлекательную монографию „Доказа тельства - это программы" Уодлера». Классический труд Фридла «Регу лярные выражения»2 не просто рекомендуется к прочтению, а «горячо рекомендуется ».
И разум еется, в этой книге о язы ке программирования много примеров исходного кода, судя по которым А ндрей - отнюдь не заурядный автор.
Вот определенный им прототип для функции поиска3:
bool find(int[] haystack, int needle);
Это книга знающ его автора об интересном языке программирования.
Уверен, вы не пож алеете, что прочли ее.
1 Клингонский язык - искусственный язык, специально придуманный для клингонов (расы воинов) - персонажей кинофильмов, сериалов, книг и ком пьютерных игр о вымышленной вселенной «Звездный путь». Существую щие естественные языки он напоминает весьма слабо. - Прим. пер.
2 Ф ридл «Регулярные выражения», 3-е издание, Символ-Плюс, 2008.
3 Если перевести идентификаторы, код примет вид: bool найти(1г^[ ] стог сена, Хотя язык D сущ ествует у ж е более десяти лет, русскоязы чны х ресур сов по нему очень мало. По сути, это несколько статей в Интернете. По этому данная книга, пож алуй, - первый источник достоверной и пол ной информации об этом языке.
Автор книги хотел отразить язы к таким, каким он «должен быть*. Н е которые аспекты языка на момент написания книги ещ е не были реали зованы в компиляторах, другие возмож ности, предоставляемые компи ляторами, напротив, не попали в книгу. Автор справедливо полагал, что читатель, ж елаю щ ий ознакомиться с возмож ностями конкретной реализации компилятора, мож ет обратиться к документации (напри мер, документация для эталонного компилятора dmd размещ ена на сай те d-program m ing-language.org). К сож алению, на момент выхода книги эта документация ещ е не была переведена на русский язык.
С другой стороны, нам бы хотелось, чтобы читатель смог найти в этой книге не только общ ее описание язы ка, но и больш ую часть информа ции, необходимой для его практического применения. П оэтому редак ция взяла на себя смелость, с согласия автора, дополнить книгу описа нием некоторых значительных аспектов язы ка, не освещ енных авто ром, объяснив причину их отсутствия в оригинале.
Кроме того, за те два года, что прош ли с момента выхода оригинальной версии этой книги, язык претерпел некоторые изменения. Например, убраны восьмеричные числовые литералы, запрещ ен неявный переход к следующей метке case конструкции switch. Эти изменения мы так ж е постарались отразить в переводе.
Следует пояснить перевод некоторых терминов.
Первый неоднозначный момент - перевод слова «character». В подавля ющем большинстве издаваемы х сейчас книг это слово переводится как «символ». М ежду тем данный перевод не вполне корректен. Со времен книги Гриса1о конструировании компиляторов в русском язы ке симво лом считается синтаксическая единица (symbol). Например, double, main 1 Грис Д. «Конструирование компиляторов для цифровых вычислительных машин». - М.: Мир, 1975.
и оператор ++ —это символы. В учебнике информатики Бауэра и Гооза символ определен как знак (литера, буква алфавита) со смыслом. Назы вать символом литеру - некорректно. Мы использовали слово «символ* в значении «symbol*, или «знак со смыслом» (например, символ перево да строки). Термин ж е «character» мы перевели как «знак». Наблюдает ся так ж е некоторый конфликт термина «знак» в смысле «буква», или «литера», и термина «знак» (sign) в математическом смысле (+ или -).
Но, как правило, из контекста понятно, в каком смысле употребляется слово «знак». Например, тип char мы называем знаковым, или литер ным типом, имея в виду, что в переменной этого типа может храниться знак алфавита, а не число со знаком. К слову сказать, в D тип char пред назначен только для хранения знака (или части знака) в кодировке UTF-8 (и, являясь интегральным типом, в математическом смысле он всегда беззнаковы й, как unsigned char в С). Д ля представления ж е одно байтного целого числа D вводит два дополнительных типа - byte (со знаком) и ubyte (без знака). То есть под «знаковым типом» мы понимаем char, wchar или dchar, а тип i n t называем целым типом, или целым типом Второй аспект - перевод слова «statem ent». Во многих книгах такие ве щи, как if, switch, while, называют операторами. В книге про D такой перевод неуместен, так как язы к предоставляет возможность перегруз ки операторов (operator overloading) +, *, % так далее для пользователь ских типов, и неизбеж на путаница с этими операторами. Поэтому мы переводим «statem ent* как «инструкция».
Напоследок несколько слов о самой книге. Эту книгу стоит прочитать всем. Если вы у ж е знакомы с D, эта книга поможет лучш е понять, поче му этот язы к устроен именно так, и научиться использовать его с наи большей отдачей. Если вы хотите узнать новый для себя язык, эта кни га - то, что вам нуж но. Поверьте, изучение D стоит потраченного време ни, и хотя этот язы к ещ е молод и находится в процессе развития, уж е сейчас его мож но использовать для решения серьезных задач. Причем это реш ение будет одновременно элегантным и эффективным.
И наконец, если вы просто ищ ете что-нибудь почитать, смело берите эту книгу. Неповторимый стиль излож ения и масса интересных фактов де лаю т чтение легким и увлекательным. Эта книга - для вас.
1 Бауэр Ф. JI., Гооз Г. «Информатика. Вводный курс: в 2-х ч.» - Пер. с нем.M.: Мир, 1990.
Обретая силу в простоте, язы к программирования порож дает красоту.
Поиск компромисса при противоречивых требованиях - слож ная зада ча, для решения которой создателю язы ка требуется не только знание теоретических принципов и практической стороны дела, но и хорош ий вкус. Проектирование язы ка программирования - это последняя сту пень мастерства в разработке программ.
D - это язык, который последовательно старается правильно действо вать в пределах выбранных им ограничений, таких как доступ систем ного уровня к вычислительным ресурсам, высокая производительность и синтаксическая простота, к которой стремятся все произош едш ие от С языки. Стараясь правильно действовать, D порой поступает традицион но - как другие языки, а порой ломает традиции с помощью свежего, инновационного решения. Иногда это приводило к пересмотру принци пов, которым, казалось, D никогда не изменит. Например, большие фрагменты программного кода, а то и целые программы, могут быть на писаны с помощью хорошо определенного, не допускающего ошибок па мяти «безопасного подмножества» D. Ценой небольшого ограничения доступа на системном уровне приобретается огромное преимущ ество при отладке программ.
D заинтересует вас, если для вас важны следую щ ие аспекты:
• П роизводит ельность. D - это язы к для системного программирова ния. Его модель памяти, несмотря на сильную типизацию, совмес тима с моделью памяти С. Ф ункции на D могут вызывать ф ункции на С, а функции на С могут использовать ф ункции D без каких-либо промежуточных преобразований.
• Выразит ельност ь. D нельзя назвать небольшим, минималистичным языком, но его удельная мощность достаточно велика. Он по зволяет определять наглядные, не требую щ ие объяснений инструк ции, точно моделирующ ие слож ны е реалии.
• «Крутящий момент». Любой лихач-«самоделкин» ск аж ет вам, что мощность еще не все - было бы где ее применить. На одних язы ках лучш е всего пиш утся маленькие программы. Синтаксические и з лишества других оправдываются только начиная с определенного объема программ. D одинаково эффективно помогает справляться и с короткими сценариями, и с большими программами, и для него отнюдь не редкость целый проект, органично вырастающий из про стенького скрипта в единственном файле.
• П араллельн ы е вы числения. П одход к параллельным вычислениям несомненное отличие D от похож их языков, отраж аю щ ее разрыв ме ж д у современными аппаратными решениями и архитектурой ком пьютеров прошлого. D покончил с проклятьем неявного разделения памяти (хотя и допускает статически проверенное, явно заданное разделение) и поощ ряет независимые потоки, которые «общаются»
друг с другом посредством сообщ ений.
• Обобщенное программирование. Идея обобщенного кода, манипули рующего другим кодом, была впервые реализована в мощных макро сах Лиспа, затем в шаблонах С++, обобщенных классах Java и схожих конструкциях др уги х языков. D такж е предлагает невероятно мощ ные механизмы обобщ енного и порождающ его программирования.
• Э клект и зм. D подразумевает, что к аж дая парадигма программиро вания ориентирована на свою задачу разработки. Поэтому он пред полагает высокоинтегрированный объединенный стиль программи рования, а не Единственно Верный Подход.
• «Это мои принципы. А если они вам не нравят ся, то у меня есть и другие>1. D старается всегда следовать своим принципам устройства язы ка. Иногда они идут вразрез с соображениями сложности реали зации и трудностей использования и, главное, с человеческой приро дой, которая не всегда находит скрытую логику здравой и интуитив но понятной. В таких случаях все языки полагаются на собственное бесконечно субъективное понимание баланса, гибкости и - особен но - хорошего вкуса. На мой взгляд, D как минимум неплохо смот рится на фоне других языков, разработчикам которых приходилось принимать реш ения того ж е плана.
Кому адресована эта книга Предполагается, что вы программист. То есть знаете, как решить типич ную задачу программирования с помощью языка, на котором вы пише те. Н еважно, какой конкретно это язык. Если вы знаете один из языков, произош едш их от А лгола (С, С++, Java или C #), то будете иметь некото рое преимущ ество перед другими читателями - синтаксис сразу пока ж ется знакомым, а риск встретить «мнимых друзей» (одинаковый син таксис с разной семантикой) будет минимальным. (Особенно это касает ся случаев, когда вы вставляете кусок кода на С в D -файл. Он либо скомпилируется и будет делать то ж е самое, либо не скомпилируется вообще.) Книга, знаком ящ ая с языком, была бы скучной и неполной, если бы не объясняла, зачем в язы к включены те или иные средства, и не показы 1 Афоризм американского комика Граучо Маркса. - Прим. ред.
Введение вала наиболее рациональные пути использования этих средств для ре шения конкретных задач. Эта книга логично обосновывает добавление в язык всех неочевидных средств и старается показать, почему не были выбраны лучш ие, на первый взгляд, проектные реш ения. Некоторые альтернативы требуют необоснованно высоких затрат на реализацию, плохо взаимодействуют с другими средствами язы ка, имеющ ими боль ше прав на существование, обладают скрытыми недостатками, кото рые не видны в коротких и простых примерах, или просто недостаточно мощны для того, чтобы что-то значить. В аж н ее всего то, что разработ чики языка могут совершать ош ибки так ж е, как и все остальные лю ди, поэтому, пож алуй, лучш ие проектные реш ения - те, которых ни кто никогда не видел.
Структура книги Глава 1 - это бодрящ ая прогулка с целью знакомства с основами язы ка.
На этом этапе не все детали полностью видны, но вы см ож ете почувст вовать язык и научиться писать на нем простейш ие программы. Главы 2 и 3 - необходимое перечисление вы ражений и инструкций язы ка со ответственно. Я попытался скрасить неизбеж ную монотонность по дробного описания, подчеркнув детали, отличающ ие D от др уги х тра диционных языков. Надеюсь, вам будет легко читать эти главы подряд, а такж е возвращаться к ним за справкой. Таблицы в конце этих глав — это «шпаргалки», интуитивно понятные краткие справочники.
В главе 4 описаны встроенные типы: массивы, ассоциативные массивы и строки. Массив можно представить себе как указатель с аварийным выключателем. Массивы в D - это средство, обеспечивающ ее безопас ность памяти и позволяющее вам наслаж даться языком. Строки - это массивы знаков Ю никода в кодировке UTF. П овсеместная поддерж ка Юникода в языке и стандартной библиотеке позволяет корректно и эф фективно обрабатывать строки.
Прочитав первые четыре главы, вы см ож ете на основе предоставляе мых языком абстракций писать простые программы вроде сценариев.
П оследующие главы знакомят с абстракциями-блоками. Глава 5 объ единяет описание различны х видов функций: параметризированны х функций реж им а компиляции (шаблоны функций) и ф ункций, вычис ляемых во время компиляции. Обычно такие вопросы рассматривают ся в более *продвинутых» главах, но в D работать с этими средствами достаточно просто, так что раннее знакомство с ними оправданно.
В главе 6 обсуж дается объектно-ориентированное программирование на основе классов. Как и раньше, здесь органично и комплексно подается информация о параметризированных классах. Глава 7 знакомит с до полнительными типами, в частности с типом struct, позволяющ им, обычно совместно с классами, эффективно создавать абстракции.
Следующие четыре главы описывают довольно специализированные, обособленные средства. Глава 8 посвящена квалификаторам типов. Ква лификаторы надеж но гарантируют от ошибок, что одинаково ценно как для однопоточных, так и для многопоточных приложений. В главе рассмотрены модели обработки исключительных ситуаций. В главе представлен мощный инструментарий D, реализующ ий парадигму кон трактного программирования. Этот материал намеренно вынесен в от дельную главу (а не включен в главу 9) в попытке развеять миф о том, что обработка ошибок и контрактное программирование - практически одно и то ж е. В главе 10 как раз и объясняется, почему это не так.
В главе 11 вы найдете информацию и рекомендации по построению боль ш и х программ из компонентов, а такж е небольшой обзор стандартной библиотеки D. В главе 12 рассмотрены вопросы перегрузки операторов, без которой серьезно пострадали бы многие абстракции, например ком плексные числа. Наконец, в главе 13 освещен оригинальный подход D к многопоточному программированию.
Краткая история Как бы сентиментально это ни звучало, D - дитя любви. Когда-то в 1990-х Уолтер Брайт, автор компиляторов для С и С++, решил, что больше не хо чет работать над ними, и задался целью определить язык, каким, по его мнению, «он должен быть». Многие из нас в тот или иной момент начина ют мечтать об определении Правильного Языка; к счастью, Уолтер уже обладал значительной частью инфраструктуры: генератором кода (backend), компоновщиком, а главное —широчайшим опытом построения язы ковых процессоров. Благодаря этому опыту перед Уолтером открылась интересная перспектива. По какому-то таинственному закону природы плохо спроектированная функциональность языка проявляется в логи чески запутанной реализации компилятора, как отвратительный харак тер Дориана Грея проявлялся на его портрете. Проектируя свой новый язык, Уолтер планомерно старался избежать таких патологий.
Едва зарож даю щ ийся тогда язы к был схож по духу с С++, поэтому про граммисты называли его просто D, несмотря на первоначальную попыт ку Уолтера даровать ем у титул «Марса*. По причинам, которые вскоре станут очевидными, назовем этот язы к D1. Страсть и упорство, с кото рыми Уолтер работал над D1 несколько лет, привлекали все больше еди номышленников. К 2 0 0 6 году D1 достиг уровня сильного языка, техни чески способного на равных соперничать с такими уж е признанными язы ками, как Java и С++. Но к тому времени уж е было ясно, что D1 ни когда не станет популярным, поскольку, в отличие от других языков, он не обладал функциональной индивидуальностью, оправдывавшей его сущ ествование. И тогда Уолтер совершил дерзкий маневр: решив пред ставить D1 в качестве этакой первой сырой версии, он перевел его в ре ж и м поддерж ки и приступил к разработке нового проекта - второй ите рации язы ка, не обязанной поддерживать обратную совместимость.
Введение Пользователи текущ ей версии D1 по-преж нему выигрывали от исправ ления ошибок, но никаких новых возмож ностей D1 не предоставлял.
Реализовать определение наилучш его языка было суж дено язы ку D2, который я и называю просто D.
Маневр удался. Первая итерация показала, что достойно внимания, а чего следует избегать. Кроме того, мож но было не спешить с рекламой нового языка - новые члены сообщ ества могли спокойно работать со ста бильной, активно используемой версией D1. Поскольку процесс разра ботки не был ограничен ни обратной совместимостью, ни сроками, м ож но было спокойно оценить альтернативы развития проекта и выработать правильное направление. Чтобы ещ е больше облегчить разработку, Уол тер призвал на помощь коллег, в том числе Бартоша Милевски и меня.
Важные решения, касающиеся взглядов D на неизменяемость, обобщ ен ное и функциональное программирование, параллельные вычисления, безопасность и многое другое, мы принимали в долгих ож ивленны х ди с куссиях на троих в одной из кофеен Киркленда (штат Вашингтон).
Тем временем D явно перерос свое прозвищ е «улучшенный С++», пре вратившись в мощный многофункциональный язы к, вполне способ ный оставить без работы как язы ки для системного и прикладного (промышленного) программирования, так и язы ки сценариев. Остава лась одна проблема: весь этот рост и все усовершенствование прош ли никем не замеченными; подходы D к программированию были доку ментированы очень слабо.
Книга, которая сейчас перед вами, - попытка восполнить это уп ущ е ние. Надеюсь, читать ее вам будет так ж е приятно, как мне - писать.
Благодарности У языка D было столько разработчиков, что я и не пытаюсь перечис лить их всех. Особо выделяются участники новостной группы d i g i t a l mars.D из сети U senet. Эта группа была для нас одновременно и рупором, и полигоном для испытаний, куда мы выносили на суд свои проектные решения. Кроме того, ребята из digitalmars.D сгенерировали множ ество идей по улучшению языка.
В разработке эталонной реализации компилятора dmd1 Уолтеру помога ло сообщество, особенно Шон Келли (Sean Kelly) и Дон Клагстон (Don Clugston). Шон переписал и усовершенствовал стандартную библиотеку, подключаемую во время исполнения (включая «сборщик мусора»). Кро ме того, Келли стал автором основной части реализации библиотеки, от вечающей за параллельные вычисления. Он мастер своего дела, а зна чит, если в ваших параллельных вычислениях появляются ош ибки, то 1 Название компилятора языка D dm расшифровывается как Digital Mars D.
Digital Mars - организация, которая занимается разработкой этого компи лятора. - Прим. пер.
они, увы, скорее всего, ваши, а не его. Дон - эксперт в математике вооб ще и во всех аспектах дробны х вычислений в частности. Его огромный труд позволил поднять численные примитивы D на небывалую высоту.
Кроме того, Дон до предела использовал способности D по генерирова нию кода. К ак только код эталонной реализации был открыт для широ кого доступа, Дон не устоял перед соблазном добавить в него что-то свое. Вот так он и занял второе место среди разработчиков компилято ра dmd. И Ш он, и Дон проявляли инициативу, выдвигая предложения по усовершенствованию специф икации D на протяжении всего процес са разработки. П оследнее (но не по значению) их достоинство в том, что они чумовые хакеры. С ними очень приятно общаться как в жизни, так и виртуально. Не знаю, чем стал бы язык без них.
Что касается этой книги, я бы хотел сердечно поблагодарить всех рецен зентов за отзывчивость, с которой они взялись за эту сложную и небла годарную работу. Б ез них эта книга не стала бы тем, что она представля ет собой сейчас (так что если она вам не нравится, пусть вас утешит то, что она могла быть гораздо хуж е). Поэтому позвольте мне выразить бла годарность А лехан дро А рагону (Alejandro Arag6n), Биллу Бакстеру (B ill B axter), Кевину Билеру (K evin Bealer), Тревису Бочеру (Travis Boucher), М айку К асиндж ино (M ike Casinghino), Альваро Кастро Кастилья (Alvaro Castro C astilla), Ричарду Чангу (Richard Chang), Дону Клагстону, Стефану Д илли (Stephan D illy), Кариму Филали (Karim Filali), М ишелю Ф ортину (M ichel Fortin), Д эвиду Х елду (David В. Held), Мише лю Х елвенш тейну (M ichiel H elvensteijn), Бернарду Хельеру (Bernard Helyer), Д ж ей сон у Х аузу (Jason House), Сэму Ху (Sam Hu), Томасу Хью му (Thomas Hume), Грэму Д ж ек у (Graham St. Jack), Роберту Ж аку (Ro b ert Jacques), Кристиану К эмму (C hristian Kamm), Дэниелу Кипу (Daniel Keep), Марку К егелу (Mark Kegel), Ш ону Келли, Максу Хесину (Мах K hesin), Симену Х ьеросу (Sim en Kjaeras), Коди Кёнингеру (Cody Koeninger), Д енису К ороскину (D enis Koroskin), JIapcy Кюллингстаду (Lars K yllingstad), Игорю Л есику (Igor Lesik), Евгению Летучему (Eugene Letuchy), П елле М анссону (Pelle Mansson), Миуре М асахиро (Miura Masahiro), Тим у Мэтьюсу (Tim M atthews), Скотту Мейерсу, Бартошу Милевски, Ф авзи М охамеду (Fawzi Mohamed), Эллери Ньюкамеру (Ellery New comer), Эрику Н иблеру (Eric Niebler), Майку Паркеру (Mike Parker), Д ереку П арнеллу (Derek Parnell), Д ж ерем и Пеллетье (Jeremie Pelletier), Пабло Риполлесу (РаЫо R ipolles), Брэду Робертсу (Brad Roberts), Майк л у Р и н н у (M ichael Rynn), Фою Сава (Foy Savas), Кристофу Шардту (C hristof Schardt), Стиву Ш вайхофферу (Steve Schveighoffer), Бенджа м ину Ш ропширу (Benjam in Shropshire), Дэвиду Симше (David Simcha), Томаш у Стаховяку (Tomasz Stachowiak), Роберту Стюарту (Robert Stew art), К нуту Эрику Тайгену (K nut Erik Teigen), Кристиану Влащану (C ristian V lasceanu) и Леору Золм ану (Leor Zolman).
Вы ведь знаете, с чего обычно начинают, так что без л иш них слов:
void main() { В зависимости от того, какие ещ е языки вы знаете, у вас мож ет возник нуть ощущение дежавю, чувство легкой благодарности за простоту, а можеу, и легкого разочарования из-за того, что D не пошел по стопам скрип товых языков, разрешающих использовать «корневые» (top-level) ин струкции. (Такие инструкции побуж даю т вводить глобальные перемен ные, которые по мере роста программы превращаются в головную боль;
на самом деле, D позволяет исполнять код не только внутри, но и вне функции main, хотя и более организованно.) Самые въедливые будут ра ды узнать, что void main - это эквивалент ф ункции i nt main, возвращ аю щей операционной системе «успех» (код 0) при успеш ном окончании ее выполнения.
Но не будем забегать вперед. Т радиционная программа типа «Hello, worldU («Здравствуй, мир!») - вовсе не повод для обсуж дения возм ож ностей языка. Она здесь для того, чтобы помочь вам начать писать и за пускать программы на этом язы ке. Если у вас нет никакой IDE, кото рая выполнит за вас сборку программы, то самый простой способ - это командная строка. Напечатав приведенный код и сохранив его в файле с именем, скаж ем, hello.d, запустите консоль и введите следую щ ие ко манды:
$./h ello H e llo, world!
Знаком $ обозначено приглаш ение консоли вашей ОС (это может быть с:\Путь\К\Папке> в W indow s или /путь/к/каталогу% в системах семейства U N IX, таких как OSX, L inux, Cygwin). Применив пару известных вам приемов систем-фу, вы см ож ете добиться автоматической компиляции программы при ее запуске. Пользователи W indows, вероятно, захотят привязать программу rdmd.exe (которая устанавливается вместе с ком пилятором D) к команде В ы п о л н и т ь. U N IX -подобные системы поддержи вают запуск скриптов в нотации «shebang»1. D понимает такой синтак сис: добавление строки в самое начало программы в файле hello.d позволяет компилировать ее автоматически перед исполнением. Внеся это изменение, просто введи те в командной строке:
(chmod нуж н о ввести только один раз).
Д ля всех операционных систем справедливо следующее: программа rdmd достаточно «умна», для того чтобы кэшировать сгенерированное прило ж ен ие. Так что фактически компиляция выполняется только после из менения исходного кода программы, а не при каж дом запуске. Эта осо бенность в сочетании с высокой скоростью самого компилятора позво ляет экономить время на зап уск ах программы м еж ду внесением в нее изменений, что одинаково полезно как при разработке больших систем, так и при написании маленьких скриптов.
Программа hello.d начинается с инструкции которая предписывает компилятору найти модуль с именем std.stdio и сделать его символы доступны ми для использования. Инструкция import напоминает препроцессорную директиву #include, которую мож но встретить в синтаксисе С и С++, но семантически она ближ е команде import язы ка Python: никакой вставки текста подключаемого модуля в текст основной программы не происходит - выполняется только про стое расш ирение таблицы символов. Если повторно применить инструк цию import к тому ж е файлу, ничего не произойдет.
По давней традиции С программа на D представляет собой набор опре делений, рассредоточенный по множ еству файлов. В числе прочего эти определения могут обозначать типы, функции, данные. В нашей первой «Shebang» (от shell bang: shell - консоль, bang - восклицательный знак), или «shabang» (# - sharp) - обозначение пути к компилятору или интерпрета тору в виде й!/путь/к/программе. - Прим. пер.
1.1. Числа и выражения программе определена функция main. Она не принимает никаких аргу ментов и ничего не возвращает, что, по сути, и означает слово void. При выполнении main программа вызывает функцию writeln (разум еется, предусмотрительно определенную в модуле std.stdio), передавая ей стро ковую константу в качестве аргумента. Суффикс ln указывает на то, что writeln добавляет к выводимому тексту знак перевода строки.
Следующие разделы - это стремительная поездка по Дибургу. Н еболь шие показательные программы даю т общ ее представление о язы ке. Ос новная цель повествования на данном этапе - обрисовать общ ую карти ну, а не дать ряд педантичных определений. П озж е все аспекты язы ка будут рассмотрены с долж ны м вниманием - в деталях.
1.1. Числа и выражения Интересовались ли вы когда-нибудь ростом иностранцев? Д авайте на пишем простую программу, которая переводит наиболее распростра ненные значения роста в ф утах и дю йм ах в сантиметры.
Рассчитать значения роста в сантиметрах для заданного диапазона значений в футах и дюймах В результате выполнения программы будет напечатан аккуратны й спи Инструкция foreach (feet; 5..7) {...} - это цикл, где определена целочис ленная переменная feet, с которой последовательно связываются значе ния 5 и 6 (значение 7 она не принимает, так как интервал открыт справа).
Как и Java, С ++ и C #, D поддерживает /* многострочные комментарии */ и / / однострочные комментарии (и, кроме того, документирующ ие коммен тарии, о которых позж е). Еще одна интересная деталь нашей малень кой программы - способ объявления данны х. Во-первых, введены две константы:
immutable inchesPerFoot = 12;
immutable cmPerInch = 2.54;
Константы, значения которых никогда не изменятся, определяются с помощью ключевого слова immutable. Как и переменные, константы не требуют явного задания типа: тип задается значением, которым ини циализируется константа или переменная. В данном случае литерал говорит компилятору о том, что inchesPerFoot —это целочисленная кон станта (обозначается в D с помощью знакомого int); точно так ж е лите рал 2.54 заставляет cmPerInch стать константой с плавающей запятой (типа double). Д алее мы обнаруживаем те ж е магические способности у определений f e et и inches: они выглядят как «обычные» переменные, но безо всяких «украшений», свидетельствующ их о каком-либо типе.
Это не делает программу менее безопасной по сравнению с той, где типы переменных и констант заданы явно:
immutable int inchesPerFoot = 12;
immutable double cmPerInch = 2.54;
и так дал ее —только меньше лиш него. Компилятор разрешает не ука зывать тип явно только в случае, когда можно недвусмысленно опреде лить его по контексту. Раз у ж заш ла речь о типах, давайте остановимся и посмотрим, какие числовые типы нам доступны.
Ц елые типы со знаком в порядке возрастания размера: byte, short, int и long, заним аю щ ие 8, 16, 32 и 64 бита соответственно. У каждого из эти х типов есть «двойник» без знака того ж е размера, названный в соот ветствии с простым правилом: ubyte, ushort, uint и ulong. (Здесь нет мо дификатора unsigned, как в С). Типы с плавающей запятой: f loat (32-битное число одинарной точности в формате IEEE 754), double (64-битное в формате IEEE 754) и r eal (занимает столько, сколько позволяют реги стры, предназначенны е для хранения чисел с плавающей запятой, но не меньше 64 бит; например, на компьютерах фирмы Intel real - это так называемое расш иренное 79-битное число двойной точности в формате Вернемся к наш им целым числам. Литералы, такие как 42, подходят под определение любого числового типа, но заметим, что компилятор проверяет, достаточно ли вместителен «целевой» тип для этого значе ния. П оэтому определение immutable byte inchesPerFoot = 12;
1.1. Числа и выражения ничем не хуж е аналогичного без byte, поскольку 12 мож но с таким ж е успехом представить 8 битами, а не 32. По умолчанию, если вывод о «це левом» типе делается по числу (как в программе-примере), целочислен ные константы «воспринимаются» как in t, а дробные - как double.
Вы можете построить множ ество выражений на D, используя эти типы, арифметические операторы и функции. Операторы и их приоритеты сходны с теми, что можно найти в языках-собратьях D: +, -, *, / и %для базовых арифметических операций, ==, !=,, = для сравнений, fun(argument1, argument2) для вызовов функций и т.д.
Вернемся к нашей программе перевода дюймов в сантиметры и отм е тим две достойные внимания детали вызова ф ункции writeln. Первая:
во writeln передаются 5 аргументов (а не один, как в той программе, что установила контакт м еж ду вами и миром D). Ф ункция writeln очень похожа на средства ввода-вывода, встречающ иеся в язы к ах Паскаль (writeln), С (printf) и С++ (cout). Все они (включая writeln и з D) принима ют переменное число аргументов (так называемые ф ункции с перемен ным числом аргументов). Однако в D пользователи могут определять собственные функции с переменным числом аргументов (чего нет в П ас кале), которые всегда типизированы (в отличие от С), без излиш него пе реопределения операторов (как это сделано в С++). Вторая деталь: наш вызов writeln неуклю ж е сваливает в кучу информацию о форматирова нии и форматируемые данные. Обычно желательно отделять данны е от представления. Поэтому давайте используем специальную функцию writefln, осущ ествляющ ую форматированный вывод:
writefln("%s' %s' ' \t%s", fee t, inches, (feet * inchesPerFoot + inches) * cmPerInch) ;
По-новому организованный вызов дает тот ж е вывод, но первый аргу мент функции writefln полностью описывает формат представления. Со знака %начинаются спецификаторы формата (по аналогии с ф ункцией pr in tf из С): например % - для целы х чисел, %f - для чисел с плаваю щей запятой и %s —для строк.
Если вы использовали p r i n t f преж де, то могли бы почувствовать себя как дома, когда б не маленькая особенность: мы ведь выводим значения переменных типа int и double - как ж е получилось, что и те и другие описаны с помощью спецификатора %s, обычно применяемого для выво да строк? Ответ прост. Средства D для работы с переменным количест вом аргументов дают wri tefln доступ к информации об исходны х типах переданных аргументов. Благодаря такому подходу программа получа ет ряд преимуществ: 1) значение %s мож ет быть расш ирено до «строко вого представления по умолчанию для типа переданного аргумента»
и 2) если не удалось сопоставить спецификатор формата с типами пере данных аргументов, вы получите ош ибку в чистом виде, а не загадоч ное поведение, присущ ее вызовам p r i n t f с неверно заданны м форматом (не говоря уж е о подрыве безопасности, возможном при вызове p r i n t f с непроверяемыми заранее форматирующ ими строками).
1.2. Инструкции В язы ке D, как и в др уги х родственных ему язы ках, любое выражение, после которого стоит точка с запятой, - это инструкция (например в программе «Hello, world!» сразу после вызова writ eln есть ;). Действие инструкции сводится к вычислению выражения.
D - член семейства с фигурны ми скобками и с блочной областью види мости». Это означает, что вы мож ете объединять несколько команд в од ну, помещ ая и х в { и }, что порой обязательно, например при желании сделать сразу несколько вещей в цикле foreach. В случае единственной команды вы вправе смело опустить фигурные скобки. На самом деле, весь наш двойной цикл, вычисляющ ий значения роста, можно перепи У пропуска фигурны х скобок для одиночных инструкций есть как пре имущество (более короткий код), так и недостаток - редактирование ко да становится более утомительным (в процессе отладки придется пово зиться с инструкциями, то добавляя, то удаляя скобки). Когда речь захо дит о правилах расстановки отступов и фигурных скобок, мнения силь но расходятся. На самом деле, пока вы последовательны в своем выборе, все это не так важно, как мож ет показаться. В качестве доказательства:
стиль, предлагаемы й в этой книге (обязательное заключение в опера торные скобки д а ж е одиночных инструкций, открывающая скобка на одной строке с соответствующ им оператором, закрывающие скобки на отдельны х строках), по типографским причинам отличается от реально применяемого автором. А раз он мог спокойно это пережить, не превра тившись в оборотня, то и любой сможет.
Благодаря язы ку P ython стал популярен иной способ отражения блоч ной структуры программы - с помощью отступов (чудесное воплоще ние принципа «форма соответствует содержанию»). Для программистов на други х язы ках утверж дение, что пробел имеет значение, - всего лиш ь нелепая фраза, но для тех, кто пишет на Python, это зарок. D обыч но игнорирует пробелы, но он разработан с прицелом на легкость син таксического разбора (т. e. чтобы при разборе не приходилось выяснять значения символов). А это подразумевает, что в рамках скромного «ком натного» проекта м ож но реализовать простой препроцессор, позволяю щ ий использовать для выделения блоков инструкций отступы (как в P ython) без каких-либо неудобств во время компиляции, исполнения и отладки программ.
Кроме того, вам дол ж н а быть хорошо знакома инструкция if:
i f ('выражениеинструкция,> e l s e 1.3. Основы работы с функциями Чисто теоретический вывод, известный как принцип структурного про граммирования [10], гласит, что все алгоритмы можно реализовать с по мощью составных инструкций, if-проверок и циклов а-ля for и foreach.
Разумеется, любой адекватный язы к (как и D) предлагает гораздо боль ше, но мы пока постановим, что с нас довольно и этих инструкций, и двинемся дальше.
1.3. Основы работы с функциями Оставим пока в стороне обязательное определение функции main и по смотрим, как определяются другие ф ункции на D. Определение ф унк ции соответствует модели, характерной и для других Алгол-подобных языков: сначала пиш ется возвращаемый тип, потом имя ф ункции и, наконец, заключенный в круглые скобки список формальных аргумен тов, разделенных запятыми. Например, определение ф ункции с им е нем pow, которая принимает значения типа double и in t, а возвращает double, записывается так:
double pow(double base, int exponent) { Каждый параметр ф ункции (base и exponent в данном примере) кроме типа может иметь необязательный класс пам ят и (storage class), опре деляющий способ передачи аргумента в функцию при ее вызове1.
По умолчанию аргументы передаются в pow по значению. Если перед типом параметра указан класс памяти ref, то параметр привязывается напрямую к входному аргументу, так что изменение параметра непо средственно отраж ается на значении, полученном извне. Например:
import std.stdio;
void fun(ref uint x, double у) { foreach (word; words) { 1 Этот файл содержит текст пьесы «Гамлет». - Прим. пер.
1.4. Массивы и ассоциативные массивы writefln(''%6u\t%s" freqs[word], word);
Свойство. keys позволяет получить только ключи ассоциативного мас сива freqs в виде массива строк, под который выделяется новая область памяти. Этого не избеж ать, ведь нам требуется делать перестановки.
sort!((a, b) { return freqs[a] > freqs[b]; })(words);
который соответствует недавно рассмотренной нотации:
sort'.('аргументы времени компиляции>)();
Взяв текст, заключенный в первые круглые скобки —!(...), - мы получим форму записи, напоминающ ую незаконченную ф ункцию - как будто ее автор забыл о типах параметров, возвращаемом типе и о самом им е (а, b) { return freqs[a] > freqs[b]; } Это лямбда-функция - небольшая анонимная ф ункция, которая обыч но создается для того, чтобы потом передавать ее другим ф ункциям в качестве аргумента. Л ямбда-функции используются постоянно и по всеместно, поэтому разработчики D сделали все возмож ное, чтобы и з бавить программиста от синтаксической нагрузки, преж де неизбеж ной при определении таких функций: типы параметров и возвращ аемый тип выясняются из контекста. Это действительно имеет смысл, потому что тело лямбда-функции по определению там, где нуж н о автору, чита телю и компилятору, то есть здесь нет места разночтениям и принципы модульности не нарушаются.
В связи с лямбда-функцией, определенной в этом примере, стоит уп о мянуть еще одну деталь. Л ям бда-ф ункция осущ ествляет доступ к пере менной freqs, локальной переменной ф ункции main, а значит, лямбдафункция не является ни глобальной, ни статической. Это больше напо минает подход Лиспа, а не С, и позволяет работать с очень мощ ными лямбда-конструкциями. И хотя обычно за такое преимущ ество прихо дится платить неявными вызовами ф ункций во время исполнения про граммы, D гарантирует отсутствие таких вызовов (и, следовательно, ничем не ограниченные возмож ности реализации инлайнинга).
Вывод измененной программы:
Что и ожидалось: самые часто употребляемые слова набрали больше всего «очков». Н астораж ивает лиш ь «Наш»1. Это слово в пьесе вовсе не отраж ает кулинарные предпочтения героев. «Наш» - всего лишь со кращ ение от *Hamlet» (Гамлет), которым помечена каж дая из его реп лик. Явно у него был повод высказаться 358 раз - больше, чем любой другой герой пьесы. Д алее по списку следует король - всего 116 реплик, меньше трети сказанного Гамлетом. А Офелия с ее 58 репликами - про сто молчунья.
1.5. Основные структуры данных Раз у ж мы взялись за «Гамлета», проанализируем этот текст чуть глуб ж е. Например, соберем кое-какую информацию о главных героях: сколь ко всего слов было произнесено каж ды м персонажем и насколько богат его (ее) словарный запас. Д ля этого с каж ды м действующ им лицом по надобится связать несколько фактов. Чтобы сосредоточить эту инфор мацию в одном месте, определим такую структуру данных:
В язы ке D понятия структуры (struct) и классы (class) четко разделены.
С точки зрения удобства они во многом схож и, но устанавливают раз ные правила: структуры - это типы значений, а классы были задуманы для реализации динамического полиморфизма, поэтому экземпляры классов могут быть доступны исключительно по ссылке. Упразднены связанное с этим непонимание, ош ибки при копировании экземпляров классов потомков в переменные классов-предков и комментарии а-ля / / Нет! Н наследуй!. Разрабатывая тип, вы с самого начала должны ре шить, будет ли это мономорфный тип-значение или полиморфная ссыл ка. Общеизвестно, что С ++ разреш ает определять типы, принадлеж ность которых к тому или иному разряду неочевидна, но эти типы ред ко использую тся и чреваты ош ибками. В целом достаточно оснований сознательно отказаться от них.
В наш ем случае требуется просто собрать немного данны х, и мы не пла нируем использовать полиморфные типы, поэтому тип s t r uc t - хоро ш ий выбор. Теперь определим ассоциативный массив, отображающий имена персонаж ей на дополнительную информацию о них (значения типа PersonaData):
PersonaData[string] info;
1.5. Основные структуры данных Все, что нам требуется, - это правильно заполнить info данны м и из hamlet.txt. Придется немного потрудиться: реплика героя м ож ет про стираться на несколько строк, и нам понадобится простая обработка, сцепляющая эти ф изические строки в одну логическую. Чтобы понять, как это сделать, обратимся к небольш ому фрагменту файла hamlet.txt, познаково представленному н и ж е (предш ествующ ие тексту пробелы для наглядности отображ аю тся видимыми знаками):
До сих пор гадают, не было ли истинной причиной гибели Полония зло употребление инструкцией goto. Но нас больше интересует другое: за метим, что реплике каж дого персонаж а предш ествуют ровно два пробе ла, за ними следует имя, после которого стоит точка, потом пробел, за которым наконец-то начинается само высказывание. Если логическая строка занимает несколько ф изических строк, то каж дая следую щ ая строка всегда начинается с четырех пробелов. М ожно осущ ествить про стое сопоставление шаблону, воспользовавшись регулярными вы раж е ниями (для работы с которыми предназначен модуль std.regex), но мы хотим научиться работать с массивами, поэтому выполним сопоставле ние «вручную». Призовем на помощь лиш ь логическую ф ункцию a.startsWith(b), определенную в модуле std.algorithm, которая сообщ ает, Управляющая функция main читает входную последовательность ф и зических строк, сцепляет их в логические строки (игнорируя все, что не подходит под наш шаблон), передает полученны е полные реплики в функцию-накопитель и в конце печатает требуемую информацию:
i mp or ts t d. a lgo r i th m, std.conv, st d. ct ype, std. regex, / / Накапливает информацию о главных героях PersonaData[string] info;
addParagraph(currentParagraph, info);
/ / Закончили, теперь напечатаем собранную информацию Зная, как работают массивы, мы без труда читаем этот код, за исключе нием конструкции t o ! s t ri n g( li n e[ 2.. $]). Зачем она нуж на и что будет, если о ней забыть?
Ц икл foreach, последовательно считывая из стандартного потока ввода строки текста, размещ ает их в переменной line. Поскольку не имеет смысла выделять память под новый буфер при чтении следующей стро ки, в каж дой итерации byLine заново использует место, выделенное для line. Тип самой переменной l i n e —char[], массив знаков.
Если вы всего лиш ь, считав, «обследуете» каж дую строчку, а потом за бываете о ней, в любом случае (как с t o!s tri ng(l ine[2.. $]), так и без нее) все будет работать гладко. Но если вы ж елаете создать код, который бу дет где-то накапливать содерж ание читаемых строк, лучш е позаботить ся о том, чтобы он их действительно копировал. Очевидно, было задума но реально хранить текст в переменной currentParagraph, а не использо вать ее как временное пристанищ е, так что необходимо получать дубли каты; отсю даиприсутствие конструкции t o!string, которая преобразует любое вы ражение в строку. Переменные типа st ri ng неизменяемы, а to гарантирует приведение к этому типу созданием дубликата.
Если забыть написать to! st ri ng и впоследствии код все ж е скомпилируется, в результате получится бессмыслица, и ошибку будет довольно-таки сложно обнаружить. Очень неприятно отлаживать программу, одна часть которой изменяет данные, находящ иеся в другой части программы, по тому что это уж е не локальные изменения (трудно представить, сколько вызовов to можно забыть при написании большой программы). К сча стью, это не причина для беспокойства: типы переменных line и current Paragraph соответствуют роли этих переменных в программе. Перемен ная l i ne имеет тип char[], представляющий собой массив знаков, кото рые можно перезаписывать в любой момент; переменная currentParagraph имеет тип s tr in g —массив знаков, которые нельзя изменять по отдельно сти. (Для самы хлюбопы тных: полноеим я THnastring - immutable(char)[], что дословно означает «непрерывный диапазон неизменяемых знаков».
1.5. Основные структуры данных Мы вернемся к разговору о строках в главе 4.) Эти переменные не могут ссылаться на одну и ту ж е область памяти, поскольку l i ne наруш ает обязательство currentParagraph не изменять знаки по отдельности. По этому компилятор отказывает в ком пиляции ошибочного кода и требу ет копию, которую вы и предоставляете благодаря преобразованию в строку с помощью конструкции t o!s tri ng. И все счастливы.
С другой стороны, если постоянно копировать строковые значения, то нет необходимости дублировать данны е на ниж нем уровне и х представ ления - переменные просто могут ссылаться на одну и ту ж е область памяти, которая наверняка не будет перезаписана. Это делает копиро вание переменных типа s t r i n g безопасным и эффективным одновремен но. Но это еще не все плюсы. Строки мож но без проблем разделять м еж ду потоками, потому что данны е типа s t r i n g неизменяемы, так что воз можность конфликта при обращ ении к памяти попросту отсутствует.
Неизменяемость - это действительно здорово. С другой стороны, если вам потребуется интенсивно изменять знаки по отдельности, возм ож но, вы предпочтете использовать тип char[], хотя бы временно.
Структура PersonData в том виде, в каком она задан а выше, очень про ста. Однако в общем случае структуры могут определять не только дан ные, но и другие сущ ности, такие как частные (приватные, закрытые) разделы (обозначаются ключевым словом private), функции-члены, тес ты модулей, операторы, конструкторы и деструкторы. По умолчанию любой элемент структуры инициализируется значением по умолчанию (ноль для целых чисел, N aN для чисел с плавающ ей запятой1 и null для массивов и других типов, доступ к которым не осущ ествляется напря мую. А теперь реализуем ф ункцию addParagraph, которая разбивает строку текста на слова и распределяет их по ассоциативному массиву.
Строка,которуюобрабаты ваетта1п,имеетвид:'Н ат. То be, or not to bethat i s the question. " Д л я т о г о ч т о б ы о т д ел и т ь и м я п е р с о н а ж а о т с л о в, которые он произносит, нам требуется найти первый разделитель " Для этого используем функцию find. В ы раж ение haystack.find(needle) возвращает правую часть haystack, начинаю щ ую ся с первого вхож де ния needle. (Если needle в haystack отсутствует, то вызов find с такими аргументами вернет пустую строку.) Пока мы формируем словарь, не мешает немного прибраться. Во-первых, нуж н о преобразовать фразу к ниж нему регистру, чтобы слово с заглавной и со строчной буквы вос принималось как одна и та ж е словарная единица. Об этом легко поза ботиться с помощью вызова ф ункции tolower. Второе, что необходимо сделать, - удалить мощный источник ш ум а - знаки пунктуации, кото рые превращают, к примеру, «him.» и «him» в разные слова. Д ля того чтобы очистить словарь, достаточно передать ф ункции s p l i t единствен ный дополнительный параметр. Имеется в виду регулярное вы раж е 1 NaN (Not а Number, еечисло) - хорошее начальное значение по умолчанию для чисел с плавающей запятой. К сожалению, для целых чисел не сущест вует эквивалентного начального значения.
ние, которое уничтож ит всю «шелуху»: regex( " \ t,.;:?]+ "). Получив та кой аргумент, ф ункция s p lit сочтет любую последовательность знаков, упомянуты х м еж ду [ и ], одним из разделителей слов. Теперь мы гото вы, как говорится, приносить больш ую пользу с помощью всего лишь маленького кусочка кода:
void addParagraph(string li ne, ref PersonaData[string] info) { / / Выделить имя персонажа и его реплику info[persona].totalWordsSpoken += words.length;
foreach (word; words) ++info[persona].wordCount[word];
Ф ункция addParagraph отвечает за обновление ассоциативного массива.
В случае если персонаж ещ е не высказывался, код вставляет «пустой»
объект типа PersonaData, инициализированны й значениями по умолча нию. П оскольку значение по умолчанию для типа uint - ноль, а создан ный в соответствии с правилами по умолчанию ассоциативный массив пуст, только что вставленный слот готов к приему осмысленной инфор Н аконец, для того чтобы напечатать краткую сводку по каж дом у персо наж у, реализуем ф ункцию printResults:
writefln("%20s %6u %6u" persona, data.totalWordsSpoken, Готовы к тест-драйву? Тогда сохраните и запустите!
1.6. Интерфейсы и классы Тут есть чем позабавиться. К ак и ож идалось, наш друж ок «Н ат»
с большим отрывом выигрывает у всех остальны х, получив львиную долю слов. Довольно интересна роль Вольтиманда («Volt»): он немного словен, но при скромном количестве реплик виртуозно демонстрирует солидный словарный запас. Еще любопытнее в этом плане роль матро са («Sailor»), который вообще почти не повторяется. Т акж е сравните красноречивую королеву («Queen») с Офелией («Oph»): королева произ носит всего на 10% слов больше, чем Офелия, но ее лексикон богаче как В выводе есть немного ш ума (например, "Both [Mar "), который прилеж ный программист легко устранит и который вряд ли статистически влияет на то, что действительно представляет для нас интерес. Тем не менее исправление последних огрехов - поучительное (и реком ендуе мое) упражнение.
1.6. Интерфейсы и классы Объектно-ориентированные средства важны для больш их проектов;
так что, знакомя вас с ними на примере маленьких программ, я рискую выставить себя недоумком. Прибавьте к этому большое ж елани е избеГлава 1. Знакомство с языком D ж ать заезж ен ны х примеров с животными и работниками, и сложится довольно неприятная картина. Да, забыл еще кое-что: в маленьких при мерах обычно не видны проблемы создания полиморфных объектов, а это очень важ но. Что делать бедному автору! К счастью, реальный мир снабдил меня полезным примером в виде относительно небольшой за дачи, которая в то ж е время не имеет удовлетворительного процедурно го реш ения. О бсуж даемы й н и ж е код - это переработка небольшого по лезного скрипта на язы ке awk, который вышел далеко за рамки заду манного. Мы вместе пройдем путь до объектно-ориентированного реше ния - одновременно компактного, полного и изящного.
Как насчет небольшой программы, собирающей статистику (в связи с этим назовем ее s t a t s ) ? П ускай ее интерфейс будет простым: имена статистических ф ункций, используемых для вычислений, передаются в s t a t s как параметры командной строки, последовательность чисел для анализа поступает в стандартный поток ввода в виде списка (разде литель - пробел), статистические результаты печатаются один за дру гим по одному на строке. Вот пример работы программы:
Н аписанны й на скорую руку «непричесанный» скрипт без проблем ре шит эту задачу. Но в данном случае при увеличении количества стати стических функций «лохматость» кода уничтож ит преимущества от быстроты его создания. Так что поищем решение получше. Для начала остановимся на простейш их статистических функциях: получение ми нимум а, максимум а и среднего арифметического. Н ащупав легко рас ш иряемый вариант кода, мы получим простор для неограниченной реа л изации более слож ны х статистических функций.
Простейший подход к решению задачи - в цикле пройтись по входным данным и вычислить всю необходимую статистику. Но выбрать такой путь - значит отказаться от идеи масштабируемости программы. Ведь всякий раз, когда нам потребуется добавить новую статистическую функ цию, придется подвергать готовый код хирургическому вмешательст ву. Если мы хотим выполнять только те вычисления, о которых попро сили в командной строке, необходимы серьезные изменения. В идеале мы долж ны заклю чить все статистические функции в последователь ные куски кода. Таким образом, мы расширяем функциональность программы, просто добавляя новый код; это принцип открытости/за крытости [39] во всей красе.
При таком подходе необходимо выяснить, что общего у всех (или хотя бы у большинства) статистических функций. Ведь наша цель - обращаться ко всем функциям из одной точки программы, причем унифицирован но. Д ля начала отметим, что Min и Мах отбирают аргументы из входной 1.6. Интерфейсы и классы последовательности по одному, а результат будет готов, как только за кончится ввод. Конечный результат - одно-единственное число. Т акж е функция Average по окончании чтения всех своих аргументов долж на выполнить завершающий ш аг (разделить накопивш уюся сум м у на чис ло слагаемых). Кроме того, у каж дого алгоритма есть собственное со стояние. Если разные вычисления долж ны предоставлять одинаковый интерфейс для работы с ними и при этом «запоминать» свое состояние, разумный шаг - сделать их объектами и определить формальный ин терфейс для управления всеми этими объектами и каж ды м из них в от дельности.
Интерфейс определяет требуемое поведение в виде набора ф ункций. Ра зумеется, тот, кто зам ахнется на реализацию интерфейса, долж ен бу дет определить все ф ункции в том виде, в каком они заявлены. Раз у ж мы заговорили о реализации, давайте посмотрим, как мож но опреде лить класс Min, так чтобы он повиновался указаниям ж елезной руки интерфейса Stat.
void postprocess() {} / / Ничего не делать M - это класс, пользовательский тип, привносящ ий в D преимущ ества ООП. С помощью синтаксиса c las s Min: S ta t класс Min во всеуслы ш ание объявляет, что он реализует интерфейс Stat. И Min действительно опре деляет все три функции, продиктованные волей Stat, в точности с теми ж е аргументами и возвращаемыми типами (иначе компилятор не дал бы M просто так проскочить). Min содерж ит всего лиш ь один закрытый элемент (тот, что помечен директивой private) — переменную min (наи меньшее из прочитанных значений) и обновляет ее внутри ф ункции accumulate. Начальное значение Min - самое большое число (которое м ож но представить типом double), так что первое ж е число из входной после довательности заместит его.
Перед тем как определить другие статистические ф ункции, реализуем основной алгоритм наш ей программы s t a t s, предусматриваю щ ий чте ние параметров командной строки, создание соответствующих объек тов, производящ их вычисления (таких как экземпляр класса Min, когда через консоль передан аргумент Min), и манипулирование ими с помо щью интерфейса Stat.
Эта небольш ая программа творит чудеса. Д ля начала список парамет ров main отличается от того, что мы видели до сих пор: на этот раз в функцию передается массив строк. Средства библиотеки времени ис полнения D инициализирую т этот массив параметрами, переданными компилятору из командной строки вместе с именем скрипта для запус ка. Первый цикл инициализирует массив sta ts исходя из значений мас сива args. Учитывая, что в D (как и в других язы ках) первый аргумент это имя самой программы, мы пропускаем первую позицию: нас инте ресует срез args[1.. $]. Теперь разберемся с командой Тут много непонятного, но, как говорят в ситкомах, я все могу объяс нить. Во-первы х, здесь знак ^ служ ит бинарным оператором, то есть осущ ествляет конкатенацию строк. Поэтому если аргумент командной строки - Min, то результат конкатенации - строка 'stats.M in ", которая и будет передана ф ункции O bject.factory. Object - предок всех классов, создаваемы х в программах на D. Он определяет статический метод fac tory, который принимает строку, ищ ет соответствующий тип в неболь ш ой базе данны х (которая строится во время компиляции), магиче ским образом создает объект типа, указанного в переданной строке, и возвращ ает его. Если запрош енны й класс отсутствует в упомянутой базе данны х, Object, factory возвращ ает null. Чтобы этого не произош л о, достаточно определить класс Min где-нибудь в том ж е файле, что и вызов Object. factory. В озм ож ность создавать объект по имени его ти па —это важ ное средство, востребованное во множестве полезных приИнтерфейсы и классы лож ений. На самом деле, оно настолько важ но, что является «сердцем»
некоторых языков с динамической типизацией. Язы ки со статической типизацией (такие как D и Java) вы нуж дены полагаться на средства своих библиотек времени исполнения или предоставлять программи сту самостоятельно изобретать механизмы регистрации и распознава Почему stats.Min, а не просто Min? D серьезно относится к принципу мо дульности, поэтому в этом язы ке отсутствует глобальное пространство имен, где кто угодно мож ет складировать что угодно. К аж ды й символ обитает в рамках модуля со своим именем, и по умолчанию имя модуля совпадает с именем его исходного ф айла без расш ирения. Таким обра зом, при условии что наш файл назван s t a t s. d, D полагает, что всякое имя, определенное в этом файле, принадлеж ит модулю s t a t s.
Осталась последняя загвоздка. Статический тип только что полученно го объекта типа Min на самом деле не Min. Это звучит странно, но легко объясняется тем, что, вызвав 0bject.factory("4To угодно"), вы мож ете создать любой объект, поэтому возвращ аемый тип долж ен быть неким общим знаменателем для всех возмож ны х объектны х типов — и это Object. Для того чтобы получить ссылку, соответствующ ую типу объек та, который вы задум али, необходимо преобразовать объект, возвра щенный Object.factory, в объект типа State. Эта операция называется приведением т ипов (type castin g). В язы ке D вы ражение cast(T) expr приводит выражение ехрг к типу Т. Операции приведения типов, в кото рых участвуют классы или интерфейсы, всегда проверяются, поэтому код надеж но защ ищ ен от дураков.
Оглянувшись назад, мы зам етим, что львиная доля того, что делает скрипт, выполняется в первых пяти его строках. Эта сам ая слож ная часть, которая полностью определяет весь остальной код. Второй цикл читает по одному числу за раз (об этом заботится ф ункция readf) и вы зывает accumulate для всех объектов, собираю щ их статистику. Ф унк ция readf возвращает число объектов, успеш но прочитанных согласно заданной строке формата. В наш ем случае формат задан в виде строки %s ", что означает «один элемент, окруж енны й любым количеством пробелов». (Тип элемента определяется типом считанного элемента, в нашем случае x принимает значение типа double.) П оследнее, что дела ет программа, - выводит результаты вычислений на печать.
1.6.1. Больше статистики. Наследование Реализация Мах так ж е тривиальна, как и реализация Min; за исклю че нием небольших изменений в accumulate, эти классы ничем не отлича ются друг от друга1. Д аж е если новое задан ие до боли напоминает пре дыдущее, в голову долж на приходить мысль «интересно», а не «о бож е, 1 Это не совсем так. Переменная-аккумулятор должна быть инициализирована значением double.max и соответственно переименована. - Прим. пауч.ред.
какая скука». Рутинны е задачи - это возможность для повторного ис пользования, и «правильные» языки, способные лучш е эксплуатиро вать различные преимущ ества подобия, по некоторой абстрактной шка ле качества долж ны оцениваться выше. Нам придется выяснить, что именно общего у функций Min и Мах (и, в идеале, у прочих статистиче ск их функций). Присмотревшись к ним, можно заметить, что обе при надлеж ат к разряду статистических функций, результат которых вы числяется ш аг за ш агом и м ож ет быть вычислен всего по одному числу.
Н азовем такую категорию статистических функций пош аговыми ф унк Абстрактный класс мож но воспринимать как частичное обязательство:
он реализует некоторые методы, но не все, так что «самостоятельно»
такой код работать не мож ет. М атериализуется абстрактный класс то гда, когда от него наследую т и в теле потомков завершают реализацию.
Класс IncrementalStat обслуж ивает повторяющийся код классов, реали зую щ их интерфейс Stat, но оставляет реализацию метода accumulate своим потомкам. Вот как выглядит новая версия класса M in:
Кроме того, в классе Min определен конструктор в виде специальной функции t h i s (), необходимы й для корректной инициализации резуль тата. Д а ж е несмотря на добавление конструктора, полученный код зна чительно улучш ил ситуацию относительно исходного положения дел, особенно с учетом того факта, что множество других статистических ф ункций так ж е соответствуют этом у шаблону (например, сумма, дис персия, среднее арифметическое, стандартное отклонение). Посмотрим на реализацию функции получения среднего арифметического, по скольку это прекрасный повод представить еще пару концепций:
c l a s s Average : I ncrementalStat { 1.7. Значения против ссылок Начнем с того, что в Average вводится ещ е одно поле, items, которое ин и циализируется нулем с помощью синтаксиса items = 0 (только дл я того, чтобы показать, как надо инициализировать переменные, но, как отм е чалось выше, целые числа и так инициализирую тся нулем по умолча нию). Второе, что необходимо отметить: Average определяет конструк тор, который присваивает переменной _result ноль. Так сделано, пото му что, в отличие от минимума или м аксимум а, при отсутствии аргу ментов среднее арифметическое считается равным нулю. И хотя мож ет показаться, что инициализировать _result значением N aN только для того, чтобы тут ж е записать в эту переменную ноль, - бессмысленное действие, уход от так называемого «мертвого присваивания» представ ляет собой легкую добычу для любого оптимизатора. Н аконец, Average переопределяет метод postprocess, несмотря на то что в классе IncrementalStat он у ж е определен. В язы ке D по умолчанию можно переопреде лить (унаследовать и заново определить) методы любого класса, но надо обязательно добавлять директиву override, чтобы избеж ать всевозмож ных несчастных случаев (таких как неудача переопределения в связи с какой-нибудь опечаткой или изменением в базовом типе, либо пере определение чего-нибудь по ошибке). Если вы поставите перед методом класса ключевое слово fin a l, то запретите классам-потомкам переопре делять эту функцию (что эффективно останавливает механизм ди нам и ческого поиска методов по дереву классов).
1.7. Значения против ссылок Проведем небольшой эксперимент:
П охож е, игры с объектом типа MyStruct сильно отличаются от игр с объ ектом типа MyObject. И в том и в другом случае мы создаем переменную, которую затем копируем в другую переменную, после чего изменяем копию (вспомните, что ++ - это унарный оператор, прибавляющий еди ни цу к своему аргументу). Этот эксперимент показывает, что после ко пирования c1 и c2 ссылаются на одну и ту ж е область памяти с инфор м ацией, а s1 и s2, напротив, «ж ивут врозь».
П оведение MyStruct свидетельствует о том, что этот объект подчиняется сем ант ике значений', каж дая переменная ссылается на собственное единственное значение, и присваивание одной переменной другой озна чает, что значение одной переменной реально копируется в значение другой переменной. И сходное значение, по образу и подобию которого изменяли вторую переменную, остается нетронутым, и обе переменные далее продолж аю т развиваться независимо друг от друга. Поведение MyClass говорит, что объект этого типа подчиняется ссылочной семан тике: значения создаю тся явно (в нашем случае с помощью вызова new MyClass), и присваивание одного экземпляра класса другому означает лиш ь то, что обе переменные будут ссылаться на одно и то ж е значение Со значениями легко работать, о них просто рассуждать, и они позволя ют производить эффективные вычисления с переменными небольшого размера. С другой стороны, нетривиальные программы сложно реали зовать, не обладая средствами доступа к переменным без их копирова ния. Отсутствие возмож ности работать со ссылками препятствует, на пример, работе с типам и, ссылающ имися на себя ж е (списки или дере вья), или структурами, ссылающ имися друг на друга (такими как до чернее окно, знаю щ ее о своем родительском окне). Любой уважающий себя язы к реализует работу со ссылками в том или ином виде; спорят только о необходим ы х умолчаниях. В С в общем случае переменные трактую тся как значения, но если пользователь захочет, он будет рабо тать со ссы лками с помощью указателей. В дополнение к указателям, С ++ определяет ссылочные типы. Любопытно, что чисто функциональ ные язы ки могут использовать ссылки или значения, когда сочтут нуж ным, потому что при написании кода м еж ду ними нет разницы. Ведь чисто функциональные язы ки запрещ аю т изменения, поэтому невоз можно сказать, когда они порож даю т копию значения, а когда просто используют ссылку на него — значения «заморожены», поэтому вы не сможете проверить, разделяется ли значение м еж ду несколькими пере менными, изменив одну из них. Чисто объектно-ориентированные язы ки, напротив, традиционно поощ ряют изменения. Д ля них общ ий сл у чай - ссылочная семантика. Некоторые такие язы ки достигают умопо мрачительной гибкости, допуская, например, динамическое изменение системных переменных. Наконец, некоторые язы ки избрали гибрид ный подход, включая как типы-значения, так и ссылочные типы, с раз ной долей предпочтения тем или другим.
Язык D систематически реализует гибридны й подход. Д ля определе ния ссылочных типов используйте классы. Д ля определения типов-зна чений или гибридных типов используйте структуры. В главах 6 и 7 со ответственно описаны конструкторы этих типов, снабженные средства ми для реализации соответствующ его подхода. Например, структуры не поддерживают динамическое наследование и полиморфизм (такой как в рассмотренной нами программе s t a t s ), поскольку такое поведение не согласуется с семантикой значений. Д инам ический полиморфизм объектов - это характеристика ссылочной семантики, и лю бая попыт ка смешать эти два подхода приведет лиш ь к ж утк им последствиям.
(Например, классическая опасность, подстерегающая программистов на С++, - slicin g, неож иданное лиш ение объекта его полиморфных спо собностей в результате невнимательного использования этого объекта в качестве значения. В язы ке D slicin g невозможен.) В завершение хочется сказать, что структуры —пож алуй, наиболее ги б кое проектное решение. Определив структуру, вы мож ете вдохнуть в нее любую семантику. Вы мож ете сделать так, что значение будет ко пироваться постоянно, реализовать ленивое копирование, а-ля копиро вание при записи, или подсчитывать ссылки, или выбрать что-то сред нее м еж ду этими способами. Вы д а ж е м ож ете определить ссылочную семантику, используя классы или указатели внут ри своей структуры.
С другой стороны, некоторые из этих альтернатив требуют подкованно сти в техническом плане; использование классов, напротив, подразум е вает простоту и унифицированность.
1.8. Итоги Эта глава - вводная, поэтому какие-то детали отдельных примеров и концепций остались за кадром или были рассмотрены вскользь. При этом опытный программист легко поймет, как можно завершить и усо вершенствовать код примеров.
Надеюсь, что-то интересное наш лось для каж дого. Кодера-практика, противника любых излиш еств, могла порадовать чистота синтаксиса массивов и ассоциативных массивов. Уже эти две концепции сильно упрощ аю т еж едневное кодирование и полезны как для малых, так и для больш их проектов. П оклонник объектно-ориентированного про граммирования, хорошо знакомый с интерфейсами и классами, мог от метить хорош ую масштабируемость языка для крупных проектов. А те, кто хочет писать на D короткие скрипты, увидели, как легко пишутся и запускаю тся сценарии, манипулирую щ ие файлами.
Как водится, полный рассказ гораздо длиннее. И все ж е полезно время от времени вернуться к основам и удостовериться, что простые вещи ос таю тся простыми.
Основные типы данных. Выражения Если вы когда-нибудь программировали на С, С++, Java или C #, то с ос новными типами данны х и выражениями D у вас не будет никаких затруднений. Операции со значениями основных типов - неотъемлемая часть решений многих задач программирования. Эти средства языка, в зависимости от ваших предпочтений, могут сильно облегчать либо от равлять вам ж изнь. Совершенного подхода не существует; нередко по ставленные цели противоречат друг другу, заставляя руководствоваться собственным субъективным мнением. Это, в свою очередь, лиш ает язык возможности угодить всем до единого. Слишком строгая система обреме няет программиста своими запретами: он вынужден бороться с компи лятором, чтобы тот принял простейш ие выражения. А сделай систему типизации чересчур снисходительной —и не зам етиш ь, как окаж еш ься по ту сторону корректности, эффективности или того и другого вместе.
Система основных типов D творит маленькие чудеса в границах, зада ваемых его принадлежностью к семейству статически типизированны х компилируемых языков. Определение типа по контексту, распростра нение интервала значений, всевозможные стратегии перегрузки опера торов и тщательно спроектированная сеть автоматических преобразо ваний вместе делают систему типизации D дотош ным, но сдерж анны м помощником, который если и придирается, требуя внимания, то обыч но не зря.
Основные типы данных можно распределить по следую щ им категориям:
Тип без зн ачени я: void, используется во всех случаях, когда фор мально требуется указать тип, но никакое осмысленное значение не порождается.
Тип null: typeof(null) - тип константы null, используется в основном в шаблонах, неявно приводится к указателям, массивам, ассоциа тивным массивам и объектным типам.
• Логический (булев) тип: bool с двумя возможными значениями true • Ц елы е типы: byte, short, in t и long, а такж е их эквиваленты без зна • В ещ ест венны е т ипы с плаваю щ ей запят ой: flo a t, double и real.
• З н аковы е типы: char, wchar и dchar, которые на самом деле содержат числа, предназначенные для кодирования знаков Юникода.
В табл. 2.1 вкратце описаны основные типы данны х D с указанием их размеров и начальных значений по умолчанию. В языке D переменная инициализируется автоматически, если вы просто определили ее, не указав начального значения. Значение по умолчанию доступно для лю бого типа как.init; например int. in it - это ноль.
Таблица 2.1. Основные типы данных D 2.1. Идентификаторы 2.1. Идентификаторы Идентификатор, или символ - это чувствительная к регистру строка знаков, начинающаяся с буквы или знака подчеркивания, после чего следует любое количество букв, знаков подчеркивания или цифр. Един ственное исключение из этого правила: идентификаторы, начинаю щиеся с двух знаков подчеркивания, зарезервированы под ключевые слова самого D. Идентификаторы, начинающ иеся с одного знака под черкивания, разрешены, и в настоящ ее время д а ж е принято именовать поля классов таким способом.
Интересная особенность идентификаторов D - их интернациональность: