第五天 - 使用 Raku 签名解构参数

Destructure your Arguments with Raku Signatures

第五天 - 使用 Raku 签名解构参数

在许多其他关键的 Raku 特性中,我认为 Signatures 是众多"杀手级"特性之一。 它们的功能如此丰富而强大,我怀疑关于如何使用它们可以写一整本书。 我想探索一下我原来忽略但是非常珍惜的一些特定功能。

您可能已经看到了基本的子程序签名:

sub myfunc($x, $y, $z) {...}

它给函数声明了 3 个标量参数, 并在函数体里面给了它们 $x, $y, $z 的名字。

太简单了。

你可以更有爱心, 给它们加上指定的类型:

sub myfunc(Str $x, Int $y, Rat $z) {...}

你可以使用笑脸符号 :D 让参数值是有定义的:

sub myfunc(Str:D $x, Int:D $y, Rat:D $z) {...}

还有很多其它花哨的说明符你可以使用,在这里我不深入了。

但是如果你的参数更复杂呢? (不是 Complex - 虽然它也起作用..)

For example, you might want to restrict a specific parameter to a Positional argument like an Array, or an Associative one like a Hash using the respective sigils, @ or %. 例如,你可能想要将特定的参数限制为像 Array 这样的 Positional 参数,或者使用相应的 @ 或 % 符号将这个参数限制为像 Hash 这样的关联参数。

sub myfunc(%h) {...}

现在我可以使用一个散列来调用该函数:

myfunc(%( a => 1, b => 'this', c => 2.2));

如果我想验证那些特定的字段是否存在,我可以把代码放在函数的顶部来做到这一点:

sub myfunc(%h) {
    die "a must be an Int" unless %h<a> ~~ Int;
    die "b must be a Str"  unless %h<b> ~~ Str;
    die "c must be a Rat"  unless %h<c> ~~ Rat;
}

如果我还想简化引用那些字段的方式,我可以将它们赋值给其他变量:

sub myfunc(%h) {
    die "a must be an Int" unless %h<a> ~~ Int;
    die "b must be a Str"  unless %h<b> ~~ Str;
    die "c must be a Rat"  unless %h<c> ~~ Rat;

    my $a = %h<a>;
    my $b = %h<b>;
    my $c = %h<c>;
}

有点无聊,对吗?

Perl 签名参数解构来拯救你了! 我们可以在子例程签名自身中做所有的事情 - 只要放一个子签名(sub-signature)在后面即可。

sub myfunc(%h (Int :$a, Str :$b, Rat :$c)) {...}

解构 JSON

相当不错,但如果你有复杂的东西呢?

假如说一块儿有嵌套结构的 JSON,某些部分可能缺失了, 它们需要默认值, 等等。

use JSON::Fast;
my $item = from-json(q:to/END/);
    {
        "book" : {
            "title"  : "A Christmas Carol",
            "author" : "Charles Dickens"
        },
        "count" : 12,
        "tags" : [ "christmas", "santa"]
    }
    END

q:to/END/ 是一个 Raku heredoc,它直接在文本中直到 END,然后我们可以使用 JSON::Fastfrom-json() 将其解析为 perl 中的数据结构。 你可以在函数签名中描述整个 JSON 结构,以便接收以下内容:

sub myfunc(% (:%book (Str:D :$title, Str:D :$author), Int :$count,
              :@tags ($first-tag, *@other-tags)) )
{...}

现在,在函数体中,我可以将这些部分引用为 $title$author$count@tags。 为了方便起见,我还将标签分成了 $first-tag@other-tags

在块儿中使用签名

当然,签名对于子程序来说是幻想的,但是你也可以在块儿(Block)中使用签名和解构。 假设你有一个上面的 JSON 条目的数组,并希望通过一个 for 循环遍历它们? 只需在 for 的尖号块中使用解构签名即可:

for @itemlist -> % (:%book (Str:D :$title, Str:D :$author), Int :$count,
                    :@tags ($first-tag, *@other-tags))
{
    say "$title, $author, $count, @tags[], $first-tag, @other-tags[]"
}

注意在这种情况下,我甚至不需要散列本身,所以我省略了散列的名称,仅使用 作为匿名散列(关联)。

你甚至可以解构对象!

你有没有试过遍历一组对象,你所做的第一件事是调用一些访问器来获取一些属性? 当然,你可以使用 .attribute 和 主题化的迭代器,但是使用子签名,你可以做更多。

class Book {
    has $.title;
    has $.author;
    has $.count;
    has @.tags;
}

my @booklist =
    Book.new(title => 'A Christmas Carol',
             author => 'Charles Dickens',
             count => 12,
             tags => <ghost christmas>),

    Book.new(title => 'A Visit from St. Nicholas',
             author => 'Clement Clarke Moore',
             count => 4,
             tags => <santa christmas>);

for @booklist -> Book $b (:$title,:$author, :$count, :@tags) {
    say "$title, $author, $count, @tags[]";
}

如果您想检查类型或定义,或设置默认值,您都可以在签名中正确地执行。 如果您不喜欢对象属性的名称,则可以使用别名来重命名它们, 你开心就行。

结论

我发现解构参数在与数据库查询结果和 JSON 交互中非常有用。 您可以使用任何其他签名特性,包括指定类型,定义,可选性,默认值,使用别名重命名,使用子集约束或“where”从句,slurpies等。

节日快乐!

comments powered by Disqus