【问题标题】:Take a reference to a Perl operator引用 Perl 运算符
【发布时间】:2016-11-24 05:27:35
【问题描述】:

在 Perl 中,是否可以对运算符进行子引用(例如 eqgt)?还是因为他们不是潜艇所以不可能?我想避免将运算符重新定义为需要比较函数的子项。

我正在对配置值应用检查功能,其中检查器取决于配置键。一个这样的检查器是“与另一个键的值比较”。 compare 函数可以通过匿名 sub 定义,或者可以为每个比较类型实现一个命名 sub,但我认为能够说 compare_to("OTHER_KEY", \&eq) 会更好。

编辑:由于一位评论员想要上下文,而其他人投票结束时认为“过于宽泛”,我会绝对清楚问题不是“如何编写配置检查器”。这只是关于引用运算符:是否可能,有哪些注意事项?

【问题讨论】:

  • 比较功能要怎么用?
  • 我应该把它理解为“不,但也许你可以通过其他方式解决它”?我也对答案本身感兴趣。我已经更新了问题。

标签: perl reference


【解决方案1】:

eqgt 这样的比较属于CORE 函数的一个特殊类别,您不能在其中创建对它们的引用。这在http://perldoc.perl.org/CORE.html的文档中有详细说明

所以看起来你不能做你想做的事。我认为您的选择包括:

  • 使用匿名 subs 或命名 subs 来包装您要使用的比较器
  • 传入字符串'eq' 并在compare_to 中执行if 并在那里调用您的比较器。
  • 您可以像其他一些语言一样,将第二个参数设为 1、0 或 -1,并将其表示为 gteqlt,然后如果该值为 coderef,则调用那个。
  • 如果您有兴趣沿着这条路线走下去,可能有一种使用AUTOLOAD 的伪魔法方法来解决此问题。

更新:

AUTOLOAD魔术示例:

use feature qw( say );

my %bin_ops = map { $_ => 1 } qw( eq ne gt lt );
sub AUTOLOAD {
    $AUTOLOAD =~ s/^.*:://;
    die "..." if !$bin_ops{$AUTOLOAD};
    return eval "\$_[0] $AUTOLOAD \$_[1]";
}

sub compare_to {
    my ($sub, $a, $b) = @_;
    return $sub->($a, $b);
}

say compare_to(\&eq, 1, 1) ? "yep" : "nope";
say compare_to(\&eq, 1, 0) ? "yep" : "nope";
say compare_to(\&gt, 1, 0) ? "yep" : "nope";
say compare_to(\&lt, 1, 0) ? "yep" : "nope";

运行它:

> perl tmp/test_coreref.pl
yep
nope
yep
nope

可能有一种方法可以在不使用 eval 的情况下做到这一点,但我暂时不打算进一步研究。

【讨论】:

  • 出于求知欲,我会对任何神奇的方法感兴趣,但不太可能实际使用它(比仅仅包装运算符要清晰得多)。
  • 使用for (qw( eq ne gt lt )) { eval "sub $_ { \$_[0] $_ \$_[1] }" } 比讨厌和非法使用 AUTOLOAD 快得多!
  • @ikegami 回复:nasty and illegal use of AUTOLOAD - 我想这是你的意见?我也用开玩笑的语气读过它,但很难从文字中分辨出来。我发现AUTOLOAD weird 和一个混淆,但也可以很酷。另外,是的,您的示例会更快。
  • 现在我添加了die "..." if !$bin_ops{$AUTOLOAD}; 并没有那么讨厌。现在它在速度方面很糟糕。 (OP 不愿调用一个简单的子程序,因此您发布了一个解决方案,该解决方案涉及调用一个非常复杂的子程序,加上eval,再加上 OP 的主体一开始就试图避免!/// 至于非法,AUTOLOAD 用于方法,但您将其用于非方法。
  • 我认为他不想创建更多的 subs 来处理他可能想要使用的所有不同比较器的原因是他不想创建额外的代码,而不是为了性能原因。我同意检查批准的选项列表通常是一个好主意。请记住,添加示例只是因为 OP 有兴趣了解有关 black magic perl 的更多信息。
【解决方案2】:

使用"abc" 创建字符串。

使用sub { $_[0] eq $_[1] } 或类似eval('$_[0] eq $_[1]') 来创建运算符。

这些不会重复操作符;他们创造了它。

在 cmets 中,您一直坚持您的问题是询问是否有可能在不首先创建运算符(将其“包装”在代码中)的情况下获得对运算符的引用。显然,答案是否定的。


另一方面,这个问题询问是否有可能获得对运算符的引用,或者更确切地说,是对 eq 的引用。

可以使用\&CORE::op_name[1] 引用某些运算符,但不能使用eq。使用该语法自动生成 sub 仅支持其语法可以由带有原型的 sub 近似的运算符[2]

当然,您可以轻松地自己创建运算符的可调用实例,而不是让 Perl 为您创建它:

my $eq = sub { $_[0] eq $_[1] };

compare("OTHER_KEY", $eq)    

sub eq { $_[0] eq $_[1] }

compare("OTHER_KEY", \&eq)

如果你想避免代码重复(有一堆类似的子声明),你可以使用eval

eval("sub $_ { \$_[0] $_ \$_[1] }")
   for qw( eq ne gt lt );

compare("OTHER_KEY", \&eq)

您可以简单地使用以下内容,但这会非常浪费和危险:

sub compare {
   ref($_[1])
      ? $_[1]->($val, $_[0])
      : eval("\$val $_[1] \$_[0]")
}

compare("OTHER_KEY", "eq")`

如果您不想涉及任何潜艇,唯一的选择是:

sub compare {
   ref($_[1]) ? $_[1]->($val, $_[0]) :
   $_[1] eq "eq" ? $val eq $_[0] :
   $_[1] eq "ne" ? $val ne $_[0] :
   $_[1] eq "lt" ? $val lt $_[0] :
   $_[1] eq "gt" ? $val gt $_[0] :
   die "Bad argument";
}

  1. 自 5.16 起。

  2. 仅适用于 prototype("CORE::op_name") 为其返回定义值的那些。这适用于一些命名的列表运算符(例如length)和一些命名的一元运算符(例如time),但不适用于任何命名的二元运算符(例如eq)。

【讨论】:

  • 我不应该将我的“避免重新定义”要求限制在匿名订阅者身上——显然命名作品也一样。这正是我想要避免的。
  • 出于对它是否可能的兴趣,并且因为 - IMO - 重复在阅读代码时会分散注意力(尤其是对于内置功能来说是不优雅的)。
  • 正如我在问题中所说,我怀疑这是不可能的,因为他们不是潜艇。所以答案是“不”。
  • say 根据CORE 文档实际上没有这样的子。还有更微妙的地方,因为其中一些可以创建引用但不能通过这些引用调用,eq 和朋友实际上是像while 这样的关键字。抛开个人风格偏好,所有这些信息在答案中都会非常好:(
  • Re "有些运算符可以引用(自 5.16 起),有些则不能",不,您可以对任何运算符执行此操作(如我所示)。您只是不能将\&CORE::syntax 用于其中一些。 /// Re "some can but cannot be called via those references", 这没有任何意义。
猜你喜欢
  • 1970-01-01
  • 2015-12-04
  • 2017-02-07
  • 2014-10-12
  • 2015-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多