【问题标题】:Why am I getting unexpected results from chained calls to map?为什么我从对 map 的链式调用中得到意外结果?
【发布时间】:2011-01-06 12:05:03
【问题描述】:

我正在使用 Getopt::Lucid 处理 CLO,但遇到了一个有趣且意想不到的问题。以下代码:

push @clo_spec, map { Switch($_) } qw(-c -m -s -p),
                map { Switch($_) } qw(--help --man --usage --version),
                map { Switch($_) } qw(--debug --verbose),
                map { Param($_)  } keys %$rc_spec_ref
;

my $clo_o = Getopt::Lucid->getopt(\@clo_spec);

产生以下错误:

'Getopt::Lucid::Spec=HASH(0x9383847)' is not a valid option name/alias

现在,Getopt::Lucid 通过引用表示有效选项的字符串表达式进行配置,然后将这些字符串传递给返回祝福哈希的六个子例程之一。每个子程序代表一种选项;开关、计数器、参数、列表或密钥对。

有趣的是,如果去掉任意三个地图表达式,

push @clo_spec, #map { Switch($_) } qw(-c -m -s -p),
                map { Switch($_) } qw(--help --man --usage --version),
                #map { Switch($_) } qw(--debug --verbose),
                #map { Param($_)  } keys %$rc_spec_ref
;

然后一切正常。更有趣的部分是,如果您将每个地图表达式括在括号中,一切都可以正常工作:

push @clo_spec, (map { Switch($_) } qw(-c -m -s -p)),
                (map { Switch($_) } qw(--help --man --usage --version)),
                (map { Switch($_) } qw(--debug --verbose)),
                (map { Param($_)  } keys %$rc_spec_ref)
;

以上内容让我相信这个问题与 Getopt::Lucid 中的错误无关。此外,我在查看 map 函数的参考后考虑了上述修复,其中提到有时 map 可能会与逗号混淆。 perl会展平嵌入的列表,而周围的括号似乎有勾勒每个map表达式的作用,但我真的不明白这是怎么回事。

谁能解释一下?

【问题讨论】:

  • 如果只将 map 函数的输入(即 qw 列表等)放在括号中会发生什么?那它会起作用吗?如果是这样,我认为发生的事情是第一张地图假设“(qw(-c -m -s -p),(map { Switch($_)}(qw(--help --man --usage --version), (map { Switch($_) } (qw(--debug --verbose), (map { Param($_) } (keys (%$rc_spec_ref)))))))" 是它的输入。你明白我在说什么吗?基本上每个地图的列表上下文输入似乎都被贪婪地解析了。
  • 它不起作用,因为(正如下面的答案所解释的那样)后续地图的输出将被添加到以前地图的输入中。贪婪输入是一种很好的思考方式。

标签: perl parsing map


【解决方案1】:

map 函数将一个列表作为参数并生成一个列表作为结果。您可以将map 语句链接在一起(将一个map 的输出作为另一个输入),这是您的第一个示例所做的。在各个 map 运算符周围添加括号会破坏链。

阅读链接的map(或grep)语句时,从右到左阅读。

push @clo_spec,
    map { Switch($_) } qw(-c -m -s -p),
    map { Switch($_) } qw(--help --man --usage --version),
    map { Switch($_) } qw(--debug --verbose),
    map { Param($_)  } keys %$rc_spec_ref;

最后一个map 为来自%$rc_spec_ref 的每个键调用Param() 并返回结果。上面的map 调用Switch() 的值--debug--verbose每个结果都来自最后一个map。上面的map 块获得更长的参数列表,其中qw() 中的标志具有与它们连接的其他map 块的结果。

在每个 map 块周围添加括号会改变代码的解析方式,导致每个 map 被单独处理,而不是菊花链。

【讨论】:

  • 与 map 和 grep(以及其他函数?)的正确关联!我什至没有考虑这一点,但我认为这是有道理的,因为它不知道在块之后会找到什么,因此必须首先扩展最右边的内容。
  • 关联性适用于二元运算符(中缀表示法)。 mapgrep 是函数。 Perl 允许您省略大多数内置函数的括号。当你这样做时,函数的原型决定了表达式的解析方式。 map 的原型表示第一个参数是要执行的块,其他所有内容都是要消耗的数据。数据元素的数量不是固定的,因此 Perl 会使用所有内容,除非您使用 map(...) 语法明确界定参数列表。
【解决方案2】:
$ perl -MO=Deparse 推@clo_spec,地图{开关($_)}qw(-c -m -s -p), 地图 { Switch($_) } qw(--help --man --usage --version), 地图 { 开关($_) } qw(--debug --verbose), 地图 { 参数($_) } 键 %$rc_spec_ref ; 我的 $clo_o = Getopt::Lucid->getopt(\@clo_spec); ^D 推@clo_spec, map({Switch($_);} ('-c', '-m', '-s', '-p'), map({Switch($_);} ( '--help', '--man', '--usage', '--version'), map({Switch($_);} ('--debug', '--verbose'), map({Param($_);} 键 %$rc_spec_ref)))); 我的 $clo_o = 'Getopt::Lucid'->getopt(\@clo_spec); - 语法正常

如果您对 Perl 解析某些内容的方式感到困惑,B::Deparse 非常棒。

在这种情况下,您可以清楚地看到每个map 不仅在您给它的qw() 上运行,而且还在它后面的map 的结果上运行。

【讨论】:

  • 酷。感谢 B::Deparse 的提醒。
【解决方案3】:

我认为正在发生的事情是,如果没有括号,每个地图的输出都会作为参数提供给它之前的地图。假设您只有其中两张地图:

push @clo_spec, (map { Switch($_) } qw(-c -m -s -p)),
                (map { Switch($_) } qw(--help --man --usage --version)),

最后一个首先执行,输入 Switch '--help'、'--man' 等。 Switch 返回您期望的哈希值。然后,第一个地图执行,给它提供开关'-c','-m','s'和'-p'。但随后它也将第一次切换的结果提供给它,这解释了为什么您会收到 HASH(...) 不是有效选项名称的错误。

解决方案?要么使用括号明确每个映射的参数[1],要么使用多个推送线,每个映射一个。

[1] 如果您确实使用了括号,我建议您不要使用 (map ....) 编写 map(....),因为这样会更清楚为什么有括号。

【讨论】:

  • 我喜欢尽可能少地调用函数,即使在实践中存在微不足道的速度和内存问题。一旦你们解释了发生了什么,map(...) 不仅是更好的样式,而且我认为它也更有意义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-10
  • 1970-01-01
  • 1970-01-01
  • 2020-05-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多