Следующие разделы – это стремительная поездка по Дибургу. Небольшие показательные программы дают общее представление о языке. Основная цель повествования на данном этапе – обрисовать общую картину, а не дать ряд педантичных определений. Позже все аспекты языка будут рассмотрены с должным вниманием – в деталях.
Следующие разделы – это стремительная поездка по Дибургу. Небольшие показательные программы дают общее представление о языке. Основная цель повествования на данном этапе – обрисовать общую картину, а не дать ряд педантичных определений. Позже все аспекты языка будут рассмотрены с должным вниманием – в деталях.
[Исходный код](src/chapter-1/)
[В начало ⮍](#1-знакомство-с-языком-d) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.1. Числа и выражения
## 1.1. Числа и выражения
Интересовались ли вы когда-нибудь ростом иностранцев? Давайте напишем простую программу, которая переводит наиболее распространенные значения роста в футах и дюймах в сантиметры.
Интересовались ли вы когда-нибудь ростом иностранцев? Давайте напишем простую программу, которая переводит наиболее распространенные значения роста в футах и дюймах в сантиметры.
Если вы использовали `printf` прежде, то могли бы почувствовать себя как дома, когда б не маленькая особенность: мы ведь выводим значения переменных типа `int` и `double`– как же получилось, что и те и другие описаны с помощью спецификатора `%s`, обычно применяемого для вывода строк? Ответ прост. Средства D для работы с переменным количеством аргументов дают `writefln` доступ к информации об исходных типах переданных аргументов. Благодаря такому подходу программа получает ряд преимуществ: 1) значение `%s` может быть расширено до «строкового представления по умолчанию для типа переданного аргумента» и 2) если не удалось сопоставить спецификатор формата с типами переданных аргументов, вы получите ошибку в чистом виде, а не загадочное поведение, присущее вызовам `printf`с неверно заданным форматом (не говоря уже о подрыве безопасности, возможном при вызове `printf`с непроверяемыми заранее форматирующими строками).
Если вы использовали `printf` прежде, то могли бы почувствовать себя как дома, когда б не маленькая особенность: мы ведь выводим значения переменных типа `int` и `double`– как же получилось, что и те и другие описаны с помощью спецификатора `%s`, обычно применяемого для вывода строк? Ответ прост. Средства D для работы с переменным количеством аргументов дают `writefln` доступ к информации об исходных типах переданных аргументов. Благодаря такому подходу программа получает ряд преимуществ: 1) значение `%s` может быть расширено до «строкового представления по умолчанию для типа переданного аргумента» и 2) если не удалось сопоставить спецификатор формата с типами переданных аргументов, вы получите ошибку в чистом виде, а не загадочное поведение, присущее вызовам `printf`с неверно заданным форматом (не говоря уже о подрыве безопасности, возможном при вызове `printf`с непроверяемыми заранее форматирующими строками).
[Исходный код](src/chapter-1-1/)
[В начало ⮍](#1-1-числа-и-выражения) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.2. Инструкции
## 1.2. Инструкции
В языке D, как и в других родственных ему языках, любое выражение, после которого стоит точка с запятой, – это инструкция (например в программе «Hello, world!» сразу после вызова `writeln` есть ;). Действие инструкции сводится к вычислению выражения.
В языке D, как и в других родственных ему языках, любое выражение, после которого стоит точка с запятой, – это инструкция (например в программе «Hello, world!» сразу после вызова `writeln` есть ;). Действие инструкции сводится к вычислению выражения.
@ -179,6 +185,9 @@ if (‹выражение›) ‹инструкция1› else ‹инструк
Чисто теоретический вывод, известный как принцип структурного программирования, гласит, что все алгоритмы можно реализовать с помощью составных инструкций, `if`-проверок и циклов а-ля `for` и `foreach`. Разумеется, любой адекватный язык (как и D) предлагает гораздо больше, но мы пока постановим, что с нас довольно и этих инструкций, и двинемся дальше.
Чисто теоретический вывод, известный как принцип структурного программирования, гласит, что все алгоритмы можно реализовать с помощью составных инструкций, `if`-проверок и циклов а-ля `for` и `foreach`. Разумеется, любой адекватный язык (как и D) предлагает гораздо больше, но мы пока постановим, что с нас довольно и этих инструкций, и двинемся дальше.
[Исходный код](src/chapter-1-2/)
[В начало ⮍](#1-2-инструкции) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.3. Основы работы с функциями
## 1.3. Основы работы с функциями
Оставим пока в стороне обязательное определение функции `main` и посмотрим, как определяются другие функции на D. Определение функции соответствует модели, характерной и для других Алгол-подобных языков: сначала пишется возвращаемый тип, потом имя функции и, наконец, заключенный в круглые скобки список формальных аргументов, разделенных запятыми. Например, определение функции с именем `pow`, которая принимает значения типа `double` и `int`, а возвращает `double`, записывается так:
Оставим пока в стороне обязательное определение функции `main` и посмотрим, как определяются другие функции на D. Определение функции соответствует модели, характерной и для других Алгол-подобных языков: сначала пишется возвращаемый тип, потом имя функции и, наконец, заключенный в круглые скобки список формальных аргументов, разделенных запятыми. Например, определение функции с именем `pow`, которая принимает значения типа `double` и `int`, а возвращает `double`, записывается так:
@ -219,10 +228,15 @@ void main()
О функциях можно еще долго рассказывать. Можно передавать функции другим функциям, встраивать одну в другую, разрешать функции сохранять свою локальную среду (полнофункциональная синтаксическая клауза), создавать анонимные функции (лямбда-функции), с удобством манипулировать ими и еще множество дополнительных «вкусностей». Со временем мы доберемся до каждой из них.
О функциях можно еще долго рассказывать. Можно передавать функции другим функциям, встраивать одну в другую, разрешать функции сохранять свою локальную среду (полнофункциональная синтаксическая клауза), создавать анонимные функции (лямбда-функции), с удобством манипулировать ими и еще множество дополнительных «вкусностей». Со временем мы доберемся до каждой из них.
[Исходный код](src/chapter-1-3/)
[В начало ⮍](#1-3-основы-работы-с-функциями) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.4. Массивы и ассоциативные массивы
## 1.4. Массивы и ассоциативные массивы
Массивы и ассоциативные массивы (которые обычно называют хеш-таблицами, или хешами) – пожалуй, наиболее часто используемые сложные структуры данных за всю историю машинных вычислений, завистливо преследуемые списками языка Лисп. Множество полезных программ не требуют ничего, кроме массива или ассоциативного массива. Так что пришло время посмотреть, как D их реализует.
Массивы и ассоциативные массивы (которые обычно называют хеш-таблицами, или хешами) – пожалуй, наиболее часто используемые сложные структуры данных за всю историю машинных вычислений, завистливо преследуемые списками языка Лисп. Множество полезных программ не требуют ничего, кроме массива или ассоциативного массива. Так что пришло время посмотреть, как D их реализует.
[В начало ⮍](#1-4-массивы-и-ассоциативные-массивы) [Наверх ⮍](#1-знакомство-с-языком-d)
### 1.4.1. Работаем со словарем
### 1.4.1. Работаем со словарем
Для примера напишем простенькую программку, следуя такой спецификации:
Для примера напишем простенькую программку, следуя такой спецификации:
@ -321,6 +335,9 @@ b = a.dup; // Полностью скопировать a в b
++b[10]; // В b[10] теперь 2, а в a[10] остается 1
++b[10]; // В b[10] теперь 2, а в a[10] остается 1
```
```
[Исходный код](src/chapter-1-4-1/)
[В начало ⮍](#1-4-1-работаем-со-словарем) [Наверх ⮍](#1-знакомство-с-языком-d)
### 1.4.2. Получение среза массива. Функции с обобщенными типами параметров. Тесты модулей
### 1.4.2. Получение среза массива. Функции с обобщенными типами параметров. Тесты модулей
Получение среза массива – это мощное средство, позволяющее ссылаться на часть массива, в действительности не копируя данные массива. В качестве иллюстрации напишем функцию двоичного поиска, реализующую одноименный алгоритм: получив упорядоченный массив и значение, двоичный поиск быстро возвращает логический результат, сообщающий, есть ли заданное значение в массиве. Функция из стандартной библиотеки D возвращает более информативный ответ, чем просто булево значение, но знакомство с ней придется отложить, так как для этого необходимо более глубокое знание языка. Позволим себе, однако, приподнять планку, задавшись целью написать функцию, которая будет работать не только с массивами целых чисел, но с массивами элементов любого типа, допускающего сравнение с помощью операции `<`. Оказывается, реализовать эту задумку можно без особого труда. Вот как выглядит функция обобщенного двоичного поиска `binarySearch`:
Получение среза массива – это мощное средство, позволяющее ссылаться на часть массива, в действительности не копируя данные массива. В качестве иллюстрации напишем функцию двоичного поиска, реализующую одноименный алгоритм: получив упорядоченный массив и значение, двоичный поиск быстро возвращает логический результат, сообщающий, есть ли заданное значение в массиве. Функция из стандартной библиотеки D возвращает более информативный ответ, чем просто булево значение, но знакомство с ней придется отложить, так как для этого необходимо более глубокое знание языка. Позволим себе, однако, приподнять планку, задавшись целью написать функцию, которая будет работать не только с массивами целых чисел, но с массивами элементов любого типа, допускающего сравнение с помощью операции `<`. Оказывается, реализовать эту задумку можно без особого труда. Вот как выглядит функция обобщенного двоичного поиска `binarySearch`:
@ -382,6 +399,9 @@ bool binarySearchR(T)(T[] input, T value)
Рекурсивная реализация явно проще и концентрированнее по сравнению со своим итеративным собратом. Кроме того, она ничуть не менее эффективна, так как рекурсивные вызовы оптимизируются благодаря популярной среди компиляторов технике, известной как *оптимизация хвостовой рекурсии*. В двух словах: если функция возвращает просто вызов самой себя (но с другими аргументами), компилятор модифицирует аргументы и инициирует переход к началу функции.
Рекурсивная реализация явно проще и концентрированнее по сравнению со своим итеративным собратом. Кроме того, она ничуть не менее эффективна, так как рекурсивные вызовы оптимизируются благодаря популярной среди компиляторов технике, известной как *оптимизация хвостовой рекурсии*. В двух словах: если функция возвращает просто вызов самой себя (но с другими аргументами), компилятор модифицирует аргументы и инициирует переход к началу функции.
[Исходный код](src/chapter-1-4-2/)
[В начало ⮍](#1-4-2-получение-среза-массива-функции-с-обобщенными-типами-параметров-тесты-модулей) [Наверх ⮍](#1-знакомство-с-языком-d)
### 1.4.3. Подсчет частот. Лямбда-функции
### 1.4.3. Подсчет частот. Лямбда-функции
Поставим себе задачу написать еще одну полезную программу, которая будет подсчитывать частоту употребления слов в заданном тексте. Хотите знать, какие слова употребляются в «Гамлете» чаще всего? Тогда вы как раз там, где надо.
Поставим себе задачу написать еще одну полезную программу, которая будет подсчитывать частоту употребления слов в заданном тексте. Хотите знать, какие слова употребляются в «Гамлете» чаще всего? Тогда вы как раз там, где надо.
@ -419,7 +439,7 @@ void main()
}
}
```
```
А теперь, скачав из Сети файл `hamlet.txt`[^6] (который вы найдете по прямой [ссылке](hamlet.txt)) и запустив нашу маленькую программу с шекспировским шедевром в качестве аргумента, вы получите:
А теперь, скачав из Сети файл `hamlet.txt`[^6] (который вы найдете по прямой [ссылке](src/hamlet.txt)) и запустив нашу маленькую программу с шекспировским шедевром в качестве аргумента, вы получите:
```sh
```sh
1 outface
1 outface
@ -489,6 +509,9 @@ sort!(‹аргументы времени компиляции›)(‹аргу
Что и ожидалось: самые часто употребляемые слова набрали больше всего «очков». Настораживает лишь «Ham»[^7]. Это слово в пьесе вовсе не отражает кулинарные предпочтения героев. «Ham» – всего лишь сокращение от «Hamlet» (Гамлет), которым помечена каждая из его реплик. Явно у него был повод высказаться 358 раз – больше, чем любой другой герой пьесы. Далее по списку следует король – всего 116 реплик, меньше трети сказанного Гамлетом. А Офелия сее 58 репликами – просто молчунья.
Что и ожидалось: самые часто употребляемые слова набрали больше всего «очков». Настораживает лишь «Ham»[^7]. Это слово в пьесе вовсе не отражает кулинарные предпочтения героев. «Ham» – всего лишь сокращение от «Hamlet» (Гамлет), которым помечена каждая из его реплик. Явно у него был повод высказаться 358 раз – больше, чем любой другой герой пьесы. Далее по списку следует король – всего 116 реплик, меньше трети сказанного Гамлетом. А Офелия сее 58 репликами – просто молчунья.
[Исходный код](src/chapter-1-4-3/)
[В начало ⮍](#1-4-3-подсчет-частот-лямбда-функции) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.5. Основные структуры данных
## 1.5. Основные структуры данных
Раз уж мы взялись за «Гамлета», проанализируем этот текст чуть глубже. Например, соберем кое-какую информацию о главных героях: сколько всего слов было произнесено каждым персонажем и насколько богат его (ее) словарный запас. Для этого с каждым действующим лицом понадобится связать несколько фактов. Чтобы сосредоточить эту информацию в одном месте, определим такую структуру данных:
Раз уж мы взялись за «Гамлета», проанализируем этот текст чуть глубже. Например, соберем кое-какую информацию о главных героях: сколько всего слов было произнесено каждым персонажем и насколько богат его (ее) словарный запас. Для этого с каждым действующим лицом понадобится связать несколько фактов. Чтобы сосредоточить эту информацию в одном месте, определим такую структуру данных:
@ -662,6 +685,9 @@ Ambassador 41 34
В выводе есть немного шума (например, `"Both [Mar"`), который прилежный программист легко устранит и который вряд ли статистически влияет на то, что действительно представляет для нас интерес. Тем не менее исправление последних огрехов – поучительное (и рекомендуемое) упражнение.
В выводе есть немного шума (например, `"Both [Mar"`), который прилежный программист легко устранит и который вряд ли статистически влияет на то, что действительно представляет для нас интерес. Тем не менее исправление последних огрехов – поучительное (и рекомендуемое) упражнение.
[Исходный код](src/chapter-1-5/)
[В начало ⮍](#1-5-основные-структуры-данных) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.6. Интерфейсы и классы
## 1.6. Интерфейсы и классы
Объектно-ориентированные средства важны для больших проектов; так что, знакомя вас с ними на примере маленьких программ, я рискую выставить себя недоумком. Прибавьте к этому большое желание избежать заезженных примеров с животными и работниками, и сложится довольно неприятная картина. Да, забыл еще кое-что: в маленьких примерах обычно не видны проблемы создания полиморфных объектов, а это очень важно. Что делать бедному автору! К счастью, реальный мир снабдил меня полезным примером в виде относительно небольшой задачи, которая в то же время не имеет удовлетворительного процедурного решения. Обсуждаемый ниже код – это переработка небольшого полезного скрипта на языке awk, который вышел далеко за рамки задуманного. Мы вместе пройдем путь до объектно-ориентированного решения – одновременно компактного, полного и изящного.
Объектно-ориентированные средства важны для больших проектов; так что, знакомя вас с ними на примере маленьких программ, я рискую выставить себя недоумком. Прибавьте к этому большое желание избежать заезженных примеров с животными и работниками, и сложится довольно неприятная картина. Да, забыл еще кое-что: в маленьких примерах обычно не видны проблемы создания полиморфных объектов, а это очень важно. Что делать бедному автору! К счастью, реальный мир снабдил меня полезным примером в виде относительно небольшой задачи, которая в то же время не имеет удовлетворительного процедурного решения. Обсуждаемый ниже код – это переработка небольшого полезного скрипта на языке awk, который вышел далеко за рамки задуманного. Мы вместе пройдем путь до объектно-ориентированного решения – одновременно компактного, полного и изящного.
Оглянувшись назад, мы заметим, что львиная доля того, что делает скрипт, выполняется в первых пяти его строках. Эта самая сложная часть, которая полностью определяет весь остальной код. Второй цикл читает по одному числу за раз (об этом заботится функция `readf`) и вызывает `accumulate` для всех объектов, собирающих статистику. Функция `readf` возвращает число объектов, успешно прочитанных согласно заданной строке формата. В нашем случае формат задан в виде строки `" %s "`, что означает «один элемент, окруженный любым количеством пробелов». (Тип элемента определяется типом считанного элемента, в нашем случае `x` принимает значение типа `double`.) Последнее, что делает программа, – выводит результаты вычислений на печать.
Оглянувшись назад, мы заметим, что львиная доля того, что делает скрипт, выполняется в первых пяти его строках. Эта самая сложная часть, которая полностью определяет весь остальной код. Второй цикл читает по одному числу за раз (об этом заботится функция `readf`) и вызывает `accumulate` для всех объектов, собирающих статистику. Функция `readf` возвращает число объектов, успешно прочитанных согласно заданной строке формата. В нашем случае формат задан в виде строки `" %s "`, что означает «один элемент, окруженный любым количеством пробелов». (Тип элемента определяется типом считанного элемента, в нашем случае `x` принимает значение типа `double`.) Последнее, что делает программа, – выводит результаты вычислений на печать.
[Исходный код](src/chapter-1-6/)
[В начало ⮍](#1-6-интерфейсы-и-классы) [Наверх ⮍](#1-знакомство-с-языком-d)
### 1.6.1. Больше статистики. Наследование
### 1.6.1. Больше статистики. Наследование
Реализация `Max` так же тривиальна, как и реализация `Min`; за исключением небольших изменений в `accumulate`, эти классы ничем не отличаются друг от друга[^9]. Даже если новое задание до боли напоминает предыдущее, в голову должна приходить мысль «интересно», а не «о боже, какая скука». Рутинные задачи – это возможность для повторного использования, и «правильные» языки, способные лучше эксплуатировать различные преимущества подобия, по некоторой абстрактной шкале качества должны оцениваться выше. Нам придется выяснить, что именно общего у функций `Min` и `Max` (и, в идеале, у прочих статистических функций). Присмотревшись к ним, можно заметить, что обе принадлежат к разряду статистических функций, результат которых вычисляется шаг за шагом и может быть вычислен всего по одному числу. Назовем такую категорию статистических функций *пошаговыми функциями*.
Реализация `Max` так же тривиальна, как и реализация `Min`; за исключением небольших изменений в `accumulate`, эти классы ничем не отличаются друг от друга[^9]. Даже если новое задание до боли напоминает предыдущее, в голову должна приходить мысль «интересно», а не «о боже, какая скука». Рутинные задачи – это возможность для повторного использования, и «правильные» языки, способные лучше эксплуатировать различные преимущества подобия, по некоторой абстрактной шкале качества должны оцениваться выше. Нам придется выяснить, что именно общего у функций `Min` и `Max` (и, в идеале, у прочих статистических функций). Присмотревшись к ним, можно заметить, что обе принадлежат к разряду статистических функций, результат которых вычисляется шаг за шагом и может быть вычислен всего по одному числу. Назовем такую категорию статистических функций *пошаговыми функциями*.
@ -830,6 +859,9 @@ class Average : IncrementalStat
Начнем с того, что в `Average` вводится еще одно поле, `items`, которое инициализируется нулем с помощью синтаксиса `items = 0` (только для того, чтобы показать, как надо инициализировать переменные, но, как отмечалось выше, целые числа и так инициализируются нулем по умолчанию). Второе, что необходимо отметить: `Average` определяет конструктор, который присваивает переменной `_result` ноль. Так сделано, потому что, в отличие от минимума или максимума, при отсутствии аргументов среднее арифметическое считается равным нулю. И хотя может показаться, что инициализировать `_result` значением NaN только для того, чтобы тут же записать в эту переменную ноль, – бессмысленное действие, уход от так называемого «мертвого присваивания» представляет собой легкую добычу для любого оптимизатора. Наконец, `Average` переопределяет метод `postprocess`, несмотря на то что в классе `IncrementalStat` он уже определен. В языке D по умолчанию можно переопределить (унаследовать и заново определить) методы любого класса, но надо обязательно добавлять директиву `override`, чтобы избежать всевозможных несчастных случаев (таких как неудача переопределения в связи с какой-нибудь опечаткой или изменением в базовом типе, либо переопределение чего-нибудь по ошибке). Если вы поставите перед методом класса ключевое слово `final`, то запретите классам-потомкам переопределять эту функцию (что эффективно останавливает механизм динамического поиска методов по дереву классов).
Начнем с того, что в `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. Значения против ссылок
## 1.7. Значения против ссылок
Проведем небольшой эксперимент:
Проведем небольшой эксперимент:
@ -872,6 +904,9 @@ void main()
В завершение хочется сказать, что структуры – пожалуй, наиболее гибкое проектное решение. Определив структуру, вы можете вдохнуть в нее любую семантику. Вы можете сделать так, что значение будет копироваться постоянно, реализовать ленивое копирование, а-ля копирование при записи, или подсчитывать ссылки, или выбрать что-то среднее между этими способами. Вы даже можете определить ссылочную семантику, используя классы или указатели *внутри* своей структуры. С другой стороны, некоторые из этих альтернатив требуют подкованности в техническом плане; использование классов, напротив, подразумевает простоту и унифицированность.
В завершение хочется сказать, что структуры – пожалуй, наиболее гибкое проектное решение. Определив структуру, вы можете вдохнуть в нее любую семантику. Вы можете сделать так, что значение будет копироваться постоянно, реализовать ленивое копирование, а-ля копирование при записи, или подсчитывать ссылки, или выбрать что-то среднее между этими способами. Вы даже можете определить ссылочную семантику, используя классы или указатели *внутри* своей структуры. С другой стороны, некоторые из этих альтернатив требуют подкованности в техническом плане; использование классов, напротив, подразумевает простоту и унифицированность.
[Исходный код](src/chapter-1-7/)
[В начало ⮍](#1-7-значения-против-ссылок) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.8. Итоги
## 1.8. Итоги
Эта глава – вводная, поэтому какие-то детали отдельных примеров и концепций остались за кадром или были рассмотрены вскользь. При этом опытный программист легко поймет, как можно завершить и усовершенствовать код примеров.
Эта глава – вводная, поэтому какие-то детали отдельных примеров и концепций остались за кадром или были рассмотрены вскользь. При этом опытный программист легко поймет, как можно завершить и усовершенствовать код примеров.
@ -880,6 +915,8 @@ void main()
Как водится, полный рассказ гораздо длиннее. И все же полезно время от времени вернуться к основам и удостовериться, что простые вещи остаются простыми.
Как водится, полный рассказ гораздо длиннее. И все же полезно время от времени вернуться к основам и удостовериться, что простые вещи остаются простыми.
[В начало ⮍](#1-8-итоги) [Наверх ⮍](#1-знакомство-с-языком-d)
[^1]: «Shebang» (от shell bang: shell – консоль, bang – восклицательный знак), или «shabang» (# – sharp) – обозначение пути к компилятору или интерпретатору в виде `#!/путь/к/программе`. – *Прим. пер.*
[^1]: «Shebang» (от shell bang: shell – консоль, bang – восклицательный знак), или «shabang» (# – sharp) – обозначение пути к компилятору или интерпретатору в виде `#!/путь/к/программе`. – *Прим. пер.*
[^2]: В этой книге под «параметром» понимается значение, используемое внутри функции, а под «аргументом» – значение, передаваемое в функцию извне.
[^2]: В этой книге под «параметром» понимается значение, используемое внутри функции, а под «аргументом» – значение, передаваемое в функцию извне.
[^3]: `.idup`– свойство любого массива, возвращающее неизменяемую (immutable) копию массива. Про неизменяемость будет рассказано позже, пока же следует знать, что ключ ассоциативного массива должен быть неизменяемым. – *Прим. науч. ред.*
[^3]: `.idup`– свойство любого массива, возвращающее неизменяемую (immutable) копию массива. Про неизменяемость будет рассказано позже, пока же следует знать, что ключ ассоциативного массива должен быть неизменяемым. – *Прим. науч. ред.*