4

Could you explain the details for the following code?

foo[x_, y_] := {a, If[x === y, b, Sequence @@ {}]}
foo[s, s]
foo[s, t]

Output:

{a,b}
{a}

Why isn't the output as follows?

{a,b}
{a,}
Display Name
  • 2,009
  • 8
  • 17
  • If you want {a,} as the result you could use Null instead of Sequence @@ {} in your If statement. – Jason B. May 13 '21 at 14:58
  • @JasonB Yes. I know it. Thanks. – Display Name May 13 '21 at 15:00
  • Since Sequence @@ {} === Nothing evaluates to True, the definition is equivalent to foo[x_, y_] := {a, If[x === y, b, Nothing]} – Bob Hanlon May 13 '21 at 17:20
  • @BobHanlon: Thank you very much. A good explanation! – Display Name May 13 '21 at 17:30
  • @BobHanlon I am actually quite surprised that Sequence[] === Nothing evaluates to True, since these are not structurally equivalent. In particular, for a generic head h, this will give False: h[Sequence[]] === h[Nothing]. – Leonid Shifrin May 14 '21 at 00:47
  • 1
    @LeonidShifrin - In looking at it closely using Trace, the reason that Sequence[] === Nothing evaluates to True is that SameQ[Sequence[], Nothing] evaluates to SameQ[Nothing] and SameQ with any single argument evaluates to True, e.g., SameQ[2] == SameQ[a] == True is True. I would have expected SameQ to complain if given a single argument. – Bob Hanlon May 14 '21 at 01:10
  • @BobHanlon Indeed, I should've figured that out :) Thanks. I guess I am getting out of shape...Not sure what the rationale was behind this semantics of SameQ, I agree with you in that I would also expect it to complain. – Leonid Shifrin May 14 '21 at 03:00

1 Answers1

6

Summary

The behaviour we see is a consequence of the way Sequence[] expressions are processed. They are only expanded when they are explicitly present as arguments to a function. In the case at hand, Sequence@@{} only takes such a form after evaluation. But If has the attribute HoldRest so that evaluation does not take place until after the argument list has already been assembled and the If function invoked.

Details

We start from the observation that the following three expressions give differing results:

{a, If[1==2, unused]} // InputForm
(* {a, Null} *)

{a, If[1==2, unused, Sequence[]]} // InputForm (* {a, Null} *)

{a, If[1==2, unused, Sequence@@{}]} // InputForm (* {a} *)

If[1==2, unused] -> Null

The first result is explained by the following documentation for If:

If[condition, t] gives Null if condition evaluates to False.

Thus:

If[1==2, unused] // InputForm
(* Null *)

{a, If[1==2, unused]} // InputForm (* {a, Null} *)

If[1==2, unused, Sequence[]] -> Null

The second result is a consequence of the attributes of If:

Attributes[If]
(* {HoldRest, Protected} *)

If does not have the attribute SequenceHold so prior to evaluation any explicit appearances of arguments with the head Sequence will be spliced into the argument list. Sequence expansion is independent of evaluation, so it occurs despite the presence of HoldRest which inhibits evaluation of the second and third arguments.

In this case, an empty sequence is spliced in so the net result is the same as the first example (i.e. as if If were invoked with only two arguments:

If[1==2, unused, Sequence[]] // InputForm
(* Null *)

{a, If[1==2, unused, Sequence[]]} // InputForm (* {a, Null} *)

If[1==2, unused, Sequence@@{}] -> Sequence[]

The argument Sequence@@{} is not evaluated when it is passed to If due to the presence of the HoldRest attribute. This means that the head of the argument expression is actually Apply, not Sequence:

Head[Unevaluated[Sequence@@{}]]
(* Apply *)

Since the head is not explicitly Sequence, it does not get spliced into the argument list and so the If expression remains in three-argument form instead of two-argument form. Since the condition is false the third argument Sequence@@{} is evaluated to Sequence[] and returned:

If[1==2, unused, Sequence@@{}] // InputForm
(* Sequence[] *)

{a, If[1==2, unused, Sequence@@{}]} // InputForm (* {a} *)

In that last expression the result is notionally {a, Sequence[]}. But since the second argument to List is explicitly Sequence[], an empty sequence gets spliced into the argument list.

WReach
  • 68,832
  • 4
  • 164
  • 269
  • +1, very nice answer. IIRC, one of the main motivating factors for introducing Nothing was to simplify this If[...] idiom and avoid using Sequence in such cases like this. – Leonid Shifrin May 13 '21 at 17:44