29
$\begingroup$

I feel like I must be missing something simple and obvious here, but this has me scratching my head.

This works as expected:

list = {f[a], f[b]};
Cases[list, f[x_] :> x] -> Position[list, f[_]]

(* {a, b} -> {{1}, {2}} *)

However, this does not:

fun[list_] := Cases[list, f[x_] :> x] -> Position[list, f[_]];
fun[list]

(* {x, x} -> {{1}, {2}} *)

Is this a bug, or have I just not had enough coffee today?

$\endgroup$
8
  • 2
    $\begingroup$ And yet fun[list_] := Cases[list, f[x_] :> x] will yield {a, b}. And fun[list_] := Cases[list, f[x$_] :> x$] -> Position[list, f[_]] works. Someone else will have to explain the details here, because I don't quite get it. $\endgroup$
    – march
    Aug 19, 2015 at 16:27
  • 2
    $\begingroup$ Ok, the fun[list_] := Cases[list, f[x$_] :> x$] -> Position[list, f[_]] really has me baffled, but at least it's an easy workaround for my problem. I'm just curious, why did adding a $ come to mind as something to try? $\endgroup$
    – rhennigan
    Aug 19, 2015 at 16:36
  • $\begingroup$ You can even simplify the workaround to fun[list_] := Cases[list, f[x_] :> x$] -> Position[list, f[_]] and it seems to work for me, as MMA adds its own $ to the first x in the Cases pattern. $\endgroup$
    – N.J.Evans
    Aug 19, 2015 at 16:38
  • $\begingroup$ @N.J.Evans has it: I Traced the evaluation of your fun[list] and noticed that during that process, Cases[list, f[x$_] :> x] shows up, so the replacement wasn't going to work. $\endgroup$
    – march
    Aug 19, 2015 at 16:39
  • 1
    $\begingroup$ @N.J.Evans: Your solution is fine: I was commenting on why the OP's version didn't work. Trace-ing the OP's fun[list] showed Cases[list, f[x$_] :> x], which is why I tried Cases[list, f[x$_] :> x$]. I still don't understand why this behavior occurs. $\endgroup$
    – march
    Aug 19, 2015 at 16:45

1 Answer 1

23
$\begingroup$

What's happening

This is not simple by any means. You have encountered another instance of a general situation with lexical scope leaks / emulation / over-protection by symbol renaming. The case at hand is pretty similar to the one discussed here, so you can read the detailed explanation of this behavior in my answer there.

Roughly speaking, outer lexical scoping constructs (RuleDelayed in the linked dicsussion, and its analog for implicit global rule application here), try to protect the inner bindings from destructive changes, but mis-interpret their pieces and instead destroy yet inner bindings in the process. We have to fool that mechanism somehow, to avoid that.

Workarounds

The "StrictLexicalScoping" system option

Thanks to the hard work of Daniel Lichtblau, we now have a system option named "StrictLexicalScoping", which, when set to True, fixes many such cases, including the one at hand. You have to execute this:

SetSystemOptions["StrictLexicalScoping" -> True]

before you enter the definition of your function, and then it will work as intended.

Fooling the protection mechanism explicitly

In your case, here is one possible such work-around that is reasonably clean:

fun[list_] := 
   With[{rule = Rule}, 
      rule[Cases[list, f[x_] :> x], Position[list, f[_]]]
   ];

There are many more variations of it. What really matters is that SetDelayed and then the internal rule application engine (internal analog of RuleDelayed for global rules) don't see external Rule during the rule application.

This is surely not something that would first come to mind, though :)

Additional references

Here are a few additional links relevant to this discussion

$\endgroup$
14
  • $\begingroup$ fun[list_List] := Evaluate@(Cases[list, g[x_] :> x] -> Position[list, g[_]]); also works. $\endgroup$
    – N.J.Evans
    Aug 19, 2015 at 16:58
  • $\begingroup$ @N.J.Evans I don't think so. It has some other symbol g in it, and Evaluate just causes its r.h.s. to evaluate immediately, so I don't see how this could possibly work. It didn't work for me anyway. $\endgroup$ Aug 19, 2015 at 17:01
  • 1
    $\begingroup$ Maybe it's good to mention that SetSystemOptions["StrictLexicalScoping" -> True] solves this one too. $\endgroup$
    – Szabolcs
    Aug 19, 2015 at 17:15
  • 1
    $\begingroup$ Well, either I'm losing my mind or 10.0.1 on OSX is not acting like everybody else's. I copied @Leonid Shifrin 's code exactly and it's still working. $\endgroup$
    – N.J.Evans
    Aug 19, 2015 at 17:16
  • 1
    $\begingroup$ fun[list_] := Thread@Rule @@ {Cases[list, f[x_] :> x], Position[list, f[_]]} ? $\endgroup$ Aug 19, 2015 at 17:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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