- MAIN 子例程
- 命名参数和位置参数
- 命名参数
- 位置参数
- Multi dispatch
- 组合命名参数和位置参数
- 可选参数和必选参数
- 别名或备用命名参数
- USAGE 子例程
Sub MAIN
在 Raku 中,命令行参数的解析是使用 MAIN
子例程完成的,MAIN 子例程是一个特殊的子例程,它根据签名解析命令行参数。与其他子程序一样,您可以使用命名参数和位置参数,可选(和必需)参数,multiple dispatch 等。
通过定义 MAIN
子例程,编译器会自动生成 USAGE 子例程。可以修改此子例程以返回自定义的 usage 信息。所有的命令行参数也可以在特殊变量 @*ARGS
中使用,它可以在 MAIN
处理之前进行更改。
命名参数和位置参数
命名参数
我们从一个简单的程序开始(保存为 prog.p6
):
use v6;
sub MAIN(
Str :$name = 'John',
Str :$last-name = 'Doe',
) {
my $formatted-name = "$name.tc() $last-name.tc()";
say $formatted-name;
}
在这个 MAIN 子程序中,我们创建了两个命名参数,$name
和 $last-name
,带有类型约束(Str
),方法是在子例程的签名给每个变量添加 :
。这些参数还具有默认值,这是通过为参数赋值来实现的。在这种情况下,我们将 $name
设置为默认值 ‘John’,将 $last-name
设置为 ‘Doe’。如果执行 prog.p6
时命令行参数与 MAIN
签名匹配,则会打印出格式化的全名:
$ raku prog.p6
John Doe
$ raku prog.p6 --name='carl' --last-name='sagan'
Carl Sagan
$ raku prog.p6 --last-name='sagan' --name='carl'
Carl Sagan
如您所见,命名参数可以按您想要的任意顺序传递。
如果没有匹配 MAIN
签名,那么我们会收到一条 usage 信息:
$ p6 prog.p6 --name='Carl' --last-name='Sagan' --career='astronomer'
prog.p6 [--name=<Str>] [--last-name=<Str>]
位置参数
如果我们想要使用位置参数,我们可以重新定义子例程的签名以仅解析位置参数。与之前的版本一样,我们将拥有参数的默认值,但这些参数现在是位置的,必须按签名定义的顺序提供:
use v6;
sub MAIN(
Str $name = 'John', # No colon(:) in the variable
Str $last-name = 'Doe', # No colon(:) in the variable
) {
my $formatted-name = "$name.tc() $last-name.tc()";
say $formatted-name;
}
使用匹配的签名执行 prog.p6
将打印以下输出:
$ raku prog.p6
John Doe
$ raku prog.p6 carl sagan
Carl Sagan
没有一个,它给我们以下 usage 信息:
$ raku prog.p6 carl sagan astronomer
prog.p6 [<name>] [<last-name>]
multiple dispatch
我们可能更喜欢在我们的小程序中使用命名参数和位置参数。正如我们之前提到的,我们可以使用 multiple dispatch(具有相同名称但具有不同签名的多个子例程)来声明具有其自己的签名的多个 MAIN
子例程。为此,每个候选者都使用 multi
关键字而不是 sub
来声明:
use v6;
multi MAIN(
Str :$name = 'John',
Str :$last-name = 'Doe',
) {
my $formatted-name = "$name.tc() $last-name.tc()";
say $formatted-name;
}
multi MAIN(
Str $name = 'John',
Str $last-name = 'Doe',
) {
my $formatted-name = "$name.tc() $last-name.tc()";
say $formatted-name;
}
两个 MAIN
子例程看起来非常相似,但它们具有不同的签名,这些签名描述了预期的命令行参数。
如果我们使用与任何 MAIN
签名匹配的命令行参数执行 prog.p6
,我们将获得格式化的全名:
$ p6 prog.p6 --name='ada' --last-name='lovelace'
Ada Lovelace
$ p6 prog.p6 marcus aurelius
Marcus Aurelius
如果没有匹配的签名,我们将获得一个有用的 usage 信息,详细说明 MAIN
子例程的可能签名:
$ p6 prog.p6 --name='Ada' --last-name='Lovelace' --title='Ms'
Usage:
prog.p6 [--name=<Str>] [--last-name=<Str>]
prog.p6 [<name>] [<last-name>]
组合命名参数和位置参数
在我们的例子中定义不同的签名来处理不同的命令行参数,命名参数和位置参数在我们的例子中是很好的。但是,如果您想在单个 MAIN
签名中混合命名参数和位置参数,该怎么办?尽管必须在命名参数之前定义位置参数,但这可以非常容易地完成。
让我们通过向第一个 multi
子例程添加位置参数来更新我们的简单程序 prog.p6
的最新迭代:
use v6;
multi MAIN(
Str $title = 'Mr', # Our positional parameter defined before named ones
Str :$name = 'John',
Str :$last-name = 'Doe',
) {
my $formatted-name = "$title.tc() $name.tc() $last-name.tc()";
say $formatted-name;
}
multi MAIN(
Str $title,
Str $name = 'John',
Str $last-name = 'Doe',
) {
my $formatted-name = "$title.tc() $name.tc() $last-name.tc()";
say $formatted-name;
}
可选参数和必选参数
默认情况下,命名参数是可选的。尽管如此,可以通过在相应的词法变量后面附加一个 !
来标记它们。例如,MAIN( :$first, :$second, :$operator ){ ... }
如果没有一些命令行参数但是 MAIN( :$first!, :$second!, :$operator! ){ ... }
假设现在需要参数并且调用者必须传递必要的参数,$ operator!){…}会这样做。
另一方面,默认情况下需要位置参数,但可以通过将相应的词汇变量附加 ?
来将其标记为可选。例如,如果在没有命令行参数但是 MAIN( $first, $second, $operator ){ ... }
的情况下调用MAIN($ first,$ second,$ operator){…}将打印一条用法消息不会因为参数现在是可选的。
通过设置默认值,也可以将位置参数定义为可选,例如我们如何使用$MAIN和$ MAIN中的$ last-name($ title,$ name =‘John’,$ last-name =‘Doe’){.. 。}。
别名或备用命名参数
命名参数及其别名通过使用冒号对语法(:)提供。冒号的存在:将决定我们是否正在创建一个新的命名参数。 让我们修改prog.p6中的第一个多数来包含一些别名:
use v6;
multi MAIN(
Str $title = 'Mr',
Str :$name = 'John',
Str :last-name($surname) = 'Doe',
Bool :p(:$print),
) {
my $formatted-name = "$title.tc() $name.tc() $surname.tc()";
if $print {
say $formatted-name;
}
}
...
这个MAIN定义了两种别名: :last-name($ surname)只将传递给命令行参数–last-name的内容别名为变量$ surname(注意缺少:)。这意味着$ surname将只是别名变量的名称,它不会创建新的命名参数:
$ p6 prog.p6 --name='alan' --last-name='turing' -p
Alan Turing
$ p6 prog.p6 --name='alan' --surname='turing'
Usage:
pos-named.p6 [--name=<Str>] [--last-name=<Str>] [-p|--print] [<title>]
- :$ print不仅是别名变量的名称,还会是一个新的命名参数,旁边:p:
$ p6 prog.p6 --name='alan' --last-name='turing'
$ p6 prog.p6 --name='alan' --last-name='turing' -p
Alan Turing
$ p6 prog.p6 --name='alan' --last-name='turing' -print
Alan Turing
您可能已经注意到,如果我们要打印人员的格式化全名,则必须现在指定标志-p(或-print)。这是因为Bool类型使$ print成为二进制标志,如果不存在则为False。如果被调用,则标志为True,这使得我们可以执行简单的if * print {…}语句。 使用别名是为参数创建长格式和短格式选项名称的简便方法。我们可以进一步修改prog.p6中的第一个multi,以便为–name和–last-name提供一个简短的表单选项名称:
use v6;
multi MAIN(
Str $title = 'Mr',
Str :n(:$name) = 'John',
Str :l(:last-name($surname)) = 'Doe',
Bool :p(:$print),
) {
my $formatted-name = "$title.tc() $name.tc() $surname.tc()";
if $print {
say $formatted-name;
}
}
...
通过使用不同的表单选项执行prog.p6,我们得到:
p6 prog.p6 --name='alan' --last-name='turing' -print
Mr. Alan Turing
p6 prog.p6 -n='grace' -l='hopper' -p 'Ms'
Ms. Grace Hopper
如果没有匹配的签名,我们会收到以下用法消息:
p6 prog.p6 -n='alan' -l='turing' -p --career='mathematician'
Usage:
prog.p6 [-n|--name=<Str>] [-l|--last-name=<Str>] [-p|--print] [<title>]
prog.p6 [<title>] [<name>] [<last-name>]
Sub USAGE
如果没有匹配的签名,我们的小程序prog.p6的最新版本将打印以下用法消息:
Usage:
prog.p6 [-n|--name=<Str>] [-l|--last-name=<Str>] [-p|--print] [<title>]
prog.p6 <title> [<name>] [<last-name>]
这是由于在MAIN子程序没有提供匹配的签名时自动调用USAGE子程序。如果没有找到这样的子例程,编译器将输出默认生成的使用消息,这意味着我们可以定义它以提供更详细的(如果我们想要的话!)用法消息。 这是带有修改的USAGE子的prog.p6:
use v6;
multi MAIN(
Str $title = 'Mr',
Str :n(:$name) = 'John',
Str :l(:last-name($surname)) = 'Doe',
Bool :p(:$print),
) {
my $formatted-name = "$title.tc() $name.tc() $surname.tc()";
if $print {
say $formatted-name;
}
}
multi MAIN(
Str $title = 'Mr',
Str $name = 'John',
Str $last-name = 'Doe',
) {
my $formatted-name = "$title.tc() $name.tc() $last-name.tc()";
say $formatted-name;
}
sub USAGE() {
print Q:c:to/END/;
Usage:
{$*PROGRAM-NAME} [-n|--name=<Str>] [-l|--last-name=<Str>] [-p|--print] [<title>]
{$*PROGRAM-NAME} [<title>] [<name>] [<last-name>]
optional arguments:
-h, --help show this help message and exit
-n=PERSON_NAME, --name=PERSON_NAME
specify person's name
-l=PERSON_LAST_NAME, --last-name=PERSON_LAST_NAME
specify person's last name
-p , --print print person's full name
<title> specify person's title ('Mr' by default)
Examples:
{$*PROGRAM-NAME} --name='richard' --last-name='feynman' -p
{$*PROGRAM-NAME} --name='sophie' --last-name='germain' -p 'Ms'
{$*PROGRAM-NAME} 'leonhard' 'euler'
END
}
注意在使用消息中提到-h(和–help)标志,我们不需要显式定义它们,因为它们是自动生成的。如果我们现在使用–help(或-h)标志执行prog.p6或不提供匹配的签名,我们将获得新的用法消息:
Usage:
prog.p6 [-n|--name=<Str>] [-l|--last-name=<Str>] [-p|--print] [<title>]
prog.p6 [<title>] [<name>] [<last-name>]
optional arguments:
-h, --help show this help message and exit
-n=PERSON_NAME, --name=PERSON_NAME
specify person's name
-l=PERSON_LAST_NAME, --last-name=PERSON_LAST_NAME
specify person's last name
-p , --print print person's full name
<title> specify person's title ('Mr' by default)
Examples:
prog.p6 --name='richard' --last-name='feynman' -p
prog.p6 --name='sophie' --last-name='germain' -p 'Ms'
prog.p6 'leonhard' 'euler'
结论
这当然只是对MAIN和USAGE子程序的肤浅看法。就像在Raku中一样,总有比眼睛更多的东西。例如,如果您希望将命名参数放在命令行中的任何位置(即使在位置参数之后),您可以修改散列%* SUB-MAIN-OPTS以允许此行为。如果您想了解更多细节,我在下面提供了一些有用的链接。
也可以看看: