20

A number of functions provide for results to be wrapped in an arbitrary head. This is very important in cases where the results should not be evaluated. Take Level as an example:

expr = Hold[{2/2, g[8/4], 1/0}];

Level[expr, {-2}, Hold]
Hold[1/2, 1/4, 1/0]

Cases however provides no such parameter, causing undesired evaluation:

Cases[expr, _Times, 3]

Power::infy: Infinite expression 1/0 encountered. >>

{1, 2, ComplexInfinity}  (* failure *)

How can this behavior be attained?

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371

1 Answers1

16

The best I have is manual RHS holding and Join, after which an arbitrary head could be Applied:

Join @@ Cases[expr, x : _Times :> Hold[x], 3]
Hold[2/2, 8/4, 1/0]

This could be done automatically as follows:

makeHeld[(L_ -> R_) | (L_ :> R_)] := L :> HoldComplete[R];
makeHeld[pat_] := x : pat :> HoldComplete[x];

heldCases[expr_, rule_, args___] :=
 Join @@ Cases[Unevaluated @ expr, makeHeld @ rule, args]

I am now reasonably happy with this but I still wonder if there is a more elegant or efficient method.


For fun here is a one-line definition of makeHeld using "vanishing patterns" and Part properties:

makeHeld[(L_ -> R_) | (L_ :> R_) | L_] := x : L -> HoldComplete[R, x][[{1}]];

It's not quite as efficient and clarity suffers so I won't replace the version above with it.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • +1. I don't know why you dislike the code, it looks pretty good to me. Just one thing - I'd use Cases[Unevaluated[expr], Replace[...],args]. – Leonid Shifrin Sep 06 '13 at 06:59
  • @Leonid I'm glad it looks good. I guess it just seems like a lot of code for what I think is a rather obvious omission from Cases syntax. Unevaluated[expr] does make sense regarding the name holdCases but it was my intent instead to implement this as if it was a parameter of Cases, just as it is for Level, and Cases does not have HoldFirst. – Mr.Wizard Sep 06 '13 at 07:02
  • @Leonid I changed the code somewhat; I think it's easier to read and a bit more efficient. Do you agree/confirm? – Mr.Wizard Sep 06 '13 at 07:22
  • +1. Can one also include the arbitrary head to make it more flexible like: heldCases[expr_, rule_, args___, head_] := Join @@ Cases[expr, makeHeld @ rule, args] /. HoldComplete -> head – RunnyKine Sep 06 '13 at 07:59
  • @RunnyKine Yes, you could, but you should write it head @@ Join @@ Cases[. . .]. I almost did this but I realized it was no shorter to include it as an argument. (Well, I guess it's one character shorter, but even I don't make too much of that.) – Mr.Wizard Sep 06 '13 at 08:04
  • That rewrite is really awesome, such an elegant and beautiful solution. – jVincent Sep 06 '13 at 08:32
  • @jVincent Thanks! It's a bit cleaner now than when I first posted. – Mr.Wizard Sep 06 '13 at 08:38
  • 1
    @Mr.Wizard The reason I suggested Unevaluated is that otherwise the expression will evaluate, once passed to heldCases, even when you wrap it in one level of Unevaluated, because there is a rewrite step heldCases to Cases. In other words, when I need to destructure an expression while keeping it unevaluated, I use Cases[Unevaluated[expr],...], while doing the same with heldCases as heldCases[Unevaluated[expr],... won't work with your current code. Re: rewrite - quite nice, I agree. – Leonid Shifrin Sep 06 '13 at 10:49
  • @LeonidShifrin Ah, I see. (How many times must I have said that after reading one of your explanations?) – Mr.Wizard Sep 06 '13 at 10:50
  • Mr.W would you kindly explain how the final pattern works? It seems to have confused the hell out of me. Particularly, what is the purpose of x? Thanks. – rcollyer Sep 06 '13 at 12:56
  • 1
    @rcollyer x := godObject["rcollyer", "confusticate"] -- meet me in chat ;-) – Mr.Wizard Sep 06 '13 at 12:59