В Perl 6 есть ключевое слово multi, которое создает так называемые мультифункции (multi-subs). Это ни что иное как множественная диспетчеризация (multiple dispatch), встроенная в язык.
В отличие от обычных функций, которые объявляются со словом sub, мультифункции требуют двух слов: multi sub. Имя функции при этом остается одним и тем же для всего набора мультифункций. В зависимости от выполненных условий, компилятор выбирает один из вариантов функции.
Число аргументов
Рассмотрим несколько примеров. Первый пример — вычисление расстояния от начала координат до точки на прямой, на плоскости или в трехмерном пространстве.
multi sub dist($x) { return $x; } multi sub dist($x, $y) { return sqrt($x ** 2 + $y ** 2); } multi sub dist($x, $y, $z) { return sqrt($x ** 2 + $y ** 2 + $z ** 2); } say dist(2); # 2 say dist(3, 4); # 5 say dist(8, 9, 12); # 17
В этом примере функции различаются исключительно числом аргументов. При вызове, Perl 6 однозначно определяет, какой из вариантов следует вызвать.
Типы аргументов
Второй пример использует для выбора тип аргумента. Совсем не обязательно для этого создавать собственные классы, можно начать с типов, встроенных в язык.
multi sub mirror(Int $i) { -$i } multi sub mirror(Str $s) { $s.flip } say mirror(42); # -42 say mirror('42'); # 24
Здесь функция возвращает должна вернуть «зеркальный» вариант своего аргумента, чтобы это ни значило. Точный смысл вы в праве установить самостоятельно для каждого типа аргумента. Для целых чисел (Int $i) это будет противоположное число, а для строки (Str $s) — строка, записанная в обратном порядке.
Точно так же возможно определить свой класс и дополнить мультифункцию соответствующим вариантом. Во-первых, определим класс для светофора, у которого есть текущее значение value. Во-вторых, создадим мультифункцию, которая ожидает такой объект и инвертирует его:
class TrafficLight { has Str $.value; } multi sub mirror(TrafficLight $obj) { $obj.value eq 'red' ?? 'green' !! 'red' } say mirror(TrafficLight.new(value => 'green')); # red
Ограничение на значение аргументов
Еще один вариант — выбор одной из мультифункций, опираясь на значение аргументов. Для простоты рассмотрим простейший вариант функции с одним аргументом, которая для коротких слов печатает предупреждение о том, что пароль слишком короткий:
multi sub set-password($pwd) { say "Password '$pwd' is OK" } multi sub set-password($pwd where {$pwd.chars <= 5}) { say "Password '$pwd' is too short!" } set-password('He11oW0rld!'); # OK set-password('qwert'); # too short!
Во втором варианте функции значение аргумента уточняется с помощью ключевого слова where:
multi sub set-password($pwd where {$pwd.chars <= 5}) { . . . }
Эта функция будет вызвана только в том случае, если выполняется условие $pwd.chars <= 5, то есть когда строка недостаточно длинна.
Обратите внимание, что для первого варианта функции никаких ограничений нет, тем не менее, Perl 6 принимает совершенно правильно решение, опираясь на весь комплект функций, и не активирует этот вариант для короткой строки.
На сегодня все. Завтра будем использовать мультифункции для создания рекурсии.
Про where я не знал, интересно. Ещё я видел такую штуку:
https://docs.perl6.org/type/Signature#Long_Names
multi sub f(Int $i, Str $s;; :$b) { say «$i, $s, {$b.perl}» };
multi sub f(Int $i, Str $s, :$b) { say «$i, $s, {$b.perl}» };
f(10, ‘answer’);
f(10, ‘answer’, :b(100));