37
$\begingroup$

This is a bid at creating a canonical Q&A.

Many questions have been asked that come down to the fact that Thread evaluates its first argument before threading is attempted. In a few minutes I found:

Basic examples of the problem:

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

(One might have expected {False, True, False})

Thread[Print[{a, b, c}, " = ", {1, 2, 3}]];

{a,b,c} = {1,2,3}

(One might have expected a=1, b=2, c=3, on three separate lines.)

How can this most easily and robustly be addressed?

$\endgroup$

3 Answers 3

25
$\begingroup$

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]
$\endgroup$
2
  • $\begingroup$ How would one use autoThread on this problem: I wanted to divide both sides of equation by something. Here is an example of what I do now ClearAll[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 by Exp[s[x]] to cancel that common term out. Currently I do Simplify@Thread[ode/Exp[s[x]], Equal]. I tried autoThread like this Simplify[autoThread[ode/Exp[s[x]]]] and few other ways, but not sure how to use it :) When I do Simplify[autoThread[ode/Exp[s[x]]],Equal] it does not give same result as the Thread command. $\endgroup$
    – Nasser
    Commented Feb 26, 2017 at 4:47
  • $\begingroup$ @Nasser SameQ[ Simplify@Thread[ode/Exp[s[x]], Equal], Simplify @ autoThread[ode/Exp[s[x]], Equal] ] is True. Obviously my code does not simplify things here but it also does not fail. You must of course still specify Equal as the second parameter of autoThread or there is no hope for it to guess which head to thread over in the more general picture. $\endgroup$
    – Mr.Wizard
    Commented Feb 26, 2017 at 11:16
18
$\begingroup$

First, let me tell you that my answer here is by no means a replacement for the trickier, but more capable implementation of Mr. Wizard. What I want to show is that the examples I point out at the end can be handled with a shorter approach. Keep this in mind. For a new user of Mathematica, the implementation below might serve as a starting point in understanding, why we need evaluation control for this kind of problem.

The following solution works by temporarily replacing the head, the outermost function, of a given expression with some dummy function that does nothing. Since the dummy function does nothing, Thread can do its work and afterward, we simply re-substitute the original head.

As Kuba pointed out, in some cases, it might be a good idea to give the dummy function the same attributes as the original head. I will give an example when this is a good idea.

SetAttributes[myThread, {HoldAllComplete}];
myThread[head_[args__]] := Module[{dummy},
  SetAttributes[dummy, Attributes[head]];
  Thread[dummy[args]] /. dummy :> head
]

Here are the examples that give the desired output:

myThread[{1, 2, 3} == {3, 2, 1}]
(* {False, True, False} *)

and

myThread[Print[{a,b,c}," = ",{1,2,3}]];
(*
a = 1
b = 2
c = 3
*)

and

lst1 = {{5, -9, 15}, {12, -15, 4}};
indices = {{2, 1, 3}, {1, 3, 2}};

myThread[Part[lst1, indices]]     
(* {{-9, 5, 15}, {12, 4, -15}} *)

and

myThread@D[{x, y}, {x, y}]
(* {1, 1} *)

and

findPrime[n_] := 
  If[PrimeQ[n], i = 1; While[Prime[i] < n, i = i + 1]; i, False];

myThread[findPrime[{7, 8, 37, 127}]]
(* {4, False, 12, 31} *)

and

myThread[{2, 3, 5, 4, 1, 8, 7}~Max~{1, 4, 6, 3, 2, 8, 8}]
(* {2, 4, 6, 4, 2, 8, 8} *)

and finally

appendTo[ll_, item_] := Map[Append[#, item] &, ll];
myThread[appendTo[list1, list2]]
(* {{{1, 2, "a"}, {3, 4, 5, "a"}}, {{1, 3, 5, "b"}, {9, 8, 
   "b"}}} *)

I left out the "C:.." example because there is no such thing on my machine but it should work as well. One example where we need to give dummy the original attributes of the head is this

myThread[{1, 2, 3} :> {Print[1], Print[2], Print[3]}]
(* {1 :> Print[1], 2 :> Print[2], 3 :> Print[3]} *)

If we don't do this, the Print statements would be evaluated and we would be left with

{1:>Null,2:>Null,3:>Null}

which is not wanted.

$\endgroup$
7
  • 3
    $\begingroup$ Consider adding: Attributes[h] = Attributes[head] in order to not leak evaluation from e.g. autoThread[{1, 2, 3} :> {Print[1], Print[2], Print[3]}] $\endgroup$
    – Kuba
    Commented Feb 26, 2017 at 10:18
  • 1
    $\begingroup$ @Mr.Wizard I'm sorry, I haven't read through all linked posts and didn't look at the bottom of your implementation because I wanted to think about it without seeing your solution. I would have seen the Thread[expr,head] examples otherwise. I particularly looked through your example post and thought a simpler solution for them can probably be grasped easier than your involved solution. One problem with Block is that you cannot block some basic symbols like List, Plus, ... which is why I chose the Module approach. I will point this out and change my post. $\endgroup$
    – halirutan
    Commented Feb 26, 2017 at 18:29
  • $\begingroup$ @Mr.Wizard I didn't want to challenge your full solution, and specifically, I didn't want that you have to defend the solution you have thought so deeply about. $\endgroup$
    – halirutan
    Commented Feb 26, 2017 at 18:31
  • $\begingroup$ @Kuba Good point. I'll include this. $\endgroup$
    – halirutan
    Commented Feb 26, 2017 at 18:32
  • $\begingroup$ Forgive me for getting defensive. Good point regarding Module rather than Block as well. I got the feeling you were saying this code would/should replace mine, but I see that is not the case. $\endgroup$
    – Mr.Wizard
    Commented Feb 26, 2017 at 18:47
12
$\begingroup$

Specific solutions

Since my goal is for not only this Question to become canonical but also my solution my function must be applicable to most cases that come up.

$\endgroup$

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.