在函数中§

查看主要文档 在上下文中 了解 sub MAIN

在 Raku 脚本中声明 sub MAIN 不是强制性的,但你可以提供一个来为你的脚本创建一个 命令行界面

在命令行界面中§

查看主要文档 在上下文中 了解 sub MAIN

在所有相关的入口阶段(BEGINCHECKINITPREENTER)运行后,并且脚本的 主线 已经执行后,将执行具有特殊名称 MAIN 的子例程。如果没有 MAIN 子例程,则不会发生错误:你的脚本只需要在脚本的主线中完成工作,例如解析参数。

MAIN 子例程的任何正常退出都将导致退出代码 0,表示成功。将忽略 MAIN 子例程的任何返回值。如果抛出一个未在 MAIN 子例程中处理的异常,则退出代码将为 1。如果分派到 MAIN 失败,则会在 STDERR 上显示一条用法消息,并且退出代码将为 2

命令行参数存在于 @*ARGS 动态变量中,并且可以在调用 MAIN 单元之前在脚本的主线中进行更改。

(多重)子例程 MAIN 的签名决定了使用标准 多重分派 语义实际调用哪个候选者。

一个简单的示例

# inside file 'hello.raku' 
sub MAIN($name{
    say "Hello $name, how are you?"
}

如果你在没有任何参数的情况下调用该脚本,你将得到以下用法消息

$ raku hello.raku
Usage:
  hello.raku <name>

但是,如果你为参数给定一个默认值,则无论是否指定名称,运行脚本始终有效

# inside file 'hello.raku' 
sub MAIN($name = 'bashful'{
    say "Hello $name, how are you?"
}
$ raku hello.raku
Hello bashful, how are you?

$ raku hello.raku Liz
Hello Liz, how are you?

另一种方法是将 sub MAIN 设置为 multi

# inside file 'hello.raku' 
multi MAIN()      { say "Hello bashful, how are you?" }
multi MAIN($name{ say "Hello $name, how are you?"   }

这将给出与上面示例相同的结果。你应该使用哪种方法来实现所需的目标完全取决于你。

如果你想传递不确定的数量的参数在 sub MAIN 中处理,你可以使用 slurpy 参数

# inside file 'hello-all.raku' 
sub MAIN(*@all{ @all.map: -> $name { say "Hello, " ~ $name } }
$ raku hello-all.raku peter paul mary
Hello, peter
Hello, paul
Hello, mary

一个更复杂的示例,使用单个位置参数和多个命名参数,还显示了 where 子句也可以应用于 MAIN 参数

# inside "frobnicate.raku" 
sub MAIN(
  Str   $file where *.IO.f = 'file.dat',
  Int  :$length = 24,
  Bool :$verbose
{
    say $length if $length.defined;
    say $file   if $file.defined;
    say 'Verbosity ', ($verbose ?? 'on' !! 'off');
}

file.dat 存在时,它将这样工作

$ raku frobnicate.raku
24
file.dat
Verbosity off

或者使用 --verbose 这样工作

$ raku frobnicate.raku --verbose
24
file.dat
Verbosity on

如果文件 file.dat 不存在,或者你指定了另一个不存在的文件名,你将获得从 MAIN 子例程的自省中创建的标准用法消息

$ raku frobnicate.raku doesnotexist.dat
Usage:
  frobnicate.raku [--length=<Int>] [--verbose] [<file>]

尽管你不需要在代码中执行任何操作来执行此操作,但它仍然可能被认为有点简洁。但是,可以通过使用 pod 特性提供提示,轻松地改善该用法消息

# inside "frobnicate.raku" 
sub MAIN(
  Str   $file where *.IO.f = 'file.dat',  #= an existing file to frobnicate 
  Int  :$length = 24,                     #= length needed for frobnication 
  Bool :$verbose,                         #= required verbosity 
{
    say $length if $length.defined;
    say $file   if $file.defined;
    say 'Verbosity ', ($verbose ?? 'on' !! 'off');
}

这将像这样改进用法消息

$ raku frobnicate.raku doesnotexist.dat
Usage:
  frobnicate.raku [--length=<Int>] [--verbose] [<file>]

    [<file>]          an existing file to frobnicate
    --length=<Int>    length needed for frobnication
    --verbose         required verbosity

从 2021.03 版本开始,传递给单个命名参数的值也可以用空格分隔。考虑一个具有以下源代码的 demo 程序

subset name of Any where Str|True;
subset port of Str;
multi MAIN(
    $file,
    name :$profile,    #= Write profile information to a file 
    port :$debug-port#= Listen for debugger connections on the specified port 
    Bool :v($verbose), #= Display verbose output 
 
{}
multi MAIN("--process-files"*@images{}

该程序生成以下用法消息

Usage:
  demo [--profile[=name]] [--debug-port=<port>] [-v] <file>
  demo --process-files [<images> ...]

    --profile[=name]       Write profile information to a file
    --debug-port=<port>    Listen for debugger connections on the specified port
    -v                     Display verbose output

以下为调用 demo 的有效方法

demo --profile ~/foo
demo --profile=/tmp/bar ~/foo
demo --debug-port 4242 ~/foo
demo --debug-port=4242 ~/foo
demo -v ~/foo
demo --process-files *.jpg

但是,以下方法无效

demo --profile /tmp/bar ~/foo
demo --debug-port ~/foo

第一个方法无效,因为 /tmp/bar~/foo 均被解析为位置参数,这意味着 demo 使用了过多的位置参数进行调用。第二个方法无效,因为 ~/foo 被解析为 --debug-port 的参数,因此 demo 缺少必需的位置参数。

以下是其工作原理;Raku 区分三种类型的选项

  • 布尔选项(如 -v),从不带参数;它们要么存在,要么不存在。

  • 具有必需参数的选项(如 --debug-port),始终带参数。如果您使用 = 为其提供参数,它将使用该参数;如果没有,它将使用以下参数。

  • 具有可选参数的选项(如 --profile),在带参数和不带参数的情况下均有效。您只能使用 = 语法为这些参数提供选项;如果该选项后有空格,则表示它在未带参数的情况下被调用。

以下是生成每种类型参数的签名

  • 布尔选项:Bool 类型约束。

  • 具有必需参数的选项:不 .ACCEPT Bool 的类型。

  • 具有可选参数的选项:.ACCEPTS True 的类型(因为不带参数传递选项等效于传递 True

与任何其他子例程一样,MAIN 可以为其命名参数定义 别名

sub MAIN(
  Str   $file where *.IO.f = 'file.dat',  #= an existing file to frobnicate 
  Int  :size(:$length= 24,              #= length/size needed for frobnication 
  Bool :$verbose,                         #= required verbosity 
{
    say $length if $length.defined;
    say $file   if $file.defined;
    say 'Verbosity ', ($verbose ?? 'on' !! 'off');
}

在这种情况下,这些别名也将作为备选方案与 --help 一起列出

Usage:
  frobnicate.raku [--size|--length=<Int>] [--verbose] [<file>]

    [<file>]                 an existing file to frobnicate
    --size|--length=<Int>    length needed for frobnication
    --verbose                required verbosity

Enumeration 可用于签名,其中参数会自动转换为其对应的 enum 符号

enum Flag  (
    FLAG_FOO => 0b001,
    FLAG_BAR => 0b010,
    FLAG_BAZ => 0b100,
);
 
sub MAIN(Flag $flag = FLAG_FOO{
    say "Flagging $flag";
}

这将与以下内容正确配合使用

raku MAIN-enum.raku FLAG_BAR

但如果使用并非 Flag 的内容进行调用,则会终止。