Perl6: переменное число аргументов для функции/подпрограммы

Я хочу иметь возможность запускать функцию с переменным количеством параметров в Perl6, но после прочтения https://docs.perl6.org/language/functions#Arguments я не вижу, как это можно сделать. Я вижу многочисленные ссылки на другие языки и предупреждение о том, что "Вопросы с похожими заголовками часто опровергаются", но я не вижу этого нигде в документации или в StackOverflow.

Я хочу сделать что-то вроде этого:

some-test($v1, $v2)

или же

some-test($v3)

но не иметь отдельную функцию, использующую multi для каждого

Как я могу создать подпрограмму Perl6, которая будет принимать переменное количество строк?

Ответ 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 !

Ответ 2

Если вы просто хотите иметь возможность принимать 1 или 2 значения, то проще всего отметить второе значение как необязательное:

sub some-test( $v1, $v2? ) { ... }

Или вы можете определить значение по умолчанию:

sub some-test( $v1, $v2="default" ) { ... }

Или, если вам нужно любое количество значений (1 или более), вы можете использовать slurpy с предложением where:

sub some-test( *@v where *.elems > 0 ) { ... }

Ответ 3

Вы можете использовать деструктуризацию подписи

sub some-test(*@all [$first, *@rest]) { ... } # must have 1 or more parameters