4

I used to think that unless and if not were fully equivalent, but this Q&A made me realize they can result in different output under list context:

use strict;
use warnings;
use Data::Printer;
use feature 'say';

my %subs = (
              unless => sub {
                            my ( $value ) = @_ ;

                            return "$value is a multiple of 3"
                              unless $value % 3 ;
                        },
              if_not => sub {
                            my ( $value ) = @_ ;

                            return "$value is a multiple of 3"
                              if not $value % 3 ;
                        },
           );

for my $name ( keys %subs ) {

    my $sub = $subs{$name};
    say $name;
    my @results = map { $sub->($_) } 1 .. 10;
    p @results;
}

Output

if_not
[
    [0] "",
    [1] "",
    [2] "3 is a multiple of 3",
    [3] "",
    [4] "",
    [5] "6 is a multiple of 3",
    [6] "",
    [7] "",
    [8] "9 is a multiple of 3",
    [9] ""
]
unless
[
    [0] 1,
    [1] 2,
    [2] "3 is a multiple of 3",
    [3] 1,
    [4] 2,
    [5] "6 is a multiple of 3",
    [6] 1,
    [7] 2,
    [8] "9 is a multiple of 3",
    [9] 1
]

The code snippet above shows that unless and if not cause the subroutine to see different values of the last-evaluated expression.

It seems that this is because if not considers not to be part of EXPR, whereas unless doesn't consider it to be part of EXPR.

Question

Are there any other examples of code usage where the two are not quite synonymous?

Zaid
  • 36,680
  • 16
  • 86
  • 155
  • 2
    *"`if not` considers `not` to be part of `EXPR`, whereas `unless` doesn't consider it to be part of `EXPR`"* This doesn't make much sense. `not` is an *operator*, whereas `if` and `unless` are flow-control language words. In your `unless` case there is no `not` at all, so "considering `not` to be part of the expression" doesn't apply. Had you also tried `unless not ...` then you would find that a `not` is *always* part of an expression, just as `!` is. `if` and `unless` simply behave in opposite ways according to the *truth* of the expression. – Borodin Jul 17 '17 at 23:52
  • If the "$value % 3" is false, the "return" EXPR can be found. If the "$value % 3" is true, the "return" EXPR can NOT be found, "if_not" will return "not $value % 3" and "unless" will return "$value % 3". – asthman Jul 18 '17 at 01:58
  • I think that the last statement before "Question" may have mislead. The point here is that `return if EXPR;` seems to have `if` (itself) be the last processed thing (so first `EXPR` is evaluated then `if` of its result), while `return unless EXPR;` seems to have `EXPR` processed last. (It's not about `not`. It's not about `return`-ing either.) If `if EXPR` and `unless EXPR` result in a different order of evaluations I think that that is significant. – zdim Jul 18 '17 at 03:10
  • @zdim: Neither `if` nor `unless` return a value and so cannot be "evaluated". The value being returned in the case where there is no explicit `return` statement is `EXPR`, which is `$value % 3` or `not $value % 3`. I don't see why you're saying ***"It's not about `not`. It's not about `return`-ing either."*** when it very clearly is. In both cases, `EXPR` is evaluated, then the `return` may be executed, depending on the value of `EXPR`. – Borodin Jul 18 '17 at 10:30
  • @zdim: Please explain this idea about "different orders of evaluation". – Borodin Jul 18 '17 at 10:36
  • I am voting to close this question on the grounds that the situation amounts to very bad programming, asking for some of the other ways Perl can be misused is off-topic. The OP appears unable or unwilling to accept any explanation of the reason behind it. It is not the intellectual curiosity that they appear to think it is. – Borodin Jul 18 '17 at 14:38
  • @Borodin You are entitled to your opinion but I take offence to being treated like a troll who lacks "intellectual curiosity". It takes time and effort to put together a question like this and I've been around SE for long enough to know the rules of engagement (by the looks of your profile, at least longer than you). As the OP I reserve the right to decide whether or not your answer addresses my question (and I have said on multiple occasions that I'm looking for examples, not explanations). If you insist on instilling best practices that really belongs in another question. – Zaid Jul 18 '17 at 15:41
  • @Zaid: *"I take offence to being treated like a troll who lacks* **"intellectual curiosity"** "* You are offended for no reason. That is not what I said. – Borodin Jul 18 '17 at 15:57
  • @Borodin It appeared that `if EXPR` and `unless EXPR` result in what I called "different order of evaluation" -- in the first one `EXPR` is evaluated first and then `if`, the other time apparently `EXPR` is the last thing evaluated. This is how it seemed and the question is about that. It came out of a discussion (where I thought that `unless` is processed as `if not`), and it certainly seemed interesting. In the end, your answer (and comment) explain it. So think that it is a good question and a good answer. I don't quite understand the close vote. – zdim Jul 18 '17 at 17:15
  • @zdim: But `if` and `unless` aren't "evaluated". They simply choose which of two statements will be the next to execute, and clearly `EXPR` *must* be evaluated before either `if` or `unless` as the value is required to make the choice. I didn't have a problem with the question at first—after all I answered it—but when the OP started insisting that he wanted examples of *where else* `unless` and `if not`, and then that I must exclude all situations with parentheses, it started to become artificial. If they would explain how they wanted to use the information then it may redeem the question. – Borodin Jul 18 '17 at 17:32
  • @Borodin Right, `EXPR` must go first -- but in that example (from another question) it seemed to us that in `if not EXPR` it wasn't, which would be very strange (while the missing `return` could explain it). Thus the question. I think that Zaid asked of possible other such behavior, assuming that they indeed behave differently in this context. In my understanding they ask about other cases precisely because this one (with incomplete `return`s) is not good practice and is artifical. So the question is about are there other such situtation, which may come up in "normal" code. – zdim Jul 18 '17 at 17:39
  • @Borodin In the end, per your answer, there is no "different behavior" -- one needs to compare `unless EXPR` with `if (!EXPR)` (but of course), and they both return the last expression, `!EXPR` though in the second case, so it's false (empty string). But the particular context of the original question (and the discussion, where I blame myself for slipping in a wrong statement) obscured it. – zdim Jul 18 '17 at 17:41
  • @zdim: I think we agree! `return "string" unless expression()` returns `expression() or "string"` while `return "string" if not expression()` returns `not( expression() ) or "string"` which will always be `1` if `expression()` is false. I also think that a question about *"are there other such situtation, which may come up in "normal" code"* would need to be closed as "too broad". – Borodin Jul 18 '17 at 17:49
  • @zdim: Yes, I sympathise. I really had to make a conscious effort *not* to chase experience points before I felt better about the several down votes that I get each week. Most of the time I never get to find out what was so egregious about my posts, but I am certain that a lot of it is politically motivated. If I ever see a problem with a post I will simply point it out in a comment or edit the post, but surprisingly many people like to hang on to their misperceptions anyway, and at that point the only remaining option is to down vote. – Borodin Jul 20 '17 at 08:07
  • @Borodin A few each week? I am sorry. The downvotes are definitely needed, but I do not like those "personal" ones. They just harm _everybody_ (never mind my mood or "points"). They misinform the future readers. It's just bad when it's not based on something. – zdim Jul 20 '17 at 09:16
  • 1
    @Borodin Agree -- the first two paragraphs in your answer cleared that I think. However, I disagree that the question "_would need to be closed_". I think that it is a good question. The perceived difference in behavior of basic constructs would be significant and should be brought up, by all means. It doesn't matter much how it's stated really -- for something like that it is OK to ask for "other examples." The explained "non-difference" in operation between `unless` and `if` is important to bring up, too, since `unless` is commonly taken as `if not`. – zdim Jul 20 '17 at 09:19
  • @Borodin Apparently, my early comment stands corrected -- it _is_ about the `not`, in a way. – zdim Jul 20 '17 at 09:21

