13

I've reduced my actual problem to:

ParallelEvaluate[
  Module[{slot=1},
    Slot[slot]
  ]
]

gives error messages that I think shouldn't be there. It seems I can work around them with

ParallelEvaluate[
  Module[{slot=1},
    Slot@@{slot}
  ]
]

and

ParallelEvaluate[
  Module[{slot=1,s=Slot},
    s[slot]
  ]
]

but I still think this is a bug in the parallel framework. Am I missing something obvious? It seems the numeric check happens with the local Module placeholder values passed to the parallelism framework in a syntactic way, not in a semantic way. But that's wrong, I should be able to pass Slot[anything]

v8.0.4 and v9 alike, Windows and Linux.

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
Andreas Lauschke
  • 4,009
  • 22
  • 20
  • It happens on OS X as well (8 & 9) and even if you replace Module with With – Szabolcs Jan 13 '13 at 19:24
  • These things happen when the expression is put in Function: Function@Module[{s = 1}, Slot[s]]. Perhaps constructing a function is part of the parallel distribution/retrieval process. – Szabolcs Jan 13 '13 at 19:40

3 Answers3

13

This message is issued by Function itself. To see this, try

Function[Module[{slot = 1}, Slot[slot]]]

If Function has named formal parameters, the message goes away:

Function[x, Module[{slot = 1}, Slot[slot]]]

So to fix this first we need to find out where is the argument passed to ParallelEvaluate wrapped by Function. Fortunately the parallel tools are defined in a plain .m (not .mx) file so we can read the source code. The relevant part turns out to be in $InstallationDirectory/AddOns/Applications/Parallel/Parallel.m, in the definition of Send:

