Perl return 中的布尔函数应该取什么值?

What values should a boolean function in Perl return?

简短问题

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

问题背景

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

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

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

如果您使用这些约定中的任何一个,您很快就会发现您编写的代码不像普通的 Perl 代码那样表现得很好。您将无法用 custom_less_than($a,$b) 替换 $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>>

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

也许您希望您的代码类似于 Perl,可以替代 Perl 现有的布尔运算符。或者您可能需要 1/0 之类的数值。或者你可能来自 LISP,并期望 Perl 的 undef 被用于 LISP 的 nil 为 false(但随后你绊倒了 Perl 将许多其他值隐式处理为 false)。

---+ 简答

这里有两个 self-consistent 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 表示 true,一个特别标记的空字符串,以便在算术中使用时不会产生警告或插入字符串。 !!0 是产生这种特殊的 Perl false 值的最简单方法之一,并且应该为许多语言(如 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

问:除了 ?: 之外,还有更短或 prefix-ish 的符号吗?

还有一些可能的方案

  • 1/() - 更准确地说,return 1 or nothing - 可以捕获
    一些 Perl-ish 错误,例如 returning 一个布尔标量 false,例如 0 或 undef 在列表上下文中,它将变为 true
  • (1)/(0) - return 包含 1 的长度为 1 的列表,或者一个空的 列表。类似于 1/(),在 true 和 false 是数组。
  • 1/undef - 另一种可能性,捕获 1/() return 1 的错误 或者没有什么可能导致

我犹豫要不要推荐这些。至少,1/() 在这方面是不一致的,但是...它们肯定已被 Perl 程序员使用,因此您应该准备好处理使用这些方案的代码。 IE。准备调试由这些方案引起的错误。

1/() 是我尝试为 return true return 1; 函数和 return false [=61= 函数的方案创建一个缩写名称],即没有操作数值的 return。 IE。 return 1 或无。我相信 return nothing 等同于 return ();。此方案可保护您免受程序员在列表上下文而不是标量上下文中评估您的函数所导致的错误。但它会使您暴露于诸如 {1=>return_nothing_if_false(),2=>return_nothing_if_false()} 之类的错误(因为您可能不希望 {1=>2}.

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

请注意 1/undef 不等同于以上任何一项。 1/undef 布尔变量在 false=undef 被打印、内插或用于算术时发出警告,但当 true=1 为 so-manipulated 时不会发出警告,并且在列表上下文中计算为 true。人们可能会忍不住说它具有所有方案中最差的特征。

我犹豫这些方案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 returns true 和 f2 returns false,反之亦然,您将收到关于奇数大小哈希的警告。

调用函数的程序员可以控制它是在列表上下文还是标量上下文中求值,但她可能无法控制它 return 是真还是假。

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

---+ 中等长度的答案

概括原始答案:

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

如果您正在创建一系列相关函数,即。图书馆,建议您选择以下 well-known 方案之一,并在您的图书馆中始终如一地使用它:

  1. Truth 1/0 - 数字 - 最便携 to/from 其他语言和 mo可打印

  2. Truth 1/!!0 - 最像标准的 Perl 关系运算符,可移植性差,可打印性差(除非你想让 false 不可见)

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

@DaveCross 建议另外一个有趣的方案

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

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

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

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

1/0 布尔方案可能最适合其他语言。

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

如果你使用的是1/!!0方案,不要说"return 0",说return !!0

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

如果您调用的代码使用不同的布尔方案(或者可能没有一致的方案),请使用

等运算符转换为您在代码中使用的布尔方案
  • !! 标准化标准 Perl 1/0!! 布尔值
  • 0+1* 从标准 Perl 1/0 转换为更便携的 1/0 布尔值!!布尔值
  • ?: 以及 Perl 的所有其他 undef 和字符串武器库,可能会或可能不会被视为错误或失败

如果查看函数的 return 值 return 是 ref 或 undef

  • 如果 1/!!0 Perl-like 布尔值,比如 return !!ref_retval_func()defined ref_retval_func()

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

下面太详细了。

---++ Possible?: return 1 or return nothing scheme (possibly 1/()?)

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

  • return 1 为布尔真值。

  • return 对于布尔假值没有任何意义。那是因为裸
    return 将 return 一个合适的值取决于
    子程序已被调用。 return 的文档是这样说的:

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

---++ Anti-Recommendation: 不要混合布尔方案 例如,在同一个函数或库中,不要做

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

在一个地方,然后在其他地方,或者在同一代码的后续演进中,做

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

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

---+ 细节过多

---++ 其他地方关于 Perl 的许多真值的讨论

What do Perl functions that return Boolean actually return and Why does Perl use the empty string to represent the boolean false value? 等帖子实际上讨论了 Perl 布尔函数和运算符 return。基本上是特殊值,其行为由 Perl 手册指定。

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

Truth and Falsehood

The number 0, the strings '0' and "" , the empty list () , and undef are all false in a boolean context. All other values are true. Negation of a true value by ! or not returns a special false value. When evaluated as a string it is treated as "" , but as a number, it is treated as 0. Most Perl operators that return true or false behave this way.

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

Relational Operators

Perl operators that return true or false generally return values that can be safely used as numbers. For example, the relational operators in this section and the equality operators in the next one return 1 for true and a special version of the defined empty string, "" , which counts as a zero but is exempt from warnings about improper numeric conversions, just as "0 but true" is.

不幸的是,What do Perl functions that return Boolean actually return 的公认答案 讨论内部结构,然后推荐

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

回到我们开始的地方。

@amon 在对问题 What do Perl functions that return Boolean actually return

的评论中向我们展示了光明 (!!)

Sidenote: you can turn any value into its corresponding boolean with double negation. This leads to the !! pseudo-operator. Very useful for returning the generic truthy or falsey value instead of some magic number. – amon Nov 22 '12 at 22:11

这些特殊的布尔值似乎没有任何文字。然而,有许多方法可以制作它们:(0<0)(0<1) 等。(!!1)(!!0) 可能是最好的 - 特别是因为在某些 C/C ++ 编程圈他们用于类似的目的。另外,!! 可以应用于传入的真值 "normalize" 它到这个 "Perl standard" 布尔值。

---++ Anti-Recommendation: 不要混合布尔方案 例如,在同一个函数或库中,不要做

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,您可能认为这些是等价的,而且大部分都是等价的,但是如果你在测试中打印布尔 return 值,你会得到差异。

如果您从 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 return 上的 !!0,据我所知,没有更简单的方法来构造 Perl 的特殊 return 值为 false.

反之,如果要使用1/0布尔方案,原代码应该写成

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

---++从值/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。

(注意:我不是说这个pre-check是性能优化---而是说性能优化可能像上面那样。性能优化是我的专长之一,你想要这样的优化重构。当优化代码执行得更好时,它很糟糕,但在它使用的某些地方中断。因此,我对执行完全一样的代码感兴趣...无论它正在替换什么,就像本机 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,你可能只做 return 0+!!find_string_in_table(@_) 因为你不需要担心像 q() 这样的字符串和 "0".

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

即0/1,但逻辑上使用 ! 取反两次运算符将您的 1 或 0 转换为 Perl 的 'native' 布尔值。

例如

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

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

如果你考虑!!作为从 "meta-boolean" 到 "special boolean"、

的转换

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

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

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

?: 更笼统,但更冗长。

---++Non-Boolean错误returns

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

"nice" 扩展 return 值以指示特殊情况,例如失败、无效输入等,并且可以在 IF 语句或其他控件的上下文中对其进行评估诸如 and 和 or 运算符之类的流程,通常用于处理此类错误,例如提供默认值。

我们将把对 non-Boolean 函数的讨论限制在这个简短的列表中:

  • ref / undef :对于 return 典型 http://perldoc.perl.org/perlobj.html 对象的函数,对 blessed 哈希或其他类型的引用。 Return undef 出错,未找到等

  • 任何值/undef:对于 return 任何类型的值、标量数字或字符串、标量引用,无论是有福的还是无福的。

value/undef 在 undef 不是合法的 return 值时效果最好,而当 undef 是合法的 value.n 时可能会出现问题。想象一个访问器函数 return 是哈希字段 $hash->{field} 的值——该字段可能合法地具有值 { field => undef },所以 returning undef dfoes 不是区分不存在的字段和存在但具有 undef 值的字段。

  • 任意字符串,可以根据上下文解释为数字或布尔值。
  • "0 but true" - 我真的不想深入这个,但是看看 What does "0 but true" mean in Perl? 对字符串 "0 but true" 的特殊处理。其他字符串会在转换为数字时发出警告,但“0 但为真”不会。
  • "0E0" - 类似地,一些 Perl 代码 returns 字符串 "0E0" 作为数字计算为 0,但作为布尔值计算为 true

GLEW 个人意见:由于我编写的代码经常需要移植到其他语言,所以我不想利用 Perl-specific 像 0+"0 but true" 这样的技巧。 "0E0" 至少是更可移植的,如果你想象在一些其他语言如 C 中一个函数 convert_string_to_float("0E0")convert_string_to_int("0x0")。我更喜欢 "0x0" 因为它看起来很特别 x,并且 0x0 是一个整数值,而 0E0 在某些语言中被解释为浮点数,因此更容易出错。

---++ Possible?: return 1 or return nothing scheme (possibly 1/()?)

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

    [=385=

    return 1 为布尔真值。

  • return 对于布尔假值没有任何意义。那是因为裸
    return 将 return 一个合适的值取决于
    子程序已被调用。 return 的文档是这样说的:

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

这很重要,因为真值和假值在标量和列表上下文之间可能会有微妙的不同。想象这样一个子程序: ...

@DaveCross 继续展示如果在数组上下文中评估布尔函数,returning 空列表以外的任何值如何导致 false-ness 丢失。即使 @array=(undef) 的计算结果为真。

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

据我所知,我也遇到过条件表达式的问题 ?: 与此约定有关。

"return;"和"return();"

我都试过了

考虑

%  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/()几乎相同。 (但我在使用这种方法时遇到了问题。)

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

最后,如果你曾经做过

$>  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 可能有一个 "official" 布尔值方案,值为 true 和 false。

我想知道它们会如何表现?

I return 1 为布尔真值。我真的看不出 !!1 除了混乱之外还有什么。

但我通常 return 没有任何布尔值假值。那是因为根据子例程的调用方式,裸 return 将 return 一个适当的值。 return 的文档是这样说的:

If no EXPR is given, returns an empty list in list context, the undefined value in scalar context, and (of course) nothing at all in void context.

这很重要,因为真值和假值在标量和列表上下文之间可能会有微妙的不同。想象这样一个子程序:

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 块。在列表上下文中,您的子例程 return 是一个包含单个标量(值 undef)的列表,因此 @array 具有单个元素并且 if (@array) 测试始终为真。

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

但是如果子程序这样写:

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

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

在我看来,从子例程返回一个明确的假标量值总是值得怀疑的。

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

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

更新:回复此评论。

IIRC I tried using this approach, but gave up on it because it produced more warnings about undefined values - i.e. it was not a drop-in replacement for an existing Perl 1/!!0 comparisons, nor for 1/0. Consider perl -wle 'print "a()=<<".a().">>\n"; sub a {if(@_) {return 1} else {return}} ', versus return !!@_

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

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

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

单凭这一点,就值得在打印布尔值之前解码这些微不足道的痛苦。