53
$\begingroup$

I have been working on picking expressions apart using Head and Part and encountered a little mystery. Consider the canonical example

a + b + c

which has FullForm

Plus[a, b, c]

We expect, then, (a+b+c)[[0]] === Plus === Head[a+b+c] to be True, and it is. But then, we would expect (a+b+c)[[1;;3]] would be {a, b, c}, wouldn't we? But it isn't. The following is true:

(a + b + c)[[1;;3]] === (a + b + c)

Somehow, [[1;;3]], which is supposed to pick off elements 1 through 3 of its argument and put them in a List, doesn't get rid of the Head, which is element 0!

The questions are, then (and I will be grateful for hints and answers!)

  1. Why doesn't (a + b + c)[[1;;3]] get rid of (a + b + c)[[0]], the Head?

  2. What is the right way to get rid of the Head?

  3. (a + b + c)[[0;;3]] produces 0. I would expect it to produce {Plus, a, b, c}. Instead, it produces 0. This just deepens the mystery for me! Why?

$\endgroup$

2 Answers 2

56
$\begingroup$

Point #1

Part always wraps element sequences with the original head of the expression.

expr = Hold[1 + 1, 2 + 2, 3 + 3, 4 + 4, 5 + 5];

expr[[{2, 3}]]
Hold[2 + 2, 3 + 3]

For this purpose a single part e.g. 1 is not a sequence but {1} and 1 ;; 1 are:

expr[[1]]

expr[[{1}]]

expr[[1 ;; 1]]
2

Hold[1 + 1]

Hold[1 + 1]

This applies at every level of the extraction:

exp2 = g[h[1, 2], i[3, 4]];

exp2[[  2 , 1  ]]
exp2[[ {2}, 1  ]]
exp2[[  2 ,{1} ]]
exp2[[ {2},{1} ]]
3

g[3]

i[3]

g[i[3]]

I used this nontrivially for Elegant manipulation of the variables list.

Point #2

Consider instead using Extract which wraps sequences in List:

Extract[expr, {{2}, {3}}]
{4, 6}

The third argument of Extract can be used to specify a function to apply to individual elements before they are evaluated:

Extract[expr, {{2}, {3}}, HoldForm]
{2 + 2, 3 + 3}

If you want all parts you can also use Level:

Level[(a + b + c), {1}, Heads -> True]
{Plus, a, b, c}

Or Cases:

Cases[(a + b + c), _, Heads -> True]
{Plus, a, b, c}

Or Replace/ReplaceAll:

(a + b + c) /. head_[body___] :> {head, body}
{Plus, a, b, c}

Point #3

The last point is more tricky and I had to check it myself. There is a behavior that I also did not expect:

Range[5][[0 ;; 5]]
{}

What I expected was an error as seen here:

Range[5][[0 ;; 4]]

and here:

Range[5][[0 ;; 6]]

When Span is used in Part[x, 0 ;; n] where n is the length of x, Part returns the head of the expression. Therefore (a + b + c)[[0;;3]] returns Plus[] and Plus[] evaluates to 0.


I believe Span behaves this way because of how it handles non-positive values, and zero-length spans. Consider:

Range[10][[-2 ;; 10]]
{9, 10}

You can see that it wraps around. Now consider:

Range[10][[5 ;; 4]]
{}

An empty span returns the head of the expression with no arguments.

Using 0 ;; n where n is the last element in the list, or 0 ;; -1, is also an empty span wrapping around the open end of the list.

Range[10][[0 ;; -1]]
{}
$\endgroup$
7
  • 2
    $\begingroup$ Ah, I see that you can answer #1 as well, not just Leonid :) $\endgroup$
    – Verbeia
    Feb 29, 2012 at 6:26
  • 1
    $\begingroup$ Regarding point #3, you might find this discussion relevant (if not too enlightening). $\endgroup$
    – Szabolcs
    Feb 29, 2012 at 11:47
  • $\begingroup$ @Szabolcs That's important information. I cannot recall a situation where it would affect me, but it would be bloody annoying to track it down if it did. $\endgroup$
    – Mr.Wizard
    Feb 29, 2012 at 16:36
  • 1
    $\begingroup$ And another place for the all-powerful Cases: Cases[{a + b + c}, head_[args___] -> {head, args}] produces {{Plus, a, b, c}} $\endgroup$
    – Reb.Cabin
    Mar 8, 2012 at 23:04
  • 2
    $\begingroup$ @Reb doing that route you don't even need Cases: a + b + c /. head_[args___] :> {head, args} $\endgroup$
    – Mr.Wizard
    Mar 9, 2012 at 0:06
16
$\begingroup$

Perhaps only the developers (or Leonid) can answer #1 and #3 and "why".

The quick answer to #2 is Apply, remembering that List is a Head like any other:

List @@ (a + b + c)

{a, b, c}

On #3, I can't explain why ;; syntax gives 0 when

(a + b + c)[[Range[0, 3]]]

gives

a + b + c + Plus

So you could try

List @@ ((a + b + c)[[Range[0, 3]]])

{a, b, c, Plus}

$\endgroup$
6
  • $\begingroup$ There is something broken in the way the site previews answers. I have been amending my answer for the last twenty minutes and only now do I see that you also posted an answer. $\endgroup$
    – Mr.Wizard
    Feb 29, 2012 at 6:48
  • $\begingroup$ @Mr.Wizard You don't get notified of a new answer when editing your already existing answer... $\endgroup$
    – rm -rf
    Feb 29, 2012 at 8:15
  • 1
    $\begingroup$ The List @@ answer has a deep, hackish charm, coming closest to answering "... and everything but Head.". The Extract and Level answers have more versatility and clearly enable complete analysis and compilation of expressions. A metacircular evaluator for MMA would not seem too remote a possibility. $\endgroup$
    – Reb.Cabin
    Feb 29, 2012 at 8:17
  • 2
    $\begingroup$ In some sense, Sequence@@(a+b+c) comes closer to "everything but Head" than List@@(a+b+c): While List is just another head (with a special input/display syntax associated with it), Sequence is the closest Mathematica has to a headless expressions: If you insert it anywhere (except functions with attribute HoldAllComplete) it just gets interpolated. $\endgroup$
    – celtschk
    May 21, 2012 at 19:35
  • 1
    $\begingroup$ @celtschk For completeness the attribute SequenceHold also prevents the flattening of Sequence expressions, just as HoldAllComplete does, while allowing other evaluation. $\endgroup$
    – Mr.Wizard
    Aug 14, 2017 at 6:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.