Send[kernels:{___kernel}, expr_] := Send[#, expr]& /@ kernels

The part causing the problem is Send[#, expr]&. Looks innocent enough, doesn't it? If we change this to Function[k, Send[k, expr]] then the message goes away.

Warning: I do not know if the form of Function with named formal parameters can cause any trouble (e.g. name collisions) in certain edge cases so be careful (or ask Leonid) ... Update: Here's an example of what may go wrong with named formal parameters.

Perhaps you can report this problem to WRI and let us know what they said.


Now that we know what the problem is caused by, we can manually construct simple problem cases. For example,

Mathematica graphics

Generally, passing any Slot not wrapped in a Function without formal arguments will cause problems (messages and unexpected results). I would consider this a bug, so I think it's a good idea to report it.

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
10

I would not classify this as a bug. The behaviour of a Slot expression that is not directly contained within a Function expression is not defined by the documentation, and is unreliable in practice. Consider the following two functions:

f[x_] := x + 100
g[x_] := x + # &[100]

They appear to be essentially equivalent, but it just so happens that the implementation of the second function involves a pure function. However, they return different results when passed a "dangling" slot reference (i.e. a slot reference that is not directly contained within a pure function):

f[#]
(* 100 + #1 *)

g[#]
(* 200 *)

The second result might surprise us, especially if the documentation for g said "adds 100 to its argument". Consider what happens if we call g with the Module expression from the question:

g[Module[{slot = 1}, Slot[slot]]]
(* 200 *)

200 is perhaps not exactly what we expected, but at least it is consistent with g[#].

On the other hand, let's try it with a new function h which is the same as g but holds its argument:

SetAttributes[h, HoldAll]
h[x_] := x + # &[100]

h[Module[{slot = 1}, Slot[slot]]]
(*
Function::slot: Slot[slot] (in Module[{slot=1},Slot[slot]]+#1&) should contain a non-negative integer. >>
(Module[{slot=1},Slot[slot]]+#1&)[100]
*)

We get the same warning message as in the question. This occurs because this expression ultimately resolves to:

Module[{slot = 1}, Slot[slot]] &

... which is manifestly incorrect since arguments to Slot in a function body must be non-negative integers -- not symbols.

ParallelEvaluate happens to be implemented somewhat like h. It holds the argument, and its implementation happens to use a pure function.

The moral of this story is that the behaviour of a dangling slot reference is essentially undefined. Its behaviour depends upon the exact implementation of the functions to which it is passed. If we are unaware of those exact implementation details, then we should avoid the use of dangling slot references since we can never be sure if that slot expression might not accidentally find its way into an internal Function expression.

To work around this difficulty, we can use a temporary symbol in place of Slot and then substitute it out after the evaluations are complete:

Module[{t}
, ParallelEvaluate[Module[{slot = 1}, t[slot]]] /. t -> Slot
]

This behaviour is just another example of the kind of "gotchas" that occur due to Mathematica's simulation of functional programming constructs through pattern matching techniques.

WReach
  • 68,832
  • 4
  • 164
  • 269
  • 4
    I agree in the sense that this is not a bug in Function or Slot, but this behavior should not be manifested in the Parallel` package. Actually I would argue that your g and h have a precedence bug; g should be defined as e.g. g[x_] := x + (# &)[100], whereupon it gives the same result as f. The documentation implicitly countenances (in the Applications section) building expressions containing Slot without a containing Function--their example of Function[Evaluate[f @@ Array[Slot, 5]]] is logically identical to e.g. With[{slots = f @@ Array[Slot, 5]}, Function[slots]]. – Oleksandr R. Jan 13 '13 at 20:49
  • 1
    @OleksandrR It is not always a simple matter to bulletproof a pure function body against injected slot references. What about the Send function exhibited in @Szabolcs answer? Named pure function arguments don't help either -- they have problems of their own. Even if such bulletproofing were simple, it is not common practice. I'm not arguing that this behaviour is desirable, but I am saying that we shouldn't expect well-defined behaviour from an ill-defined language construct. – WReach Jan 13 '13 at 21:21
  • 2
    The problem is that there are no clear hints in the docs that there is anything wrong with using naked slots. As Oleksandr points out there is actually an example of programmatically constructing a pure function, which could be perceived as an encouragement to do such things. (I believe @Andreas must have been doing something similar: constructing code programmatically. Though it's true that combining this with parallel evaluation is a little unusual.) Also consider that ParallelEvaluate usually takes code as its argument, unlike most other functions. I wouldn't say that this problem was ... – Szabolcs Jan 14 '13 at 01:49
  • ... easily avoidable. So I'd still call this a bug, a usability bug if you like. What's described in your blog I'd also call a bug (even if it's not really fixable given the design of the language ... ) It is very sad and unfortunate that Mathematica has these problem. Lately I am never confident enough to try to use techniques like code generation/manipulation in practice (and not just for fun in a very simple scenario). I can simply never feel confident enough that there is no input or edge case that could break my function. – Szabolcs Jan 14 '13 at 01:56
  • 2
    @Szabolcs. True the documentation has an example of generated slots, but the example conforms to my advice to only use it when the exact context is known (f has no definitions). I totally agree to report it -- I reported the issue in my blog. Sounds like a great possible issue for the docs. However, I anticipate the same response: avoid the situation. ... – WReach Jan 14 '13 at 02:28
  • @Szabolcs ... But to prohibit library code from referencing named arguments within a slotted pure function is far too draconian. It is much easier to prohibit client code from using dangling slots -- it is rare and easily avoided. I totally agree that it is unfortunate that these cases exist and that code generation can be very hard. My strategy to cope with that is to try to avoid non-trivial evaluations within generation (e.g using Hold, Block, etc). – WReach Jan 14 '13 at 02:29
  • 2
    @WReach +1, agree with all your points. By the way, I wrote a tiny framework to resolve such nasty variable collisions / leaks as in your example in the blog. It lives here. When I call runWithRenamings[ ClearAll[f, g]; f[x_] := g[Function[a, x]]; g[fn_] := Module[{h}, h[a_] := fn[a]; h[0]]; f[999]], I get the correct result. Of course this does not eliminate the problem, but it might be useful for debugging, and also one can run some code in a "safer mode", when wrapped in runWithRenamings. – Leonid Shifrin Jan 14 '13 at 10:14
  • @Leonid do you have any insight into why the renaming mechanism does not use unique symbols by default? If it would, we could safely use named arguments and this trouble with Slot would have a simple resolution. To me it seems absurd that conflicting symbols are renamed so predictably. – Oleksandr R. Jan 14 '13 at 13:27
  • @OleksandrR. I really don't know. One blind guess is that this mechanism was designed / implemented quite a long time ago, and wasn't touched much after that. Also, the mechanism of emulation of lexical scope based on renamings seems to be inherently flawed, so one can always break it (intentionally). So perhaps, this fact was realized, and so the developers did not go further in making it (a little) safer since this does not fully solve the problem. But I agree that, from the practical / user point of view, using fully unique symbols would be very helpful. – Leonid Shifrin Jan 14 '13 at 13:32
  • 1
    +1! I like how the 3 answers so far add important non-overlapping bits. I think it's a bug, since by default in Mathematica I think one should assume anything it's allowed as input. We don't need permission to take the square root of Lena. We need to be warned when some inputs can't be used. However, I agree that we should agree on not calling this a bug, or otherwise we'll end up with tons of similar bugs: as you said, too many functions don't work fine with hanging slots as input... as long as it isn't there in the possible issues of Slot as you suggested – Rojo Jan 15 '13 at 04:09
8

This is easily fixed by:

  Send[kernels:{___kernel}, expr_] := Table[Send[k, expr], {k, kernels}]

instead of

Send[kernels:{___kernel}, expr_] := Send[#, expr]& /@ kernels

While changing Parallel.m is one possibility, we can also
change the DownValues programmatically:

Parallel`Developer`Send;
Unprotect[Parallel`Developer`Send];
Parallel`Developer`Send[kernels : {___Parallel`Kernels`kernel}, expr_] := 
  Table[Parallel`Developer`Send[k, expr], {k, kernels}];
Protect[Parallel`Developer`Send];

This first idea is of giving Send Attribute Listable is wrong, thank's Oleksandr. Might be nice if there would be a ListableFirst attribute. But there is not.

Rojo
  • 42,601
  • 7
  • 96
  • 188
Rolf Mertig
  • 17,172
  • 1
  • 45
  • 76
  • The problem with this is that it needs to thread only over those lists appearing in the first argument. Personally, I'd rather do the threading explicitly than rely on the vagaries of Listable. – Oleksandr R. Jan 14 '13 at 13:22
  • Just like Power[{1, 2, 3}, 2]. So what is vague here? Listable behavior is not going to change I guess. – Rolf Mertig Jan 14 '13 at 14:21
  • 1
    You're probably right that it won't change. But what if someone wants to ParallelEvaluate a list that happens to be the same length as the list of kernels? – Oleksandr R. Jan 14 '13 at 14:29
  • It's a nice idea to rely on Listable for a solution. Hopefully this could be robust (unlike a Function with named arguments). – Szabolcs Jan 14 '13 at 15:32
  • The only answer offering a simple good solution in the first lines, deserves more votes, +1! – Rojo Jan 15 '13 at 03:54
  • @Rojo: Thanx. Do you have an idea how to do the DownValues replacement without using ToString? I tried, and it did not work ... (even with Verbatim all over the place). – Rolf Mertig Jan 15 '13 at 12:21
  • @RolfMertig why not simply load the new definition? – Rojo Jan 15 '13 at 13:21
  • @Rojo I don't like to directly change files in $InstallationDirectory. It is usually better (updates, portability) to patch/bug-fix Mathematica explicitly. So The high-level patch could go somewhere in the application code (or $UserBaseDirectory init.m or on a webserver or wherever). The point is that I don't quite understand why I cannot Replace the InputForm of DownValues directly. I had to go through this ToString kludge. But I don't have time to delve deeper into it (probably it is also not that important, just intriguing). – Rolf Mertig Jan 15 '13 at 13:56
  • @RolfMertig I mean, what about this. Seems to work for me. What issue are you running into? – Rojo Jan 15 '13 at 14:00