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]