-2

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];
Thread[{1,2,3}=={1,b,3}]

gives (threading)

{True,2==b,True}

But

ClearAll[b];
b=2;
Thread[{1,2,3}=={1,b,3}]

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

True

And

ClearAll[b];
Thread[{1,2,3}=={1,b,3}]/.b->2

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.

orome
  • 12,819
  • 3
  • 52
  • 100
  • 2
    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
  • 2
    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
  • @OleksandrR.: As MapThread. – orome Dec 14 '15 at 23:37
  • Surely you have enough Mathematica experience to know, when using a built-in function, you must keep in mind how and when its arguments are evaluated. In particular, for functions that follow the standard evaluation rules, you must keep in mind that all arguments get fully evaluated before the function body sees them. – m_goldberg Dec 15 '15 at 00:43
  • @m_goldberg: Yes, but there are enough exceptions that its confusing. The issue is really from moving back and fourth among languages. Mathematica has some misleading (not bad, just misleading) features. I keep having to remind myself that its all just pattern matching, and that what look like functions aren't functions and what look like types aren't types. – orome Dec 15 '15 at 00:50
  • 2
    Frequent use of ?? or Attributes is highly recommended. – m_goldberg Dec 15 '15 at 01:08
  • 1
    This is described under Possible issues on the Thread help page. – Sjoerd C. de Vries Dec 21 '15 at 11:15
  • @SjoerdC.deVries: Understood. I think this question got off on the wrong foot. Chip Hurst fortunately understood what the issue was and how to address it. – orome Dec 23 '15 at 13:19

1 Answers1

8

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.

Greg Hurst
  • 35,921
  • 1
  • 90
  • 136
  • 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. – Greg 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
  • @MikeHoneychurch: Not really. If it evaluates its arguments its not threading; see MapThread. – orome Dec 14 '15 at 23:13
  • @ChipHurst: What about MapThread. That behaves as expected but doesn't have these issues. Am I missing something about how that works by comparison? – orome Dec 14 '15 at 23:14
  • @raxacoricofallapatorius no point in circular discussion. Examine the Trace and the Attributes and you must conclude that it is threading and behaving as expected. If an expression cannot be threaded, e.g. Thread[True] then what do you expect to occur? – Mike Honeychurch Dec 14 '15 at 23:16
  • MapThread squelches some auto evaluation: MapThread[Equal, {{1, 2, 3}, {1, 2, 3}}]. This choice of syntax does a good job at preventing some auto evaluation because the head List is inert. A better example is Thread[SameQ[list1, list2]] will always return True or False, whereas MapThread[SameQ, {list1, list2}] will always return a list. – Greg Hurst Dec 14 '15 at 23:20
  • @MikeHoneychurch: So why have a circular discussion when it could be avoided by attempting to answer the question? Thread[f[{a,b,c},{1,2,3}]] threads if the result is {f[a,1],f[b,2],f[3,c]}. But Thread sometimes does not do that, and whether it does depends on whether a, b, c and f can be evaluated. That's the phenomenon that seems odd (at least coming from a better organized language like, say Haskell). (How Thread[True] evaluates is irrelevant; what's relevant is why the argument to Thread is allowed to become True when it was, say f[{a,b,c},{1,2,3}].) – orome Dec 14 '15 at 23:22
  • @raxacoricofallapatorius It is circular because an explanation has been provided (also in your other recent question). There is no HoldFirst attribute. Therefore you are attempting to do Thread[True] which returns True. If you can show an example where Thread does not actually attempt to thread then I will comment further but as we have seen your examples do not show that. – Mike Honeychurch Dec 14 '15 at 23:37
  • @MikeHoneychurch: What do you mean by "there is no HoldFirst attribute? The question (and what is meant by "doesn't thread") should be clear. What does MapThread do differently? Are you saying it is impossible to design Thread[f[{a,b,c},{1,2,3}]] so that it works like MapThread[f, {{a,b,c},{1,2,3}}] (as the docs actually sort of imply it does)? If that's the case, it may be the answer to the question. – orome Dec 14 '15 at 23:43
  • @raxacoricofallapatorius MapThread[f, {{a, b, c}, {1, 2, 3}}] does not literally contain f[{a, b, c}, {1, 2, 3}], which consequently is not a possible result of evaluation no matter what attributes MapThread has. It's as simple as that. – Oleksandr R. Dec 14 '15 at 23:46
  • @OleksandrR.: That's clear. But does that mean it is impossible to make Thread behave like MapThread. If so, that may the answer. If not, the question remains: why choose not to? – orome Dec 14 '15 at 23:47
  • @raxacoricofallapatorius no, of course it's not impossible. But it's not the way Mathematica's evaluation sequence normally works. You're perfectly free to circumvent the ordinary sequence of evaluation if that's what you want in a given case, but I don't see why this ought to be the default especially for Thread. – Oleksandr R. Dec 14 '15 at 23:51
  • @OleksandrR. @raxacoricofallapatorius and the point of my answer is to show there's always going to be some hiccups in any implementation. I think the best example is actually x = {1, 2, 3} == {a, b, c}; heldThread[x]. This will return {1, 2, 3} == {a, b, c}, which shows a Thread that holds it's inputs to prevent evaluation will ultimately produce unwanted results too. – Greg Hurst Dec 15 '15 at 00:32
  • @ChipHurst: That's a good point. And the right thing to focus on here. In your example Thread[x] does what I (stubbornly) want, but there's no way to put MapThread and x together to get that outcome. (It comes down to terrible documentation really; but it's not the first time). – orome Dec 15 '15 at 00:48
  • Yes, I agree. I thought of the same example as a problematic case. There are other ways to do it, of course--Blocking or substitution of the Head, for example, to avoid interference from Listable. Still, I don't really see why such non-standard special case semantics would make sense for Thread in particular when they are not applied to anything else. I think @raxacoricofallapatorius would be better off to write their own Thread with this behavior than objecting too strongly to the existing function following the standard and generally expected evaluation sequence. – Oleksandr R. Dec 15 '15 at 00:54
  • @OleksandrR.: Sorry, I wasn't trying to get anyone upset or make them feel threatened. See the actual question (and ChipHurst's answer, which addresses it). – orome Dec 15 '15 at 01:04
  • 4
    @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 01:11
  • 1
    @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 01:18
  • @ChipHurst: Can you copy this answer over to this question? I'm going to delete this one, it has attracted to much negative energy and misapprehension from the folks that don't like to see MMA criticized. – orome Dec 15 '15 at 12:32