Ответ 1
TL; DR Вы спрашиваете о функциях variadic. 1 Простое использование просто. Некоторые особенности P6, в частности позиционные и именованные аргументы и вариативная деструктуризация, добавляют некоторые морщины. Также посмотрите другие ответы, которые тоже очень полезны.
переменное количество аргументов
Простое использование простой переменной функции:
sub variadic (|args) { say args .elems }
variadic(); # 0
variadic('1'); # 1
variadic('1', '2'); # 2
|foo
параметр хлебает все остальные аргументы в Capture
, связанный с идентификатором sigilless foo
:
sub variadic ($a, @b, %c, |others) { say others[0] }
variadic 1, [2,3], (:foo), 4, [5,6], (:bar); # 4
В приведенном выше примере others
параметры "хлебают" 1 до последних трех перечисленных аргументов, начиная с 4
.
Вариационные вариации
Вещи не всегда просты:
variadic(1, 2); # 2 -- works but args are Ints
variadic(:foo,:bar); # 0 -- where did :foo, :bar go?
variadic((:foo)); # 1 -- works; arg is Pair (:foo)
variadic((1, 2)); # 1 -- works; arg is List (1,2)
В остальной части этого ответа я объясняю:
-
Ограничивающие аргументы, например, гарантирующие, что они все строки.
-
Позиционные против именованных аргументов;
**@foo
и*%foo
-
Вариационная позиционная деструкция;
[email protected]
и*@foo
Сдерживающие аргументы
переменное количество строк
Вы можете наложить любые ограничения на аргументы slurped, используя предложение where
. (Который вы можете в свою очередь упаковать в subset
если хотите.)
Например, чтобы ограничить все аргументы типа Str
:
sub variadic (|args where .all ~~ Str) { say args .elems }
variadic(); # 0
variadic('1'); # 1
variadic('1', '2'); # 2
variadic(1); # Constraint type check failed
Позиционные против именованных аргументов; **@foo
и *%foo
P6 поддерживает позиционные и именованные аргументы.
Использование |foo
в сигнатуре всегда захватывает все оставшиеся аргументы, как позиционные, так и именованные:
sub slurps-into-WHAT (|args) { say WHAT args }
slurps-into-WHAT(); # (Capture)
Capture
хранит позиционные аргументы во внутреннем списке, доступном через позиционную подписку (например, args[...]
), и именованные аргументы в хэше, доступном через ассоциативную подписку (т.е. args<...>
или args{...}
):
sub variadic (|args) { say " {args[1]} {args<named2>}" }
variadic('pos0', 'pos1', named1 => 42, named2 => 99); # pos1 99
Иногда предпочтительнее собрать только именованные аргументы, либо позиционные, либо собрать оба, но в отдельных параметрах.
Чтобы собрать именованные аргументы, используйте параметр вида *%foo
(один префикс звездочки и хэш-аргумент):
sub variadic-for-nameds (*%nameds) { say %nameds }
variadic-for-nameds(:foo, :bar); # {bar => True, foo => True}
(Обратите внимание, что все методы собирают именованные аргументы таким образом, даже если их сигнатура явно не говорит об этом. 2)
Чтобы собрать позиционные аргументы, используйте параметр в виде **@foo
(два префикса звездочки, за которыми следует массив arg):
sub variadic-for-positionals (**@positionals) { say @positionals }
variadic-for-positionals(1, 2, 3); # [1 2 3]
Вариационная позиционная деструкция; [email protected]
и *@foo
P6 предоставляет ряд возможностей по деструктуризации аргументов, не связанных с переменным числом аргументов.
Первая переменная форма параметра деструктуризации позиционных элементов это [email protected]
. Это имеет тот же эффект, что и **@foo
за исключением одного случая; если параметр variadic получает только один аргумент, и этот аргумент является списком или массивом, то этот параметр привязывается к содержимому этого списка или массива, удаляя контейнер списка/массива:
sub variadic-plus ([email protected]) { say @positionals }
variadic-plus(1,2,3); # says same as for **@positionals
variadic-plus(1); # says same as for **@positionals
variadic-plus([1]); # [1] -- instead of [[1]]
variadic-plus((1,2,3)); # [1 2 3] -- instead of [(1 2 3)]
Форма [email protected]
была введена для поддержки "правила единого аргумента". Он используется разработчиками ядра, пишу встроенные модули. Пользователи могут захотеть использовать его, когда они хотят того же поведения.
Другая форма разрушения вариационных позиционеров - *@foo
. Он делает то же самое, что и [email protected]
в том, что он извлекает содержимое из аргументов контейнера списка или массива и выбрасывает контейнер. Но это гораздо агрессивнее
-
Это делает это для всех аргументов.
-
Если аргумент является списком, а не массивом (
(...)
а не[...]
), то он спускается в этот список и рекурсивно повторяет упражнение, если элемент списка сам является другим внутренним списком или массивом.
Таким образом:
sub variadic-star (*@positionals) { say @positionals }
variadic-star((1,2),[3,4]); # [1 2 3 4]
variadic-star((1,2),(3,4,(5,6,(7,8)))); # [1 2 3 4 5 6 7 8]
variadic-star((1,2),(3,4,[5,6,(7,8)])); # [1 2 3 4 5 6 (7 8)]
(Обратите внимание, как он удалил контейнер из массива [5,6,(7,8)]
но не опустился в него.)
Одна последняя вещь; существуют ли переменные параметры деструктурирования? Кому ты рассказываешь. 3
Бонусный раздел: foo(...)
против foo (...)
(Я включил этот бонусный раздел в надежде, что он избежит путаницы. Если этот раздел сам по себе сбивает с толку, просто проигнорируйте его.)
Рутинные вызовы могут быть написаны с или без скобок вокруг их списка аргументов, и они означают одно и то же. Открывающая круглая скобка должна следовать сразу за именем процедуры, без пробелов:
sub foo (|args) { say args[0] }
foo 'a', 'b'; # a
foo('a', 'b'); # a
(Это правило применяется только к вызову подпрограммы, между именем подпрограммы и ее аргументами. Оно не применяется к объявлению подпрограммы, между именем подпрограммы и ее параметрами. Для последнего, объявления, вы не можете оставлять пробел или использовать пробел как я выше с sub foo (|args)
и это не имеет значения.)
Если вы вставляете пробел между foo
и открывающей скобкой в вызове, вы пишете что-то другое:
foo ('a', 'b'); # (a b)
Это вызывает foo
с одним аргументом, одним списком ('a', 'b')
в отличие от foo('a', 'b')
который вызывает foo
с двумя аргументами, двумя значениями внутри скобок.
Следующие вызовы foo
с двумя аргументами, оба из которых являются списками:
foo ('a', 'b', 'c'), ('d', 'e', 'f'); # (a b c)
Вы можете вкладывать круглые скобки:
foo( ('a', 'b', 'c'), ('d', 'e', 'f') ) ; # (a b c)
foo (('a', 'b', 'c'), ('d', 'e', 'f')) ; # ((a b c) (d e f))
foo( (('a', 'b', 'c'), ('d', 'e', 'f')) ) ; # ((a b c) (d e f))
Последние два вызова foo
получают один аргумент, один список ( ('a', 'b', 'c'), ('d', 'e', 'f') )
(который содержит два внутренних списка),
Сноски
1 Стандартный отраслевой термин для этого - функция с вариацией. На момент написания этого ответа компилятор Rakudo P6 использовал стандартный отраслевой термин ("variadic") в сообщениях об ошибках, но официальный документ P6 имеет тенденцию использовать слово "slurpy" вместо "variadic" и говорить о "неаккуратных аргументах" ".
2 Методы всегда имеют неявный переменный параметр с именем %_
если он не указан явно:
say .signature given my method foo {} # (Mu: *%_)
3 Язык P6 и/или компилятор Rakudo P6 в настоящее время позволяет записывать параметры в виде +%foo
и **%foo
. На самом деле не имеет смысла разрушать различные имена. Возможно, это объясняет, почему обе эти формы делают безумные вещи:
-
**%foo
, по-видимому, неотличим от%foo
. -
+%foo
, по-видимому, неотличим от**@foo
за исключением использования идентификатора%foo
вместо@foo
. Объект, связанный с%foo
являетсяArray
!