Analysis of the problem
All functions in Mathematica either hold one or more of their arguments, or per The Standard Evaluation Procedure the arguments are evaluated before the function is applied. Thread has no HoldFirst attribute therefore it falls into the latter category.
Because of this in an expression like Thread[Print[{a, b, c}, " = ", {1, 2, 3}]]; Print evaluates entirely free of Thread, and Thread only sees the result of that which is Null.
Manual evaluation control
The typical solution to this problem is to manually take control of the evaluation order. A common recommendation is MapThread as the head to be applied is kept separate from the expression elements until construction is complete:
MapThread[Equal, {{1, 2, 3}, {3, 2, 1}}]
This is hardly a complete solution however as MapThread only operates over List and does not handle singletons.
Sometimes one can use Thread and Apply a head afterward, which handles singletons:
Print @@@ Thread[{{a, b, c}, " = ", {1, 2, 3}}];
a = 1
b = 2
c = 3
One may wonder why Thread does not have HoldFirst as referenced at the start. The mechanism of Unevaluated effectively gives us a one-off operation as though that attribute applied:
Thread[ Unevaluated[ {1, 2, 3} == {3, 2, 1} ] ]
{False, True, False}
This is not a general solution however because it interferes with expected evaluation in other cases:
p = {1, 2, 3};
q = {3, 2, 1};
Thread[ Unevaluated[ foo[p, q] ] ]
foo[{1, 2, 3}, {3, 2, 1}]
More complicated cases may require one to inject various evaluated forms into an Unevaluated expression:
p = a + b + c;
q = x + y + z;
With[{p = p, q = q},
Thread[ Unevaluated[p*q*r], Plus]
]
a r x + b r y + c r z
Automated solution
Wouldn't it be nice to have Thread just work in most cases without having to resort to manual evaluation control? I think so, and I am going to try to make it happen.
Here is my initial attempt. It surely has limitations and I expect more than a couple of bugs, but already I think it is applicable in a wide range of cases. I hope that with feedback from the community I can continue to refine and extend it.
I will be using a modified form of my step function from How do I evaluate only one step of an expression? with a specific container to differentiate its results from appearances of HoldForm.
The code in provided at the bottom to avoid interrupting the flow of this long post.
Basic examples
autoThread[{1, 2, 3} == {3, 2, 1}]
{False, True, False}
autoThread[Print[{a, b, c}, " = ", {1, 2, 3}]];
a = 1
b = 2
c = 3
p = {1, 2, 3};
q = {3, 2, 1};
autoThread[foo[p, q]]
{foo[1, 3], foo[2, 2], foo[3, 1]}
p = a + b + c;
q = x + y + z;
autoThread[p*q*r, Plus]
a r x + b r y + c r z
Advanced stepwise evaluation
By leveraging the special evaluation provided by step my function can do things that are almost impossible otherwise.
x := 1 + 2 + 3;
y := a + b + c
autoThread[x*y, Plus]
a + 2 b + 3 c
foo[6] := x*y
bar := foo;
autoThread[bar[2*3], Plus]
a + 2 b + 3 c
autoThread Code
SetAttributes[{step, $stepHold}, HoldAll]
step[expr_] :=
Module[{P},
P = (P = Return[$stepHold @@ #, TraceScan] &) &;
TraceScan[P, expr, TraceDepth -> 1]
]
Attributes[autoThread] = HoldFirst;
autoThread[body_] := autoThread[body, List]
autoThread[body : _[___, h_[___], ___], h_, seq_: All] :=
Thread[Unevaluated @ body, h, seq]
autoThread[body : f_[arg___], h_, seq_: All] :=
With[{new =
Replace[
MapAll[step, Unevaluated[body], Heads -> True],
(step | $stepHold)[x_] :> x, -1, Heads -> True
]},
(new /. $stepHold[eval_] :> autoThread[eval, h, seq])
/; new =!= $stepHold[body]
]
autoThread[else_, x___] :=
step[else] /. $stepHold[eval_] :> autoThread[eval, x]
autoThreadon this problem: I wanted to divide both sides of equation by something. Here is an example of what I do nowClearAll[y, x, s];eq = y''[x] + y'[x] == y[x];ode = eq /. y -> Function[{x}, Exp[s[x]]]Now I want to divide both sides byExp[s[x]]to cancel that common term out. Currently I doSimplify@Thread[ode/Exp[s[x]], Equal]. I triedautoThreadlike thisSimplify[autoThread[ode/Exp[s[x]]]]and few other ways, but not sure how to use it :) When I doSimplify[autoThread[ode/Exp[s[x]]],Equal]it does not give same result as theThreadcommand. – Nasser Feb 26 '17 at 04:47SameQ[ Simplify@Thread[ode/Exp[s[x]], Equal], Simplify @ autoThread[ode/Exp[s[x]], Equal] ]isTrue. Obviously my code does not simplify things here but it also does not fail. You must of course still specifyEqualas the second parameter ofautoThreador there is no hope for it to guess which head to thread over in the more general picture. – Mr.Wizard Feb 26 '17 at 11:16