What is the rationale for Thread not threading as a side effect if the arguments of the function to be threaded can be fully evaluated?

For example,

ClearAll[b];


{True,2==b,True}


But

ClearAll[b];
b=2;


gives (no threading, and a completely different type of result)

True


And

ClearAll[b];


gives

{True,True,True}


What's the purpose of the behavior? Shouldn't Thread always thread?

Note: the question is how the comes about ("Thread evaluates its arguments"); but why it makes sense to do this? Why make whether threading actually happens contingent on the state of arguments.

• I think this was answered in your earlier post. The argument is evaluated prior to threading and returns True. Using Trace will illuminate the internal processing. – Mike Honeychurch Dec 14 '15 at 22:44
• @MikeHoneychurch: No. That's not the question. It's about what purpose that serves (specifically why have Thread not thread, as a side effect), not how it comes about; see the question. – orome Dec 14 '15 at 22:46
• Thread is always threading. However you see something different to what you want because arguments are evaluated prior to threading. There is no HoldFirst or similar attribute. – Mike Honeychurch Dec 14 '15 at 23:12
• Frequent use of ?? or Attributes is highly recommended. – m_goldberg Dec 15 '15 at 1:08
• This is described under Possible issues on the Thread help page. – Sjoerd C. de Vries Dec 21 '15 at 11:15

Mathematica evaluates subexpressions in a bottom up fashion:

Trace[Thread[{1, 2, 3} == {1, 2, 3}]]

{{{1,2,3}=={1,2,3},True},Thread[True],True}


This means by the time Thread is being applied, it's argument is already True.

One way around this is to use Unevaluated.

Thread[Unevaluated[{1, 2, 3} == {1, 2, 3}]]

{True, True, True}


## Edit

If Thread were to have some sort of HoldFirst attribute, it might not be as well defined as you might think.

The question is, how much partial evaluation is allowed?

1. If no partial evaluation is allowed, then

a = {1, 2, 3}; b = {x, y, z}; Thread[a == b]


would return a == b, since the depths of the unevaluated a and b are 0.

1. Suppose h[a, b] is the input, and it evaluates h, a, and b but doesn't evaluate their composition. Then

Thread[{1, 2, 3} == {x, y, z} /. {x -> 1}]


would not work as expected.

So the moral here is it's a bit trickier than one might think, and to account for a held attribute, one would need to often do tricks like

Thread[#]&[expr]


or

With[{e = expr}, Thread[e]]


which makes code messier.

• The question isn't how the behavior comes about, it's what purpose that behavior serves. Why should Thread be designed to sometimes thread, and sometimes not thread over it's arguments. Especially since whether it does or not is dependent on what happens to be defined at the time in its context. – orome Dec 14 '15 at 22:57
• @raxacoricofallapatorius See my edit. Let me know if you agree with this or not. It's partially gut feelings on my part. – Chip Hurst Dec 14 '15 at 23:11
• @raxacoricofallapatorius Thread is always threading. However you see something different to what you want because arguments are evaluated prior to threading. There is no HoldFirst or similar attribute. – Mike Honeychurch Dec 14 '15 at 23:11
• @raxacoricofallapatorius you didn't upset me or make me feel threatened and I hope I didn't to you either. You can criticize the design of Mathematica as much as you like, but the point I had intended was: given that it has a particular design, it's generally better for built-in functions to consistently follow that rather than each having their own special semantics. If a user-defined function with non-standard evaluation is desired, there are tools provided within the language to construct that. – Oleksandr R. Dec 15 '15 at 1:11
• @raxacoricofallapatorius take t[h_[args__List]] := MapThread[h, {args}] and x = {1, 2, 3} == {a, b, c}. Now t[x] threads like Thread but using MapThread. You can make the "converse" function as well. – Oleksandr R. Dec 15 '15 at 1:18