串联的多个 Perl 胖逗号( a => b => 1 )有什么好的用途吗?

Are there any good uses for multiple Perl fat commas in series ( a => b => 1 )?

BRIEF:问:Perl 的粗逗号在系列中有什么好的用途吗?

例如func_hash_as_array_arg( a=>b=>1 )

详情:

我刚刚被两个胖逗号/胖箭头串联引起的错误所困扰:

$ bash $> perl -e 'use strict; use warnings; my @v = ( a=> b => 1 )'
✓ 

实际上是在函数中;实际上在一个对象的构造函数中(blessed hash),所以我在想 {} 当它是新的(a=>b=>1)时。

$ bash $>  perl -e '
    use strict; use warnings; 
    sub kwargs_func{ print "inside\n"; my %kw = $_[0] ;}; 
    kwargs_func( a=> b => 1 )
'
inside
Odd number of elements in hash assignment at -e line ##.
✓ 

显然我很快就发现了这个错误——但我宁愿有编译时错误或警告而不是 运行 时错误。

问:系列中的粗逗号有什么好用的吗?

令我惊讶的是竟然没有 'use warnings' 警告。


这是一个半合法使用的人为示例。我可以想象在现实生活中遇到的一个:

我做了很多图形代码。

假设输入一个常量图,如 K3、K4 或 K3,3(我将假设所有弧都是双向的)

人们可能会成对地输入此类图表,例如

K3: (a<=>b, a<=>b, b<=>c). 

但输入为

可能会更好
K3: (a<=>b<=>c<=>a). 

减少重复,因为一个人得到更大的图表。

例如K4成对写作是

K4: ( a<=>b, a<=>c, a<=>d, b<=>c, b<=>d )

而使用这些 "chains" K4 是:

K4: (a<=>b<=>c<=>d<=>a<=>c,b<=>d)

我写了我们现在称为 DSL(领域特定语言)的东西,它接受这样的 "chain" 符号。注意:在上面使用 <=>,故意使用非 Perl 友好的语法。

当然,在 Perl 中必须指示这样一个链的结束,可能是通过 undef:

K4: (a=>b=>c=>d=>a=>c=>undef,b=>d=>undef)

尽管可能会省略最后一个 undef。

懒得输入K3,3,就输入K3,2:

DSL pairs K3,2: (a<=>x, a<=>y, b<=>x, b<=>y, c<=>x, c<=>y )

DSL chains: K3,2:  (y<=>a<=>x<=>b<=>y<=>c<=>x)

Perl pairs K3,2: (a=>x, a=>y, b=>x, b=>y, c=>x, c=>y )

Perl chains: K3,2:  (y=>a=>x=>b=>y=>c=>x=>undef)

我喜欢带有关键字参数的函数。在 Perl 中,有两种主要的方法可以做到这一点:

func_hash_as_array_arg( kwarg1=>kwval1, kwarg2=>kwval2 )
func_hashref_as_scalar_arg( { kwarg1=>kwval1, kwarg2=>kwval2 } )

可以以相当好的方式与位置混合

func( posarg1, posarg2, kwarg1=>kwval1, kwarg2=>kwval2 )
func( posarg1, posarg2, { kwarg1=>kwval1, kwarg2=>kwval2 } )

还有不太好的方式

func( { kwarg1=>kwval1, kwarg2=>kwval2 }, varargs1, vargags2, ... )

尽管 我更喜欢 f(k1=>v1) 而不是 f({k1=>v1}) - 更简洁 - 事实上 hashref "keyword argument group" 提供更多编译时检查 很有趣。我可能会翻转。

当然,真正的问题是 Perl 需要正确的关键字参数语法。

Perl6 做得更好。


对于 grins,一些相关的代码示例用 2 个粗逗号串联。

$ bash $>  perl -e 'use strict; use warnings; my %v = ( a=> b => 1 )'
Odd number of elements in hash assignment at -e line 1.
✓

$ bash $>  perl -e 'use strict; use warnings; my $e = { a=> b => 1 }'
Odd number of elements in anonymous hash at -e line 1.
✓ 

$ bash $>  perl -e 'use strict; use warnings; my $e = [ a=> b => 1 ]'
✓ 

$ bash $>  perl -e '
    use strict; use warnings; 
    sub kwargs_func{ print "inside\n"; my %kw = $_[0] ;}; 
    kwargs_func( a=> b => 1 )
