8

简短的问题

在 Perl 代码库中一致地表示真假的最佳方法是什么?

  • 1/ 0?

  • 1/ Perl 的本机布尔运算符
    返回的特殊空字符串?

  • undef?

  • ()(即空列表)?

问题背景

我们都知道 Perl 在布尔值方面非常灵活,就像在大多数情况下一样。

例如,Perl 将以下内容视为 false:、undef()数字0(即使写为 000 或 0.0)、空字符串'0'(包含单个 0 数字的字符串)。Perl 将以下内容视为 true:任何其他标量值、, 1-1其中带有空格的字符串 ( ' ')、'00'字符串中的多个 0、"0\n"('0' 后跟换行符)、, ,'true'等。另外,数组-到标量和列表到标量的转换意味着您通常可以避免使用空数组作为假值。(感谢http://perlmaven.com/boolean-values-in-perl获取被 Perl 接受为真或假的标量值的部分列表。)'false''undef'

但是考虑到所有这些被视为真或假的值,Perl 布尔函数应该返回什么?

  • 1/0
  • -1/0(不)
  • 1/undef
  • 1/""

如果您使用这些约定中的任何一种,您将很快发现您编写的代码的行为不如普通 Perl 代码好。您将无法替换$a<$bcustom_less_than($a,$b)并使其工作完全相同。

考虑:

> perl -e 'use warnings; 
           sub my_eq { return ( $_[0]==$_[1] ? 1 : 0 ) }
           print "compare (1==0) <<".(1==0).">>"
                 ." to my_eq(1,0) <<".my_eq(1,0).">>"
                 ."\n" ;'
Output:
compare (1==0) <<>> to my_eq(1,0) <<0>>

当您自己编写时,从 Perl 中的布尔函数返回值的最著名方法是什么?

也许您希望您的代码与 Perl 类似,可以替代 Perl 现有的布尔运算符。或者您可能想要像 1/0 这样的数值。或者您可能来自 LISP,并期望 Perlundef被用于 LISP 的nilfalse(但随后您会绊倒 Perl 将许多其他值视为 false 的隐式处理)。

4

2 回答 2

6

我为布尔真值返回 1。除了混乱之外,我真的看不出这!!1会增加什么。

但我通常不返回布尔假值。这是因为barereturn会根据子例程的调用方式返回适当的值。的文档是return这样说的:

如果没有给出 EXPR,则在列表上下文中返回一个空列表,在标量上下文中返回未定义的值,并且(当然)在 void 上下文中什么都没有。

这很重要,因为真值和假值在标量和列表上下文之间可能存在细微差别。想象一个像这样的子程序:

sub some_boolean {
  if ($some_condition) {
    return 1;
  else {
    return undef; # same effect with any scalar value
  }
}

如果在标量上下文中调用它,这很好。

if (some_boolean()) {
  ...
} else {
  ...
}

一切都按预期工作。但是如果你在列表上下文中调用它,事情就会变得有点奇怪。

my @array = some_boolean();
if (@array) {
  ...
} else {
  ...
}

在这种情况下,该else块永远不会被调用。在列表上下文中,您的子例程返回一个包含单个标量(值undef)的列表,因此@array只有一个元素并且if (@array)测试始终为真。

当然,您的子例程并不打算在列表上下文中调用。但是您无法控制其他程序员将如何使用您的代码。

但是如果子程序是这样写的:

sub some_boolean {
  if ($some_condition) {
    return 1;
  } else {
    return;
  }
}

一切都会按预期进行。如果在标量上下文中调用您的子例程,它将返回一个标量 false 值,如果在列表上下文中调用它,它将返回一个空列表。

在我看来,从子例程返回显式错误标量值总是值得怀疑的。

如果您想返回显式标量 false 值,那么检查调用上下文并在错误时采取适当措施是非常值得的。

croak 'Subroutine called in list context.' if wantarray;

更新:回答此评论。

IIRC 我尝试使用这种方法,但放弃了它,因为它产生了更多关于未定义值的警告——即它不是现有 Perl 1/!!0 比较的替代品,也不是 1/0。考虑perl -wle 'print "a()=<<".a().">>\n"; sub a {if(@_) {return 1} else {return}} '_return !!@_

布尔值的作用是您可以询问它是真还是假。就这些。在我看来,你不应该期望只打印一个布尔值。您通常可以在不触发警告的情况下打印布尔值这一事实很好,但不应依赖。如果我想打印一个布尔值,我总是会使用某种逻辑检查(可能是三元运算符)来做到这一点。所以我会这样写你的例子:

$ perl -wle 'print "a()=<<".(a() ? "True" : "False").">>\n"; sub a {if(@_) {return 1} else {return}}'

我只是重申我原来的观点。如果您为 false 返回任何标量值,并且有任何方法可以在列表上下文中调用您的子例程(甚至是意外),那么您已经在代码中引入了潜在的错误。

仅此一项,就值得在打印布尔值之前对其进行解码的微不足道的痛苦。

于 2016-09-18T09:37:16.490 回答
-2

---+ 简短回答

以下是 Perl 中布尔值的两个自洽方案:

  • 1/0- 可打印和便携
  • 1/!!0- 最像 Perl 的原生布尔函数

1/0可能是其他语言的程序员最熟悉的,比如 C 或 Python。您可以打印1/0布尔值、添加它们等等。但是... Perl 的本机布尔运算符不是1/0值, return $a<0return 1 if $a<0; return 0.

1/0!!我尝试为 Perl 的本机布尔运算符使用的方案创建一个缩写名称:1 表示真,一个空字符串,经过特殊标记,以便在用于算术或插入字符串时不会产生警告。!!0是产生这个特殊的 Perl 假值的最简单的方法之一,并且应该为许多语言(如 C)的程序员所熟悉,作为标准化布尔值的一种方法。您可以添加1/0!!,并且可以打印1/0布尔值,只要您不关心 false 值是否不可见,即空字符串。

避免在同一函数或库中意外混合数字 1/0、Perl 的本机条件和其他方案。undef

故意混合时,使用转换运算符,例如

  • !!$bool_0or1转换为传统的1/!!0Perl 值
  • (0+($a<0))将 Perl 关系结果转换为1/0
  • (!!any_other_boolean())
  • (0+any_other_boolean())
  • ($other_bool?1:())1/()从其他布尔方案转换为
  • ($other_bool?1:undef)1/undef从其他布尔方案转换为

问:除了 之外,还有更短或类似前缀的符号?:吗?

还有一些可能的方案

  • 1/()- 更准确地说,返回 1 或什么都不返回- 这可以捕获
    一些 Perl 错误,例如在列表上下文中返回布尔标量 false (如 0 或 undef),它会变为 true
  • (1)/(0)- 返回一个包含 1 的长度为 1 的列表,或一个空列表。与 类似1/(),在 true 和 false 都是数组的意义上是一致的。
  • 1/undef- 另一种可能性,捕获1/() 返回 1 或什么都不会导致的错误

我毫不犹豫地推荐这些。至少,1/()是不一致的,但是……它们肯定已经被 Perl 程序员使用过,所以你应该准备好处理使用这些方案的代码。即准备调试由这些方案引起的错误。

1/()return 1;是我尝试为返回 true的函数和返回 false 的函数做的方案创建缩写名称return;,即没有操作数值的返回。即返回 1 或无。我相信return nothing等同于return ();. 此方案可保护您免受程序员在列表上下文而不是标量上下文中评估您的函数引起的错误。但它会使您暴露于诸如{1=>return_nothing_if_false(),2=>return_nothing_if_false()}(snce 您可能不想要{1=>2}.

顺便说一句,我认为执行该方案可能更一致(1)/()。这将允许您始终如一地拥有这种布尔类型的变量,当然是@array变量。

请注意,这1/undef不等同于上述任何一项。 当 false=被打印或插值或用于算术时,1/undef布尔变量会发出警告,但当 true=1 被如此操作时不会发出警告,并且在列表上下文中评估为 true。undef有人可能会说它具有所有方案中最差的特征。

1/()我对这些方案,(1)/()或 1/undef犹豫不决。至少,在这方面1/()是不一致的

所有这三个方案1/()(1)/()或 1/undef 都会使您面临诸如 之类的错误{1=>f1_return_nothing_if_false(),2=>f2_return_nothing_if_false()},因为您可能不希望{a=>"b"}两者都为假并且{a=>1,b=>1}两者都为真。至少如果 f1 返回 true 而 f2 返回 false,或者反之亦然,您将收到关于奇数大小散列的警告。

调用函数的程序员可以控制是否在列表或标量上下文中计算它,但她可能无法控制它是否返回真或假。

恕我直言,如果您使用 1/(),(1)/()或 1/undef ,则无法在数组上下文中安全地调用此类函数,例如构建关键字参数foo(kw=>boolfunc(),...)foo({kw=>boolfunc(),kw2=>...},...). 不必分散!或全部为0+。

---+ 中等长度的答案

概括原始答案:

Perl 有许多表示真理的方法。或者更确切地说,Perl 将许多不同的值解释为真或假。

如果您正在创建一系列相关功能,即。一个库,建议您选择以下知名方案之一,并在您的库中始终如一地使用它:

  1. 真相1/0- 数字 - 最便携/来自其他语言,更可打印

  2. 真相1/!!0- 最像标准 Perl 关系运算符,不那么便携,不那么可打印(除非你想让 false 不可见)

这个答案强调布尔函数或方法,谓词。它不是试图讨论非布尔函数,它返回实际的东西,如数字、字符串或引用 - 除了下面简要介绍。

@DaveCross 提出了一个额外的有趣方案

  1. return 1/return没有(几乎1/(),空列表)

我记得 Perl 早期的一个方案 - 在 refs 之前,我认为甚至之前undef是一个可以返回的值。但是 IIRC 这个方案有问题,你se warnings也可能有问题?:,所以我犹豫是否完全推荐它,直到有人更好地解释如何避免这些问题。可能使用wantarray.

---++ 选择 1/0 或 1/0!!(本机 Perl)并保持一致

我建议您选择其中一种布尔方案,并始终如一地使用该方案。

1/0 布尔方案可能最可移植到其他语言。

1/!!0 方案将使您的代码更接近于本地 Perl 运算符。

如果您正在使用该1/!!0方案,请不要说“返回 0”,说return !!0.

如果您正在使用该1/0方案,请不要说return $a < $b,而是说return 0+($a < $b)

如果您调用的代码使用不同的布尔方案(或者,可能没有一致的方案),请使用以下运算符转换为您在代码中使用的布尔方案

  • !!规范化标准 Perl1/0!!布尔值
  • 0+1*从标准 Perl 1/0 转换为更便携的 1/0 布尔值!!布尔值
  • ?:以及所有其他 Perl 的 undef 和字符串,可能或可能不想被视为错误或失败

如果查看返回 ref 或 undef 的函数的返回值

  • 如果1/!!0是类似 Perl 的布尔值,请说 return!!ref_retval_func()defined ref_retval_func()

  • 如果1/0更便携的布尔值,说 return0+!!ref_retval_func()0+(defined ref_retval_func())

下面说的太详细了。

---++ 可能?:返回 1 或不返回任何方案(可能是 1/()?)

@DaveCross 提出了一个有趣的建议:

  • 为布尔真值返回 1。

  • 布尔 false 值不返回任何内容。这是因为裸
    返回将根据
    子例程的调用方式返回适当的值。退货文件是这样说的:

    如果没有给出 EXPR,则在列表上下文中返回一个空列表,在标量上下文中返回未定义的值,并且(当然)在 void 上下文中什么都没有。

---++ 反推荐:不要混合布尔方案 例如,在同一个函数或库中,不要这样做

return $arg1 < $arg2;  # returning a standard Perl 1/!!0 Boolean

在一个地方,然后在其他地方,或者在相同代码的后续演变中,执行

return 0; 
return undef; 
return ''; 
return (); 

即选择一个布尔方案,并保持一致。主要是,这涉及到错误值的一致性;在较小程度上是真实价值。

---+ 过多凌乱的细节

---++ 在别处讨论 Perl 的许多真理价值

诸如返回布尔值的 Perl 函数实际返回什么以及Perl 为什么使用空字符串来表示布尔值 false 之类的帖子?讨论 Perl 布尔函数和运算符实际返回的内容。基本上是特殊值,其行为由 Perl 手册指定。

@cim 链接到 perl 手册:http ://perldoc.perl.org/perlsyn.html#Truth-and-Falsehood

真与假

数字 0、字符串 '0' 和 "" 、空列表 () 和 undef 在布尔上下文中都是错误的。所有其他值都为真。对真实值的否定!或不返回一个特殊的假值。当作为字符串求值时,它被视为 "" ,但作为数字,它被视为 0。大多数返回 true 或 false 的 Perl 运算符都以这种方式运行。

同样http://perldoc.perl.org/perlop.html#Relational-Operators

关系运算符

返回 true 或 false 的 Perl 运算符通常返回可以安全地用作数字的值。例如,本节中的关系运算符和下一节中的等式运算符返回 1 表示 true 和定义的空字符串的特殊版本 "" ,它被视为零,但不会受到有关不正确数字转换的警告,只是因为“0 但真实”是。

不幸的是,对于返回 Boolean 的 Perl 函数实际返回什么的公认答案 讨论了内部结构,但随后建议

my $formatted = $result ? '1' : '0';

这又回到了我们开始的地方。

@amon 在对返回布尔值的 Perl 函数实际上返回什么问题的评论中向我们展示了光 (!!)

旁注:您可以使用双重否定将任何值转换为相应的布尔值。这导致了!!伪运算符。对于返回通用的真值或假值而不是一些幻数非常有用。– 阿蒙 2012 年 11 月 22 日 22:11

这些特殊的布尔值似乎没有任何文字。然而,有许多产生它们的方法:(0<0)(0<1)(!!1)(!!0)并且可能是最好的——尤其是因为在某些 C/C++ 编程圈子中,它们被用于类似的目的。另外,!!可以应用于传入的真值以将其“标准化”为这个“Perl 标准”布尔值。

---++ 反推荐:不要混合布尔方案 例如,在同一个函数或库中,不要这样做

return $arg1 < $arg2;  # returning a standard Perl 1/!!0 Boolean

在一个地方,然后在其他地方,或者在相同代码的后续演变中,执行

return 0; 
return undef; 
return ''; 
return (); 

即选择一个布尔方案,并保持一致。主要是,这涉及到错误值的一致性;在较小程度上是真实价值。

例如,避免从

return $arg1 < $arg2;  # returning a standard Perl 1/!!0 Boolean

if( $arg1 < $arg2 ) {
     log_or_print('found $arg1 <$arg2');
     # other stuff to do if less-than
     return 1;
} else {
     log_or_print('found not( $arg1 < $arg2)');
     # other stuff to do if not-less-than
     # which may not be the same thing as greater-than-or-equal
     return 0;
}

或者

if( $arg1 < $arg2 ) {
     ...
} else {
     ...
     return undef;
}

从其他地方来到 Perl,您可能会认为这些是等价的,而且大多数情况下是等价的,但是如果您在测试中执行诸如打印布尔返回值之类的操作,您将得到差异。

如果你从 Perl-ish 布尔运算符演变代码

return $arg1 < $arg2;  # returning a standard Perl 1/!!0 Boolean

进化到

if( $arg1 < $arg2 ) {
     log_or_print('found $arg1 <$arg2');
     # other stuff to do if less-than
     return 1;
} else {
     log_or_print('found not( $arg1 < $arg2)');
     # other stuff to do if not-less-than
     # which may not be the same thing as greater-than-or-equal
     return !!0;
}

如果您希望行为尽可能接近相同。注意 false 返回的 !!0,据我所知,没有更简单的方法可以构造 Perl 的 false 特殊返回值。

相反,如果你想使用 1/0 布尔方案,它们的原始代码应该写成

return 0+($arg1 < $arg2);  # returning a standard Perl 1/!!0 Boolean

---++ 从 value / undef 创建一个谓词

同样,您可能会想采用诸如

sub find_string_in_table {
   # returns string value if found, undef if not found
   return $lookup_table->{$_[0]}; 
}

并将其重构为谓词

sub is_string_in_table {
   return find_string_in_table(@_);
}

然后可能会演变为进行健全性检查或性能优化。

sub is_string_in_table {
   return 0 
       # don't even bother for long strings
       if 1000000 < length($_[0]);
   return find_string_in_table(@_);
}

这既不是 1/0 也不是 1/!!0,也不是始终如一的 value/undef。

(注意:我并不是说这个预检查是性能优化 --- 但我是说性能优化可能看起来像上面那样。性能优化是我的专长之一,你希望这样的优化是重构的。它很烂当优化的代码表现更好,但在某些地方使用它时会中断。因此,我对执行完全一样的代码感兴趣......无论它被替换,就像本机 Perl 关系运算符。完全意味着完全。)

如果您使用标准的 Perl-ish 布尔值,请改为执行以下操作。

sub is_string_in_table {
   return !!0 
       # don't even bother for long strings
       if 1000000 < length($_[0]);
   return (defined find_string_in_table(@_));
}

或者如果您使用 1/0 布尔值

sub is_string_in_table {
   return 0 
       # don't even bother for long strings
       if 1000000 < length($_[0]);
   return 0+(defined find_string_in_table(@_));
}

如果不是 find_string_in_table 而是 find_object_ref_in_table,您可能会直接返回0+!!find_string_in_table(@_),因为您不需要担心和 之类的q()字符串"0"

如果您希望您编写的代码中的布尔函数表现得像本机 Perl 运算符一样返回 (!!1) 为真, (!!0) 为假。

即 0/1,但在逻辑上使用 ! 运算符将您的 1 或 0 转换为 Perl 的“本机”布尔值。

例如

sub my_boolean_function {
      ... 
      return !!1; # true 
      ...
      return !!0; # false
}

**---+ 0/1 --> 1/!!0 转换**

如果考虑!!作为从“元布尔”到“特殊布尔”的转换,

将 1* 或 0+ 视为从特殊布尔值到普通 0/1 布尔值的转换。

例如print "test".(1*($a eq $b))."\n"

例如print "test".(0+($a eq $b))."\n"

?: 更笼统,但更冗长。

---++ 非布尔错误返回

本问答强调布尔函数或方法、谓词。它不是试图讨论非布尔函数,它返回实际的东西,如数字、字符串或引用 - 除了下面简要介绍。

将返回值扩展以指示特殊情况(例如失败、无效输入等)是“不错的”,并且可以在 IF 语句或其他控制流(例如 and 和 or 运算符)的上下文中对其进行评估,通常用于处理此类错误,例如提供默认值。

我们将对非布尔函数的讨论限制在这个简短列表中:

  • ref/ undef:对于返回典型http://perldoc.perl.org/perlobj.html对象的函数,一个对祝福哈希或其他类型的引用。返回undef错误,未找到等。

  • 任何值 / undef:对于返回任何类型的值、标量数字或字符串的函数,标量 ref 无论是祝福还是未祝福。

value/undef 在undef不是合法的返回值时效果最好,当 undef 是合法的值时可能会出现问题。例如,想象一个返回哈希字段值的访问器函数 $hash->{field} - 该字段可能合法地具有 value { field => undef },因此返回 undef dfoes 不能区分不存在的字段和存在但具有 undef 值的字段。

  • 任意字符串,可以根据上下文解释为数字或布尔值。
  • “0 但真实” - 我真的不想进入这个,但看看Perl 中的“0 但真实”是什么意思?用于字符串“0 but true”的特殊处理。其他字符串在转换为数字时会发出警告,但“0 但为真”不会。
  • "0E0" - 类似地,一些 Perl 代码返回字符串 "0E0",它作为数字计算为 0,但作为布尔值返回 true

GLEW 个人意见:由于我编写的代码经常需要移植到其他语言,因此我不喜欢利用 Perl 特有的技巧,例如0+"0 but true". "0E0"如果您想象在 C 等其他语言中是函数convert_string_to_float("0E0")convert_string_to_int("0x0"). 我更喜欢"0x0"它,因为它与 x 看起来很特殊,并且0x0是一个整数值,而0E0在某些语言中被解释为浮点数,因此更有可能给出错误。

---++ 可能?:返回 1 或不返回任何方案(可能是 1/()?)

@DaveCross 提出了一个有趣的建议:

  • 为布尔真值返回 1。

  • 布尔 false 值不返回任何内容。这是因为裸
    返回将根据
    子例程的调用方式返回适当的值。退货文件是这样说的:

    如果没有给出 EXPR,则在列表上下文中返回一个空列表,在标量上下文中返回未定义的值,并且(当然)在 void 上下文中什么都没有。

这很重要,因为真值和假值在标量和列表上下文之间可能存在细微差别。想象一个像这样的子程序:...

@DaveCross 继续展示如果在数组上下文中评估布尔函数,返回空列表以外的任何值如何导致错误性丢失。甚至@array=(undef)评估为真。

我希望这个计划能够奏效。use warnings我想我几年前在 Perl 4 或更早的版本中使用过它,但是当它开始成为要做的事情时就放弃了。

据我记得,我也遇到了条件表达式的问题?:遵守这个约定。

我已经尝试了两个“返回”;和“返回();”

考虑

%  perl -wle 'print "a()=<<".a().">>\n"; sub a {if(@_) {return 1} else {return}}'
Use of uninitialized value in concatenation (.) or string at -e line 1.
a()=<<>>

%  perl -wle 'print "a()=<<".a().">>\n"; sub a {if(@_) {return 1} else {return ()}}'
Use of uninitialized value in concatenation (.) or string at -e line 1.
a()=<<>>

%  perl -wle 'print "a()=<<".a().">>\n"; sub a { return @_ } '
a()=<<0>>

%  perl -wle 'print "a()=<<".a().">>\n"; sub a { return !!@_ } '
a()=<<>>

%

---+ 底线

使用1/0(可打印和便携),1/0!!(最像 Perl 的本机布尔函数)。

可能return 1return没有,这几乎与1/(). (但我在使用这种方法时遇到了问题。)

避免undef在同一个函数或库中混合数字 1/0、Perl 的本机条件、和其他方案。

最后,如果你曾经做过

$>  perl -wle 'print false && true'

你可能已经收到

Unquoted string "false" may clash with future reserved word at -e line 1.
Unquoted string "true" may clash with future reserved word at -e line 1.
Bareword found in conditional at -e line 1.
true

所以似乎有朝一日 Perl 可能会为布尔值提供一个“官方”方案,其值为 true 和 false。

我想知道那些将如何表现?

于 2016-09-17T01:15:30.333 回答