4
$\begingroup$

I am trying to use Thread to apply a function involving RandomReal but it does not work as I expected.

If I have the function

f[x_, y_] := x + y

I can use Thread to map over a list of x values of interest

Thread[f[{1, 2, 3}, 4]]

I cannot, however, do this if I have the function

f[x_, y_] := RandomReal[{x , y}]

If I call the Thread function again I get the following error error message error message

I expected to get as output the results of

{f[1,4], f[2,4], f[3,4]}

so basically 3 random numbers. Probably more importantly than why this doesn't work - is how can I replicate this behaviour when using RandomReal in the function?

$\endgroup$
  • $\begingroup$ Side note: f[#, 4]&/@{1,2,3} is more natural in this case. $\endgroup$ – xzczd Jun 30 at 3:57
  • $\begingroup$ @xzczd I didn't see your comment but we're thinking the same. The mechanism of Thread is somewhat a bit of complicated and I rarely use it. $\endgroup$ – SneezeFor16Min Jun 30 at 4:44
6
$\begingroup$
Thread[Unevaluated@f[{1, 2, 3}, 4]]
{1.946182809, 2.842898138, 3.571798651}

Explanation

Documentation of Thread:

Thread evaluates the whole expression before threading.

f[x_, y_] := x + y

Thread[f[{1, 2, 3}, 4]]
=> Thread[{1, 2, 3} + 4] (* Evaluates *)
=> Thread[{5, 6, 7}]
=> {5, 6, 7} (* Threads... trivially *)

Since + is Listable, nothing went wrong even though f was evaluated first.

f[x_, y_] := RandomReal[{x, y}]

Thread[f[{1, 2, 3}, 4]]
=> Thread[RandomReal[{1, 2, 3}, 4]] (* Evaluates... *)
=> (* Error *)

"Evaluate first" passes wrong arguments to RandomReal.

f[x_, y_] := RandomReal[{x, y}]

Thread[Unevaluated@f[{1, 2, 3}, 4]]
=> Thread[f[{1, 2, 3}, 4]] (* Evaluates *)
=> {f[1,4],f[2,4],f[3,4]} (* Threads *)
=> ...

Unevaluated helps f survive the evaluation.


f[x_, y_] := RandomReal[{x, y}]
list = {1, 2, 3};

Thread[Unevaluated@f[list, 4]]
=> Thread[f[list, 4]] (* Evaluates *)
=> f[list, 4] (* Threads... Nothing to thread! *)
=> RandomReal[{1, 2, 3}, 4]
=> (* Error *)

list should not survive the evaluation, while f should. We can use Inactive on f and Activate afterwards:

f[x_, y_] := RandomReal[{x, y}]
list = {1, 2, 3};

Activate@Thread[Inactive[f][list, 4]]
=> Activate@Thread[Inactive[f][{1, 2, 3}, 4]] (* Evaluates *)
=> Activate@{Inactive[f][1,4], Inactive[f][2,4], Inactive[f][3,4]} (* Threads *)
=> {f[1,4], f[2,4], f[3,4]} (* Activates *)
=> ...

Actually, there're many simpler and faster ways to do this, like:

f[#, 4] & /@ list
| improve this answer | |
$\endgroup$
  • $\begingroup$ Hope I can get away with a follow-up question. Why does this fail if you replace the {1,2,3} with something like list = {1,2,3}; Thread[Unevaluated@f[list, 4]] ? Is it because of the Unevaluated? $\endgroup$ – Esme_ Jun 29 at 22:57
  • $\begingroup$ @Esme_ Yes, in this case the {1,2,3} is not explicit. In other words, Thread cannot see the {1,2,3} inside list. A possible solution: list = {1, 2, 3}; With[{list = list}, Thread[Unevaluated@f[list, 4]]], another: list = {1, 2, 3}; Thread[Unevaluated@f[#, 4]] &@list. $\endgroup$ – xzczd Jun 30 at 3:48
  • $\begingroup$ @Esme_ Yes, please see my updated answer. $\endgroup$ – SneezeFor16Min Jun 30 at 4:04
  • $\begingroup$ Thanks SneezeFor16Min and xzczd for the explanations and detailed answers. Despite many years of using Mathematica I'm still only touching the surface! $\endgroup$ – Esme_ Jun 30 at 5:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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