将子集中正则表达式的命名捕获放入签名中的变量中

Put named capture from regex in Subset into a variable in the signature

考虑

subset MySubset of Str where * ~~ /^ \d $<interesting> = ( \d+ ) $/;

现在我想在我的签名中使用子集作为类型,但是通过解包将捕获的部分放入变量中,有点像

sub f( MySubset $( :$interesting ) )
{
    say $interesting;
}

f( "12345678" ); # should say 2345678

这当然行不通。甚至可以这样做吗?

我很确定 wheres(和 subsets)只需回答 True/False。布拉德同意。

基本上总是元编程问题的答案,但我认为你不是这个意思(而且几乎从来没有深入挖掘)。

因此,这里有几种方法可以让您获得接近您想要的东西。


基于 Brad 的见解的(由于 MONKEYing 而可疑)解决方案:

use MONKEY;
augment class Str {
  method MyMatch { self ~~ / ^ \d $<interesting> = ( \d+ ) $ / }
}

class MyMatch is Match {}

sub f( MyMatch() $foo (:$interesting) ) { say ~$interesting }

f( "12345678" ); # 2345678

坏消息是,即使字符串不匹配,sub 调度仍然有效。该文档清楚地表明强制方法(上面的method MyMatch)目前不能发出失败信号:

The method is assumed to return the correct type — no additional checks on the result are currently performed.

人们可以希望有一天 augmenting a class 将是一件正式值得尊敬的事情(而不是要求 use MONKEY...)并且强制可以表示失败。那时我认为这可能是一个不错的解决方案。


上面的变体绑定到 $/ 所以你可以使用 $<interesting>:

use MONKEY;
augment class Str {
  method MyMatch { self ~~ / ^ \d $<interesting> = ( \d+ ) $ / }
}

class MyMatch is Match {}

sub f( MyMatch() $/ ) { say ~$<interesting> }

f( "12345678" ); # 2345678

另一种避免 MONKEY 绕过的方法是按照您的建议使用 subset,但将正则表达式和子集分开:

my regex Regex { ^ \d $<interesting> = ( \d+ ) $ }
subset Subset of Str where &Regex;

sub f( Subset $foo ; $interesting = ~($foo ~~ &Regex)<interesting> )
{
    say $interesting;
}

f( "12345678" ); # 2345678

备注:

  • 正则表达式至少解析输入值两次。首先在Subset中决定调用是否派发到sub。但是匹配的结果被丢弃了——值以字符串形式到达。然后正则表达式再次匹配 ,因此可以解构匹配。对于当前的 Rakudo,如果 submulti,情况会更糟——正则表达式将被使用 three 次,因为 Rakudo 目前同时进行了一次试验绑定作为决定匹配哪个 multi 的一部分,然后 另一个 绑定实际调用。

  • 参数可以根据之前的参数进行设置。我已经用 $interesting 做到了。签名可以包含属于调度决策一部分的参数,而其他参数则不是。它们由分号分隔。我将这两个特征结合起来创建了另一个变量,我想你可能会认为这是一件积极的事情。您的评论表明您不这样做,这非常合理。 :)

子签名解包是关于将值转换为 Capture 并与之匹配。

class Point {
  has ( $.x, $.y );
}

my ( :$x, :$y ) := Point.new( x => 3, y => 4 ).Capture;

say "[$x,$y]"; # [3,4]

因为 Str 没有名为 $.interesting 的 public 属性,它不会匹配。

子集只是额外的代码,用于比其他方式更完整地检查值。它不会将值转换为新类型。


如果您使用 $<interesting>.

可能会更有效
sub f( MySubset )
{
    say $<interesting>;
}

当然,因为块有自己的$/,这也行不通。


虽然将信息从子集传递到签名可能很好,但我不知道如何去做。


附带说明一下,where 已经进行了智能匹配,因此在其中使用 ~~ 是一个非常糟糕的主意。

这基本上就是您的子集的工作方式:

"12345678" ~~ ( * ~~ /…/ )

在这种特殊情况下,您可以只使用 .substr

sub f( MySubset $_ )    {
    .substr(1)
}

我想不出使用 subset 类型的方法,但是 一种方法 - 有点...创造力 - 进行匹配并在签名中解压。

Match 继承自 Capture,因此在签名中解压一个很简单——只要我们能安排一个包含我们希望的 Match 的参数打开包装。一种方法是引入一个带有默认值的进一步参数。我们不能真正阻止任何人传递给它——尽管我们可以通过使用匿名命名参数来让它变得很痛苦。因此,如果我们这样写:

sub foo($value, :$ (:$col, :$row) = $value.match(/^$<col>=[<:L>+]$<row>=[\d+]$/)) {
    say $col;
    say $row;
}

并将其命名为foo("AB23"),输出为:

「AB」
「23」

最后,我们可以将规则分解为命名标记,从而实现:

‌‌my token colrow { ^$<col>=[<:L>+]$<row>=[\d+]$ }
sub foo($value, :$ (:$col, :$row) = $value.match(&colrow)) {
    say $col;
    say $row;
}