1 Answers1

3

The difference is that, for your unless case, the last-evaluated expression is $value % 3 whereas for your if not case, the last-evaluated expression is not $value % 3

not behaves as a simple operator, whereas if and unless are language constructs like while and for that aren't involved in the value of expressions

In any case it's bad practice to rely on the returned value of a subroutine unless it has a return for every path, or it ends in an expression. perldoc perlsub says this

If no return is found and if the last statement is an expression, its value is returned. If the last statement is a loop control structure like a foreach or a while, the returned value is unspecified.

So just because you think you know what a subroutine without a return will evaluate too, it's not actually reliable unless you end your subroutine with an expression, and may well change in later versions of Perl.

Just write a return for the other case of your condition and your code will instantly become clearer. Or you could use a conditional expression which specifies the value to be returned for both conditions

cond_exp => sub {
    my ( $value ) = @_;

    $value % 3 ? "" : "$value is a multiple of 3";
}
Borodin
  • 126,100
  • 9
  • 70
  • 144
  • I thought the question made it clear, but I wasn't asking for an explanation of why there is a difference in behaviour between the two; I wanted to know if there are other examples where `unless` and `if not` behave differently. We are so used to seeing that `unless` can be rewritten as `if not` that the differences in the example above came as a bit of a surprise initially. Are there any other examples where the result of using `unless` vs `if not` results in different outcomes? – Zaid Jul 18 '17 at 10:36
  • @Zaid: Answering your question directly is pretty much impossible. Any other such circumstances would be as obscure as this one, and just because I cannot immediately imagine any other cases doesn't mean there are none. Your question comes from a misconception that the equivalence of `unless` and `if not` *doesn't mean that you can do a naive string replacement of one with the other* without changing the semantics of your code. I hoped that, if I explained the source of the discrepancy, you would be able to spot and avoid similar situations yourself. – Borodin Jul 18 '17 at 10:44
  • @Zaid: What people mean when they say `unless` is equivalent to `if not` is that `unless` and `if`may be swapped as long as their boolean control expression is also negated. You can't reliably invert the sense of a boolean expression by just putting `not` in front of it, and have to apply some intelligence to the rule. Note, for instance that an ordinary `unless` (not a statement modifier) has parentheses around its expression. But that doesn't mean that `unless ( $value ) { ... }` can be replaced by `if not ( $value ) { ... }`. That won't even compile. – Borodin Jul 18 '17 at 10:54
  • I appreciate your efforts to explain the misconception. Please note that the purpose of this question was to document examples where the two do not yield the same outcome, not to explain why the two forms gave different results in my example. Re: *... other such circumstances would be as obscure as this one... because I cannot immediately imagine any other cases doesn't mean there are none* - I'm not looking for a quick response. If there is no other example then so be it. But if (or when) a similar scenario is encountered I believe it would be useful to document it in a Q&A like this one. – Zaid Jul 18 '17 at 11:14
  • Re: *You can't reliably invert the sense of a boolean expression by just putting `not` in front of it* - to my feeble mind this is a bold claim. Could you share an example or two to clarify what you mean here? – Zaid Jul 18 '17 at 11:17
  • @Zaid: *"purpose of this question was to document examples where the two do not yield the same outcome"* I realise that, but since it was based on a misconception of the rule, I thought it would be more useful to explain that misconception than to produce an arbitrary list of examples. There are *plenty* of other examples, such as *every `if` statement*, as I described in my last comment. But surely you don't need to be told that those won't work? And the same should apply to every other situation as long as you *understand the meaning of the equivalence. – Borodin Jul 18 '17 at 11:31
  • @Zaid: Sure. `not $i and $j` is different from `not($i and $j)`. `not $i xor $j` is different from `not($i xor $j)`. `not ($i) + $j` is different from `not(($i) + $j)`. – Borodin Jul 18 '17 at 11:41
  • For the purposes of this question I think it's fair to not worry about the non-parenthesized variants, so we're comparing `unless(EXPR)` against `if not(EXPR)`. – Zaid Jul 18 '17 at 13:28
  • @Zaid: Then you're exploring an entirely hypothetical situation, and there's even less point in your original question. Discarding parentheses is just removing the facility for explicit control over the order of evaluation of an expression which is determined implicitly by operator priority. `unless $i and $j` is different from `if not $i and $j` *entirely because* the latter means `if not($i) and $j`. If you're prohibiting the use of parentheses then you're just making it much harder to talk about the subject. – Borodin Jul 18 '17 at 14:28
  • @Zaid: The bottom line is that `if` and `unless` are *language constructs* and not operators, so the distinction in this case is because the boundary between the language word and the expression is moved depending on whether you use `unless EXPR` or `if not EXPR`. I don't see the point in pursuing this question any further, especially now that you are starting to impose arbitrary conditions. – Borodin Jul 18 '17 at 14:32
  • 1
    @Zaid I believe that the first paragraph (along with the second) explains the blunder in our conversation that lead to this. (I take credit for the blunder since I raised "_`unless` is `if not`_" error.) We should compare `unless` with `if` (no `not`!) and then there is no confusion: `unless (EXPR)` returns the remainder, while `if (!EXPR)` the untrue. So the behavior is consistent. The first two paragraphs clear that. I still think that it is a good and worthy question, and this post answers it. – zdim Jul 18 '17 at 17:19
  • @zdim: I think it's fine to say that `unless` is equivalent to `if` not as long as you apply some intelligence to it instead of expecting to be able to `s/unless/if not/` a program and have it still work. To be accurate we need some parentheses. The *statement modifier* `... unless *expr*` is identical to `... if not(*expr*)` while the *statement* `unless ( *expr* ) { ... }` is identical to `if ( not(*expr*) ) { ... }`. The point is that `unless` is the *opposite test* to `if`, so the value of the *expression* must also be inverted to give the same behaviour. – Borodin Jul 18 '17 at 17:41
  • 1
    Agreed, and that is precisely what slipped, that last sentence in your comment. When I finally got to test it better I came up with `return unless $_[0] > 5` against `return if $_[0] <= 5` (in two subs) and the follow-up to that lit up the bulb. I think that I had had somewhere in my mind that `unless` is `if not` -- which it isn't. I do think that it is worth clearing up in a Q/A. – zdim Jul 18 '17 at 18:01