В данной статье приводятся наиболее полезные примеры перегрузки на основе имён аргументов, которые встретились в моей практике, и которые я воплотил в 11l. Просто про именованные аргументы функций/методов здесь я говорить не буду: их польза или вред являются предметом споров, а поддержка в языках программирования варьируется от практически полного игнорирования (привет, C++) до явного злоупотребления\overuse этой возможностью (привет, Swift).
Под перегрузкой функций на основе имён аргументов понимается возможность объявления функций, имеющих одинаковое имя и, как правило, одинаковое количество аргументов, но которые [аргументы] отличаются своим именем [тип аргументов при этом может быть одинаковым]. При этом при вызове такой функции аргументы [те, которые отличаются только своим именем] следует именовать, иначе непонятно, какой перегруженный вариант функции следует выбрать.
Конструкторы
Наибольшую пользу, по моему мнению, перегрузка на основе имён аргументов имеет для конструкторов. Т.к. если для обычных функций/методов вполне можно обойтись добавкой к названию функции/метода [например,
index(of: 1)
можно заменить на indexOf(1)
, а index(where: ...)
— на indexWhere(...)
], то для конструкторов такое уже подходит гораздо хуже: charFromDigit
или intToStringWithRadix
/stringFromIntWithRadix
выглядят громоздко и некрасиво.Конструктор символа в 11l имеет следующие формы:
Char(code' 65)
— создаёт символ с кодом 65 [т.е. соответствующий латинской буквеA
];Char(digit' i)
— создаёт символ, соответствующий цифре числаi
(при этом числоi
должно быть неотрицательным и однозначным, т.е. от 0 до 9 включительно, иначе конструктор порождает исключение), является сокращённой формой записи выраженияChar(code' ‘0’.code + i)
[плюс проверка на принадлежностьi
диапазону0..9
];Char(string' s)
— создаёт символ из односимвольной строкиs
(если строкаs
пустая, либо содержит более одного символа, то порождается исключение). Может быть полезно когда символ создаётся из строки, полученной от пользователя [через консольный ввод, аргумент командной строки или из конфигурационного файла], чтобы не добавлять проверку на длину строки вручную.
Такие конструкторы повышают читаемость кода создания символа, полностью исключая неоднозначности:
Char(code' 0)
создаёт символ с кодом 0 [также можно использовать более короткую запись: "\0"
], а Char(digit' 0)
создаёт символ, соответствующий цифре 0 (т.е. с кодом 48), при этом Char(0)
является ошибкой компиляции {так как Char(0)
можно воспринимать двояко: с одной стороны в языках C/C++ char(0)
означает символ с кодом 0, а с другой стороны String(0)
/str(0)
является строкой, состоящей из символа цифры 0, и было бы логично если бы Char(0)
сохранял такое же поведение}.Чем полезен отдельный тип для символа, и почему подход C++ [при котором
char
по факту совпадает с типом int8_t
] является неудачным решением я подробно написал здесь.Конструктор
Int
имеет следующие формы:Int(Float f)
— создаёт целое число из вещественного числаf
путём отбрасывания дробной части;Int(String s)
— создаёт целое число из строкиs
;Int(s, radix' base)
— создаёт целое число из строкиs
с заданным основанием системы счисления;Int(bytes' b)
— создаёт целое число из массива байтb
(порядок от младшего к старшему [little-endian]);Int(bytes_be' b)
— создаёт целое число из массива байтb
(порядок от старшего к младшему [big-endian]).
Конструктор
String
имеет следующие формы:String(o)
— создаёт строковое представление объектаo
;String(i, radix' base)
— создаёт строку из числаi
с заданным основанием системы счисления.
Если бы в C++ можно было использовать именованные аргументы, тогда
std::vector
логичнее было бы конструировать так:std::vector<int> a(size: 10); // вместо std::vector<int> a(10);
std::vector<int> b(reserve: 10); // ну или b(capacity: 10);
Метод split() у строк
Допустим, необходимо провести разбор BBCode-подобной разметки.
Вот некоторые поддерживаемые теги.
Разбор можно реализовать посимвольным сканированием размеченного текста. При обнаружении символа открывающей квадратной скобки необходимо найти соответствующую закрывающую скобку, а затем проверить, является ли подстрока, заключённая в квадратные скобки, допустимым тегом. Первое, что необходимо сделать — это разделить такую подстроку символом - [b]жирный[/b]
- [url]http://...[/url]
- [url=http://...]ссылка[/url]
- [img]http://...[/img]
- [img=50,40]http://...[/img] (изображение с заданными шириной и высотой)
- [color=red]красный[/color]
- [color=255,0,0]тоже красный[/color]
- [color=255,0,0,128]красный полупрозрачный[/color]
=
. На Python это выглядит так:tag_arr = tag_str.split('=', maxsplit = 1)
Обратите внимание на именованный аргумент maxsplit
метода split
. Значение 1
означает, что строка tag_str
будет разбита только по первому найденному символу =
, что позволяет корректно парсить теги вида [url=https://www.google.com/search?q=test]
.[В 11l аналогом аргумента
maxsplit
из Python является limit
, причём maxsplit = 1
соответствует limit' 2
. Такое поведение присуще Ruby и PHP и видится мне более естественным.]Затем необходимо проверить значение
tag_arr[0]
:match tag_arr[0]:
case 'b':
...
case 'url':
...
case 'img':
if len(tag_arr) == 2:
size = tag_arr[1].split(',')
assert(len(size) == 2)
sizex, sizey = map(int, size)
...
...
case 'color':
if len(tag_arr) != 2:
raise TextParseError(...)
color = tag_arr[1].split(',')
assert(len(color) in (1, 3, 4))
if len(color) == 1:
...
else:
color_components = list(map(int, color))
...
Пару строк на Python:
color = tag_arr[1].split(',')
assert(len(color) in (1, 3, 4))
можно объединить в одну строку на 11l, используя именованный аргумент req
:var color = tag_arr[1].split(‘,’, req' (1, 3, 4))
При этом возможна оптимизация на основании того факта, что максимально возможная длина массива строк [либо массива из StringView
], возвращаемого методом split
, равна 4. Другими словами, такой вызов метода split
может возвращать не динамический массив, а статический, память под который выделяется на стеке.Аналогично, эти 3 строки на Python:
size = tag_arr[1].split(',')
assert(len(size) == 2)
sizex, sizey = map(int, size)
можно объединить в одну строку на 11l:var (sizex, sizey) = tag_arr[1].split(‘,’, req' 2).map(Int)
Вообще говоря, использование assert-ов [как явных, так и неявных внутри метода
split(..., req' ...)
] в коде парсера — не самая лучшая идея, но вполне жизнеспособная: можно просто завернуть код парсинга тегов в блок try-catch и при возникновении исключения в development-сборке показывать MessageBox с сообщением об ошибке [с кнопками ‘Продолжить’ и ‘Debug break’], а в финальной сборке для конечных пользователей игнорировать неправильный тег, либо подкрашивать его красным цветом. [При этом падать приложение из-за ошибки в теге, разумеется, не должно в любом случае.]Можно, конечно, аккуратно обработать все возможные ошибки в тегах {но основная проблема тут даже не в дополнительном коде обработки ошибок, а в придумывании хороших понятных текстов сообщений об ошибках :)(:}, но особого смысла в таких дополнительных проверках в коде я не вижу — документация по всем поддерживаемым парсером тегам должна быть обязательно, и вполне достаточно указания того, в каком именно теге размеченного текста присутствует ошибка — беглого взгляда на описание данного тега в документации будет, как правило, достаточно для нахождения и исправления ошибки.
Кроме
limit
и req
вариантов метода split
, в 11l есть ещё String.split(d, ', max)
[который по сути равнозначен String.split(d, req' 1..max)
] и String.split(d, ', first)
, который возвращает только первые first элементов массива строк.Разумеется, можно было просто добавить методы
split_limit()
, split_req()
, split_max()
и split_first()
, но перегруженная split()
с именованными аргументами всё-таки красивее.Функции min() и max()
Также как в Python, функции
min()
и max()
в 11l имеют опциональный именованный аргумент key
.Следующий код выведет самую длинную строку в массиве
arr
:var arr = [‘a’, ‘bc’]
print(max(arr, key' s -> s.len))
Именованные аргументы/параметры шаблонов
В попытках добавить класс статического массива [массива, максимальная длина которого известна на этапе компиляции] в 11l, я сделал неожиданное открытие. А именно то, что шаблонные параметры могут быть именованными аналогично аргументам функций. [Впрочем, поиском ‘named template parameters’ выяснилось, что не мне первому пришла в голову эта идея.]
Вот пример объявления статического массива в 11l:
Array[Int, max_len' 10] static_array
[Int, max_len' 10] static_array2 // сокращённая форма [тип такой же]
И аналогично, массив фиксированного размера:
Array[Int, len' 10] fixed_array
[Int, len' 10] fixed_array2 // сокращённая форма [тип такой же]
[Я предпочитаю
len
, а не size
, т.к. последний у меня ассоциируется с размером в байтах (например, в языке Си sizeof(a)
для массива из 10 целых 32-разрядных чисел возвращает 40, а не 10).]Кроме того, массив фиксированного размера, инициализированный элементами, можно объявить так:
var fixed_array1 = -[1, 2, 3]
var fixed_array2 = -[0] * 10 // равнозначно -[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Зачем вообще нужен статический массив и массив фиксированного размера, когда есть более универсальный динамический массив?
Чтобы не выделять память динамически под массив небольшого размера. Особенно это полезно в критичных к производительности местах кода, а также просто в целях удобства: например для задания треугольника удобно использовать именно массив фиксированного размера, который состоит из 3-х элементов, задающих координаты вершин треугольника.
Стоит заметить, что статический массив в 11l является более продвинутым, чем std::array или boost::array в C++ (по сути, он является аналогом TArray<T, TFixedAllocator<MaxLen>> в Unreal Engine): статический массив в 11l позволяет добавлять и удалять элементы, и поддерживает практически все методы (все, за исключением метода reserve) и операторы, применимые к обычным динамическим массивам.
Заключение
Действительно полезных применений перегрузки на основе имён аргументов в моём опыте встретилось, как видите, совсем немного, и было бы здорово увидеть ещё хотя бы несколько штук, а также обсудить полезность этой возможности языка программирования, который вы используете [или необходимость добавления этой возможности, если ваш язык её ещё не поддерживает].