Perl 6 - 匿名状态变量及它们的工作原理

Anonymous State Variables And How They Work


当调试代码的时候, 我经常添加一个计数变量以用于循环, 所以我能跟踪发生了什么, 或我能在代码片段中处理正迭代的部分数据集:

my $event-no = 0;
for get_events() -> $event {
    $event-no++;
    process-event($event);
    last if $event-no >= 5;
}

如果你正在调试, 或者你正尝试在单行中节省空间, Perl 6 实际上有一个匿名状态变量(anonymous state variables)标记, 用不含名字的 $符号来标示(你还可以在很多可迭代对象身上使用 kv 方法来完成类似的东西, 但是匿名的 $ 更普遍。)

for get_events() -> $event {
    process-event($event);
    last if ++$ >= 5;
}

然而, 注意; 下面这样的用法是没有效果的:

for get_events() -> $event {
    process-event($event);
    $++;
    last if $ >= 5;
}

好了, 为什么是那样的?

Use the Source


好吧, 让我们来看看 Rakudo 源代码, 可以吗?

如你所想, 在 Perl 6 Grammar 中查找 $ 是怎样被解析的将会是一个很困难的任务。所以我们让编译器自己来帮助我们! 我们会使用一个小例子:

for ^10 { $++ }

并让 Rakudo 吐出它生成的 AST, 专门用于查找变量:

  $ perl6 --target=ast -e 'for ^10 { $++ }' | grep Var
      - QAST::Var(attribute $!do)
      - QAST::Var(attribute $!do)
    - QAST::Var(local __args__ :decl(param))
          - QAST::Var(lexical $¢ :decl(contvar))
          - QAST::Var(lexical $! :decl(contvar))
          - QAST::Var(lexical $/ :decl(contvar))
          - QAST::Var(lexical $_ :decl(contvar))
          - QAST::Var(lexical GLOBALish :decl(static))
          - QAST::Var(lexical EXPORT :decl(static))
          - QAST::Var(lexical $?PACKAGE :decl(static))
          - QAST::Var(lexical ::?PACKAGE :decl(static))
          - QAST::Var(lexical $=finish :decl(static))
                - QAST::Var(lexical $ANON_VAR__1 :decl(statevar))
                - QAST::Var(lexical $_ :decl(param))
                      - QAST::Var(lexical $ANON_VAR__1) :BY<EXPR/POSTFIX W> :nosink<?> :WANTED $
                        - QAST::Var(lexical $ANON_VAR__1) :BY<EXPR/POSTFIX W> :nosink<?> :WANTED $
          - QAST::Var(lexical $=pod :decl(static))
          - QAST::Var(lexical !UNIT_MARKER :decl(static))
            - QAST::Var(local ctxsave :decl(var))
            - QAST::Var(contextual $*CTXSAVE)
              - QAST::Var(local ctxsave)
                - QAST::Var(local ctxsave)
                - QAST::Var(local ctxsave)

你可能不会立即看到它, 但是那儿有一个可疑的声明: $ANON_VAR__1。现在我们有了一个搜索字符串并想得到更多相关的结果, 用 ack 这样的工具搜索源代码, 我们会找到 src/Perl6/Actions.nqp这个文件。让我们深入进去!

# taken from rakudo@85d20f3
sub declare_variable($/, $past, $sigil, $twigil, $desigilname, $trait_list, $shape?, :@post) {
    ...
    elsif $desigilname eq '' {
        if $twigil {
            $/.CURSOR.panic("Cannot have an anonymous variable with a twigil");
        }
        $name    := QAST::Node.unique($sigil ~ 'ANON_VAR_');
        $varname := $sigil;
    }
    ...
}

所以这部分代码(搜索 ANON_VAR 时唯一的结果)告诉我们当我们声明一个符号后面没有名字的变量时, 我们应该生成一个唯一的名字。

How Did We Get Here?


那很好, 但是我们怎么从 grammar 中到达那里? 这种情况下我使用的小技巧就是抛出一个异常并查看回溯发生在哪?

sub declare_variable($/, $past, $sigil, $twigil, $desigilname, $trait_list, $shape?, :@post) {
    ...
    elsif $desigilname eq '' {
        if $twigil {
            $/.CURSOR.panic("Cannot have an anonymous variable with a twigil");
        }
+       if nqp::atkey(nqp::getenvhash(), 'ROB_DEBUG') {
+           $/.CURSOR.panic("here I am!");
+       }
        $name    := QAST::Node.unique($sigil ~ 'ANON_VAR_');
        $varname := $sigil;
    }
    ...
}

重新编译之后, 打开 ROB_DEBUG 环境变量并运行, 并使用 --ll-exception, 来确保内部构件被包含进了堆栈跟踪中:

$ ROB_DEBUG=1 perl6 --ll-exception -e 'for ^10 { $++ }'

我不会临时包含这个堆栈跟踪, 但是你可以自己生成它如果你愿意追随的话。通过查看出现在提到 Actions.nqp:3160(我插入异常的地方) 后面提到 Grammar.nqp 的第一个堆栈跟踪项, 我们来到 Grammar.nqp中的 token variable:

# also taken from rakudo@85d20f3
token variable {
    :my $*IN_META := '';
    [
    | :dba('infix noun') '&[' ~ ']' <infixish('[]')>
    | <sigil> <twigil>? <desigilname>
      [ <?{ !$*IN_DECL && $*VARIABLE && $*VARIABLE eq $<sigil> ~ $<twigil> ~ $<desigilname> }>
        { self.typed_panic: 'X::Syntax::Variable::Initializer', name => $*VARIABLE } ]?
    | <special_variable>
    | <sigil> $<index>=[\d+]                              [<?{ $*IN_DECL }> <.typed_panic: "X::Syntax::Variable::Numeric">]?
    | <sigil> <?[<]> <postcircumfix>                      [<?{ $*IN_DECL }> <.typed_panic('X::Syntax::Variable::Match')>]?
    | <?before <sigil> <?[ ( [ { ]>> <!RESTRICTED> <?{ !$*IN_DECL }> <contextualizer>
    | $<sigil>=['$'] $<desigilname>=[<[/_!¢]>]
    | {} <sigil> <!{ $*QSIGIL }> <?MARKER('baresigil')>   # try last, to allow sublanguages to redefine sigils (like & in regex)
    ]
    [ <?{ $<twigil> && $<twigil> eq '.' }>
        [ <.unsp> | '\\' | <?> ] <?[(]> <!RESTRICTED> <arglist=.postcircumfix>
    ]?
    { $*LEFTSIGIL := nqp::substr(self.orig(), self.from, 1) unless $*LEFTSIGIL }
}

这段代码对你没有什么意义如果你初学 Perl 6的话, 更不用说 Rakudo 源代码了。我认为这一句是最重要的:

| {} <sigil> <!{ $*QSIGIL }> <?MARKER('baresigil')> # try last, to allow sublanguages to redefine sigils (like & in regex)

这个分支接受由符号唯一组成的变量。所以 token variable 匹配源代码中的每个裸的 $ 实例, 并且每次发生都会调用 Actions::declare_variable, 生成不同的变量, 我用这个片段来说没明:

for ^3 {
    say ++$;
    say ++$;
}
=output
1
1
2
2
3
3

所以, 对于匿名状态变量你只能执行非常简单的操作。记住你也可以使用匿名数组或匿名散列变量来处理东西:

for ^10 {
    say((@).push($_));
}

但是在正式代码中不建议这么用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容