'
inside
Odd number of elements in hash assignment at -e line ##.
✓ 

$ bash $>  perl -e '
   use strict; use warnings; 
   sub kwargs_func{ print "inside\n"; my %kw = %{$_[0]} ;}; 
   kwargs_func( {a=> b => 1} )
'
Odd number of elements in anonymous hash at -e line ##.
inside
✓ 

对于那些不知道的人,箭头用作逗号,自动引用左侧的裸词。

我想不出有什么时候看到一串粗逗号在常规代码中运行良好。通常我会选择报价运算符。

my @array = qw(Minimal syntax for all these words and easier than lots of arrows);

粗逗号的主要好处是它在视觉上将它的两个论点联系起来,而且我们还可以免费获得引号。这自然对哈希有帮助,但它偶尔也有其他用途。

但是,我看不出将它们链接起来有什么用处。那些 => 传达了元素之间的关联,在我看来,这对于普通列表来说几乎是错误的。如果它用于 实现 这样的行为,那是另外一回事——列表本身没有这样的 属性 并且使用暗示它的语法可能会产生误导(请参阅注释结尾)。即使是引用便利也有故障

my @a_list = (a => b => 'c');  #  qw(a b c)

ab 被视为引用,因为它们位于 => 的左侧,但 c 不是。

至于应该将什么以及如何报告为错误,一串粗逗号只是(花哨的)逗号,允许将它们链接起来,形成一个列表。如果你碰巧有偶数个,你也可以分配给一个散列,它是一个包含偶数个元素的列表

my %h = ('one', 1, 'two', 2);    # same as:  my %h = (one => 1, two => 2);
my %g = (one => 1 => two => 2);  # misleading

我发现这具有误导性,因为散列元素是成对关联的,而不是链式关联的。

对于奇数个元素,我们得到运行时警告,而不是编译错误。这发生在 赋值 时,因为到那时没有理由标记 => 的奇数,它是一个列表。

总而言之,这对我来说是尴尬和困惑的。我也看不到它有任何好处。

我会说 -- 如果是关于引用,请使用 qw。如果它用于哈希,请正常使用它。


如果它用于实现关联行为,我仍然不会尝试在列表语法中指明它。评论中提出了一个有趣的目的,与图表有关。考虑

create_graph(a => b => c => 'a');  # hash? list? why are these chained?

虽然用户不应该关心实现,但这个界面确实让我想知道那些 => 的建议。我应该通过 class/method 名称知道它是一个图形,那么为什么是链,那是什么意思?

---+简介

除了图形和路径(如旅行推销员或关键路径)的表示法之外,多个串行 fat arrow/commas 可以作为您可能会调用的函数的很好的语法糖

# Writing: creating $node->{a}->{b}->{c} if it does not already exist
assign_to_path($node=>a=>b=>c=>"value"); 

