|
| 1 | +# `undef`-aware Equality Operators |
| 2 | + |
| 3 | +## Preamble |
| 4 | + |
| 5 | + Author: Paul Evans <PEVANS> |
| 6 | + Sponsor: |
| 7 | + ID: TODO |
| 8 | + Status: Exploratory |
| 9 | + |
| 10 | +## Abstract |
| 11 | + |
| 12 | +Adds new infix comparison operators that are aware of the special nature of the `undef` value, comparing it as distinct from the number zero or the empty string. |
| 13 | + |
| 14 | +## Motivation |
| 15 | + |
| 16 | +Perl has two sets of comparison operators; one set that considers the stringy nature of values, and another that considers numerical values. When comparing against `undef`, the stringy operators treat undef as an empty string, and the numerical operators treat it as zero. In both cases, a warning is produced: |
| 17 | + |
| 18 | +``` |
| 19 | +Use of uninitialized value in string eq at ... |
| 20 | +``` |
| 21 | + |
| 22 | +Sometimes it is useful to consider that `undef` is in fact a different value, distinct from any defined string or number - even empty or zero. Currently in Perl, to check if a given value (which may be `undef`) is equal to a given string value, the test must make sure to check for definedness: |
| 23 | + |
| 24 | +```perl |
| 25 | +if( defined $x and $x eq $y ) { ... } |
| 26 | +``` |
| 27 | + |
| 28 | +Furthermore, if the value `$y` can also be undefined, it may be desired that in that case it matches if `$x` is also undefined. In that situation, the two cases must be considered individually: |
| 29 | + |
| 30 | +```perl |
| 31 | +if( (!defined $x and !defined $y) or |
| 32 | + (defined $x and defined $y and $x eq $y) ) { ... } |
| 33 | +``` |
| 34 | + |
| 35 | +Countless bugs across countless modules have been caused by failing to check in such a way and using code such as simply `$x eq $y` in these circumstance. |
| 36 | + |
| 37 | +By providing new comparison operators that have different behaviour with undefined values, Perl can provide more convenient choice of behaviours for authors to use. |
| 38 | + |
| 39 | +## Rationale |
| 40 | + |
| 41 | +(explain why the (following) proposed solution will solve it) |
| 42 | + |
| 43 | +## Specification |
| 44 | + |
| 45 | +A new operator, named `equ`, which has the same syntax as regular string equality `eq`, but semantics identical to that given above: |
| 46 | + |
| 47 | +```perl |
| 48 | +$x equ $y |
| 49 | + |
| 50 | +# Equivalent to (!defined $x and !defined $y) or |
| 51 | +# (defined $x and defined $y and $x eq $y) |
| 52 | +``` |
| 53 | + |
| 54 | +In particular, given two `undef` values, this operator will yield true. Given one `undef` and one defined value, it yields false, or given two defined values it will yield the same answer that `eq` would. In no circumstance will it provoke a warning of undefined values. |
| 55 | + |
| 56 | +Likewise, a new operator named `===`, which provides the numerical counterpart: |
| 57 | + |
| 58 | +```perl |
| 59 | +$x === $y |
| 60 | + |
| 61 | +# Equivalent to (!defined $x and !defined $y) or |
| 62 | +# (defined $x and defined $y and $x == $y) |
| 63 | +``` |
| 64 | + |
| 65 | +Note that while the `===` operator will not provoke warnings about undefined values, it could still warn about strings that do not look like numbers. |
| 66 | + |
| 67 | +## Backwards Compatibility |
| 68 | + |
| 69 | +As these infix operators are currently syntax errors in existing versions of Perl, it is not required that they be feature-guarded. |
| 70 | + |
| 71 | +However, it may still be considered useful to add a feature guard in order to provide some compatibility with existing code which uses the `Syntax::Operator::Equ` CPAN module. Likely this would be named `equ`: |
| 72 | + |
| 73 | +```perl |
| 74 | +use feature 'equ'; |
| 75 | +``` |
| 76 | + |
| 77 | +Once the feature becomes stable it is likely this would be included in an appropriate `use VERSION` bundle, so users would not be expected to request it specifically in the long-term. |
| 78 | + |
| 79 | +## Security Implications |
| 80 | + |
| 81 | +No new issues are anticipated. |
| 82 | + |
| 83 | +## Examples |
| 84 | + |
| 85 | +## Prototype Implementation |
| 86 | + |
| 87 | +This operator is already implemented using the pluggable infix operator support of Perl version 5.38, in CPAN module [`Syntax::Operator::Equ`](https://metacpan.org/pod/Syntax::Operator::Equ). |
| 88 | + |
| 89 | +## Future Scope |
| 90 | + |
| 91 | +Considering further the possible idea to provide a `match/case` syntax inspired by [`Syntax::Keyword::Match`](https://metacpan.org/pod/Syntax::Keyword::Match), this operator would be a useful addition in combination with that as well, allowing dispatch on a set of fixed values as well as `undef`: |
| 92 | + |
| 93 | +```perl |
| 94 | +match( $x : equ ) { |
| 95 | + case(undef) { say "The value in x is undefined" } |
| 96 | + case("") { say "The value in x is empty string" } |
| 97 | + case("ABC") { say "The value in x is ABC" } |
| 98 | + ... |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +## Rejected Ideas |
| 103 | + |
| 104 | +* It is not possible to unambiguously provide extensions of the ordering operators `cmp` and `<=>` in a similar way, because this requires a decision to be made as to the sorting order of undefined values, compared to any defined string or number. While it may be natural to consider that undef sorts before an empty string, there is no clear choice on where undef would sort compared to (signed) numbers. Would it be before any number, even negative infinity? This would place it far away from zero. |
| 105 | + |
| 106 | +* Likewise, because it is not possible to provide an undef-aware version of `cmp` or `<=>`, the ordering comparison operators of `le`, `lt` and so on are also not provided. |
| 107 | + |
| 108 | +## Open Issues |
| 109 | + |
| 110 | +* Should we also provide negated versions of these operators? While much rarer in practice, it may be useful to provide a "not equ", perhaps spelled `nequ` or `neu`; and likewise `!===` or `!==` for the numerical version. These do not suffer the sorting order problem outlined above for more general comparisons. |
| 111 | + |
| 112 | +* As an entirely alternate proposal, should we instead find ways to apply behaviour-modifying flags to the existing operators? That is, rather than adding a new `equ` and `===` could we instead consider some syntax such as `eq:u` and `==:u` as a modifier flag, similar to the flags on regexp patterns, as a way to modify operators? This would be extensible in a more general way to more operators, while also allowing more flexible flags in future, such as for instance a case-ignoring string comparison to be spelled `eq:i`. This alternate proposal may be the subject of an alternate PPC document. |
| 113 | + |
| 114 | +## Copyright |
| 115 | + |
| 116 | +Copyright (C) 2024, Paul Evans. |
| 117 | + |
| 118 | +This document and code and documentation within it may be used, redistributed and/or modified under the same terms as Perl itself. |
0 commit comments