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,}
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,}
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.
Nothing was to simplify this If[...] idiom and avoid using Sequence in such cases like this.
– Leonid Shifrin
May 13 '21 at 17:44
{a,}as the result you could useNullinstead ofSequence @@ {}in yourIfstatement. – Jason B. May 13 '21 at 14:58Sequence @@ {} === Nothingevaluates toTrue, the definition is equivalent tofoo[x_, y_] := {a, If[x === y, b, Nothing]}– Bob Hanlon May 13 '21 at 17:20Sequence[] === Nothingevaluates toTrue, since these are not structurally equivalent. In particular, for a generic headh, this will giveFalse:h[Sequence[]] === h[Nothing]. – Leonid Shifrin May 14 '21 at 00:47Trace, the reason thatSequence[] === Nothingevaluates toTrueis thatSameQ[Sequence[], Nothing]evaluates toSameQ[Nothing]andSameQwith any single argument evaluates toTrue, e.g.,SameQ[2] == SameQ[a] == TrueisTrue. I would have expectedSameQto complain if given a single argument. – Bob Hanlon May 14 '21 at 01:10SameQ, I agree with you in that I would also expect it to complain. – Leonid Shifrin May 14 '21 at 03:00