# Reading
my $cvalue = follow_path($node=>a=>b=>c=>"default value);

后者类似于

my $cvalue = ($node->{a}->{b}->{c})//"default value);

虽然你可以在指针追踪/hashref 路径跟随函数中做更多的事情,但你可以用//

原来我的个人库里已经有这样的函数了,只是我不知道你可以a=>b=>"value"用它们来让它们在使用时看起来不那么难看。

---+ 详情

我通常尽量不在这个论坛上回答我自己的问题,鼓励其他人回答 - 但在这种情况下,除了我在原始问题后不久在内部发布的人为示例外,我已经意识到我的想法是多个 fat arrow/commas 系列的完全合法用途。

如果不允许连续使用多个粗箭头,我不会抱怨,因为它们通常是一个真正的错误,但至少有两个地方是合适的。

(1) 输入图作为链

提醒:我的第一个,完全人为的,多个脂肪pointer/commas系列的用例是通过使用"chains"更容易输入某些图表。例如。 classic 死锁图将成对 { 1=>2, 2=>1 },并作为 "chain" (1=>2=>1)。如果你想显示一个带有 "chord" 或快捷方式的大循环图,它可能看起来像 ([1=>2=>3=>4=>5=>6=>1],[3=>6]).

请注意,我使用了节点编号:如果我想使用节点名称,我可能必须执行 (a=>b=>c=>undef) 以避免在循环 (a=>b=>"c") 中引用最后一个节点。这是因为左侧的隐式引用而不是右侧的参数。由于无论如何您都必须使用 undef 来支持节点名称,因此可能只是 "flatten" ([1=>2=>3=>4=>5=>6=>1],[3=>6])([1=>2=>3=>4=>5=>6=>1=>undef,3=>6=>undef)。在前面,链的末尾由数组末尾 [...] 指示。在后者中,通过 undef。使用 undef 使 a => 左侧的所有节点在句法上统一。

我承认这是人为的 - 这只是我想到的第一件事。

(2) 作为数据类型的路径

稍微不做作:想象您正在编写、使用或测试正在通过图表寻找 "paths" 的代码 - 例如汉密尔顿主义者、旅行商、绘图、电子电路速度路径分析。就此而言,任何关键路径分析或数据流分析。

我在刚刚列出的 6 个领域中的 4 个领域工作过。虽然我从来没有在这样的代码中使用过 Perl fat arrow/commas(当我一直在处理这样的任务时,通常 Perl 对这样的代码来说很慢),我当然可以承认,尽管写 (a, b,c,d,e) 在计算机程序中,在我自己的笔记中我通常画箭头(a->b->c->d->e)。我认为能够将其编码为 (a=>b=>c=>d=>e=>undef) 会非常令人愉快,即使使用丑陋的 undefs。 (a=>b=>c=>d=>e=>undef) 优于 qw(a b c d e),如果我试图使代码符合我的想法。

"Trying to make the code resemble my thinking"经常是我在做的事情。我想使用问题区域通用的符号。有时我会使用 DSL,有时我自己写,有时只写一些字符串或文本解析例程但是如果像 Perl 这样的语言有一个看起来很熟悉的语法,那就更少了。

顺便说一下,在 C++ 中,我经常将链或路径表示为

Path p = Path()->start("a")->link_to("b")->link_to("c")->end("d");

不幸的是,这很冗长,但几乎是不言自明的。

当然,这样的表示只是程序员API:实际的数据结构通常隐藏得很好,很少是上面暗示的线性链表。

无论如何 - 如果我需要在 Perl 中编写这样的 "path-manipulating" 代码,我可以使用 (a=>b=>c=>undef) 作为符号 - 特别是当传递给像 Path(a=>b=> c=>undef) 创建实际的数据结构。

甚至可能有一些稍微更令人愉快的方式来处理拟合 arrow/comma 右侧的非引用:例如。有时我可能会使用 0 或 -1 之类的代码来指示闭环(循环)或尚未完成的路径:Path(a=>b=>c=>0) 是循环,Path(a=>b=>c=>-1) 不是。 0 更像是一个闭环。不幸的是,这意味着您不能拥有数字节点。或者可以利用更多的 Perl 语法:Path(a=>b=>c=>undef), Path(a=>b=>c=>[]), Path(a=>b=>c=>{})

我们在这里所做的就是使用编程语言的语法来创建类似于问题域的符号。

(3) 最后,一个更 "native Perl"-ish 的用例。

您是否曾想在无法保证路径的所有元素都存在时访问 $node->{a}->{b}->{c}

有时一个人最终会写出像

这样的代码

写作时:

$node = {} if not defined $node;
$node->{a} = {}  if not exists $node->{a};
$node->{a}->{b} = {}  if not exists $node->{a}->{b};
$node->{a}->{b}->{c} = 0;

读书的时候……嗯,你可以想象。在没有引入//操作符之前,我是懒得输入的。使用 // 运算符,这样的代码可能如下所示:

my $value = $node->{a}->{b}->{c}//"default value if the path is incomplete";

是的,是的...永远不要暴露数据结构的那么多细节。在编写上述代码之前,应该重构为一组很好的面向对象 APIs。等等

不过,当你不得不处理别人的Perl代码时,你可能会运行进入上面。特别是如果那个人是匆忙的 EE,而不是 CS 专业。

总之:我个人的Perl库函数早就有封装了上面的。

从历史上看,这些看起来像:

assign_to_hash_path( $node, "a", "b", "c", 0 )
# sets $node->{a}->{b}->{c} = 0, creating all nodes as necessary
# can follow or create arbitrarily log chains
# the first argument is the base node,
# the last is the value
# any number of intermediate nodes are allowed.

或者,更明显的是赋值:

${hash_path_lhs( $node, "a", "b", "c")} = 0
# IIRC this is how I created a left-hand-side
# by returning a ref that I then dereffed.

和阅读(现在通常 // 对于简单的情况):

my $cvalue = follow_hash_path_undef_if_cannot( $node, "a", "b", "c" );

由于阅读的简单情况现在通常是//,因此值得一提的是不太简单的情况,例如在您正在创建(创建、零填充或读取时复制)的模拟器中,或者可能跟踪统计信息或修改状态,如 LRU 或历史

my $cvalue = lookup( $bpred_top => path_history => $path_hash => undef );    
my $cvalue = lookup( $bpred_top => gshare => hash($pc,$tnt_history) => undef );    

基本上,这些库是类固醇的 // 运算符,有更广泛的选择要做的是完整路径不存在(或者即使它确实存在,例如计数统计和缓存)。

他们使用引号运算符会稍微愉快一些,例如

assign_to_hash_path( $node, qw{a b c}, 0);
${hash_path_lhs( $node, qw{a b c})} = 0;
my $cvalue = follow_hash_path_undef_if_cannot( $node, qw{a b c});

但是现在我用了很多年 perlobj 脑袋已经很厚了,我觉得 fat arrow/commas 可能会让这些看起来更舒服:

assign_to_hash_path( $node => a => b => c => 0);
my $cvalue = follow_hash_path( $node => a => b => c => undef );

不幸的是,LHS 功能并没有太大改进,因为需要引用这样一个路径的最后一个元素:

${hash_path_lhs( $node=>a=>b=>"c"} = 0;
${hash_path_lhs( $node=>a=>b=>c=>undef} = 0;

所以我很想放弃 LHS,或者使用一些强制性的最终参数,比如

${hash_path_lhs( $node=>a=>b=>c, Create_As_Needed() ) = 0;
${hash_path_lhs( $node=>a=>b=>c, Die_if_Path_Incomplete() ) = 0;

LHS代码看起来很丑,但其他两个看起来还不错,希望这样一个链的最后一个元素要么是要分配的值,要么是默认值。

assign_to_hash_path( $node => a => b => c => "value-to-be-assigned");
my $cvalue = follow_hash_path( $node => a => b => c => "default-value" );

不幸的是,没有明显的地方可以放置关键字选项 - 以下内容不起作用,因为您无法在开头或结尾区分可选关键字和参数:

assign_to_hash_path( $node => a => b => c => 0);
assign_to_hash_path( {warn_if_path_incomplete=>1}, $node => a => b => c => 0);
my $cvalue = follow_hash_path( $node => a => b => c => undef );
my $cvalue = follow_hash_path( $node => a => b => c => undef, {die_if_path_incomplete=>1} );

我偶尔会用到一个关键字class,缩写为KW,这样一个类型查询就可以告诉我们哪个是关键字,但那是次优的——实际上,它还不错,但就是Perl没有单一的 BKM(是的,TMTOWTDI):

assign_to_hash_path( $node => a => b => c => 0);
assign_to_hash_path( KW(warn_if_path_incomplete=>1), $node => a => b => c => 0);
my $cvalue = follow_hash_path( $node => a => b => c => undef );
my $cvalue = follow_hash_path( KW(die_if_path_incomplete=>1), $node => a => b => c => undef );
my $value = follow_hash_path( $node => a => b => c => undef, KW(die_if_path_incomplete=>1) );

结论:Foo(a=>b=>c=>1) 看起来很奇怪,但可能是 useful/nice 语法糖

所以:虽然我更希望 use warnings 警告我关于 foo(a=>a=>1),当一个关键字被意外复制时,我认为系列中的多个 fat arrow/commas 可能是有助于使某些类型的代码更具可读性。

虽然我还没有看到任何真实世界的例子,但通常如果我能想象到一些东西,一个更好、更有洞察力的 Perl 程序员已经编写了它。

而且我正在考虑修改我的一些遗留库以使用它。事实上,我可能不必返工-我设计的库被称为

assign_to_hash_path( $node, "a", "b", "c", 0 )

如果作为

调用可能已经有效
assign_to_hash_path( $node => a => b=> c => 0 )

简单的工作示例

对于 grins,一个简单路径跟随函数的示例,它比使用 //

更方便地报告更多错误
$ bash 1278 $>  cat example-Follow_Hashref_Path.pl
use strict;
use warnings;

sub follow_path {
    my $node=shift;
    if( ref $node ne 'HASH' ) {
    print "Error: expected $node to be a ref HASH,"
      ." instead got ".(
          ref $node eq ''
        ?"scalar $node"
        :"ref ".(ref $node))
      ."\n";
    return;
    }
    my $path=q{node=>};
    my $full_path = $path . join('=>',@_);
    foreach my $field ( @_ ) {
    $path.="->{$field}";
    if( not exists $node->{$field} ) {
        print "stopped at path element $field"
          ."\n    full_path = $full_path"
          ."\n    path so far = $path"
          ."\n";
        return;
    }
    $node = $node->{$field}
    }
}

my $node={a=>{b=>{c=>{}}}};

follow_path($node=>a=>b=>c=>"end");
follow_path($node=>A=>b=>c=>"end");
follow_path($node=>a=>B=>c=>"end");
follow_path($node=>a=>b=>C=>"end");
follow_path({}=>a=>b=>c=>"end");
follow_path(undef=>a=>b=>c=>"end");
follow_path('string-value'=>a=>b=>c=>"end");
follow_path('42'=>a=>b=>c=>"end");
follow_path([]=>a=>b=>c=>"end");

并使用:

$ perl example-Follow_Hashref_Path.pl
stopped at path element end
    full_path = node=>a=>b=>c=>end
    path so far = node=>->{a}->{b}->{c}->{end}
stopped at path element A
    full_path = node=>A=>b=>c=>end
    path so far = node=>->{A}
stopped at path element B
    full_path = node=>a=>B=>c=>end
    path so far = node=>->{a}->{B}
stopped at path element C
    full_path = node=>a=>b=>C=>end
    path so far = node=>->{a}->{b}->{C}
stopped at path element a
    full_path = node=>a=>b=>c=>end
    path so far = node=>->{a}
Error: expected $node to be a ref HASH, instead got scalar undef
Error: expected $node to be a ref HASH, instead got scalar string-value
Error: expected $node to be a ref HASH, instead got scalar 42
Error: expected $node to be a ref HASH, instead got ref ARRAY
✓
$

另一个例子($node->{a}->{B}->{c}//"premature end")

$ bash 1291 $>  perl -e 'use warnings;my $node={a=>{b=>{c=>"end"}}}; print "followed path to the ".($node->{a}->{B}->{c}//"premature end")."\n"'
followed path to the premature end
$ bash 1292 $>  perl -e 'use warnings;my $node={a=>{b=>{c=>"end"}}}; print "followed path to the ".($node->{a}->{b}->{c}//"premature end")."\n"'
followed path to the end

我承认我很难记住 // 的绑定强度。

终于

顺便说一句,如果有人有使用 //-> 的习语示例,可以避免创建库函数,尤其是对于写入,我很想听听。

能够创建库使事情变得更容易或更愉快是件好事。

不需要这样做也很好 - 如 ($node->{a}->{B}->{c}//"default").

我不仅回答了我自己的问题,而且我还要单独给出第二个答案! :-}{

无论如何,今天,在我问出最初的问题几个月后,我盯着一些代码并自言自语 'Boy, I wish that Perl had a prefix field accessor'。

Perl 的标准 hashref 字段访问器是后缀或后缀:$hashref->{fieldname}

某些语言有前缀字段访问器:

为了像 Perl 一样,可以想象把它写成 {fieldname}<-$hashref

尽管当我看到它完成时它更加冗长:

POSTFIX:   field of object
PREFIX:    object 's field

(是的,'s 是该语言的运算符。不,不是 COBOL - 我认为它是 POP-4,也可能是 Algol-68,其中没有内置但可以编写)

比较
PERL:      $object->{field}
C, etc:    object.field
           object->field

然后我意识到你可以在 perl

中写一个不太令人反感的前缀字段 getter
sub get_field ($%) {
    my $fieldname = shift;
    my $hash = shift;
    return $hash->{$fieldname};
}

# used
assert( 1 == get_field 'fieldname', $some->long_and_complex_function(with,args) )

或者,愉快地使用粗逗号来避免在字段名上使用字符串引号

assert( 1 == get_field fieldname=>$some->long_and_complex_function(with,args) )

相比
assert( 1 == $some->long_and_complex_function(with,args)->{fieldname} )

好的,这只是(滥用)使用一个粗逗号。

但显然它可以扩展到

assert( 1 == get_field 3rd=>2nd=>1st=>$object )

相当于

assert( 1 == $object->{1st}->{2nd}->{3rd} )

我不确定我是否同意。但是...