Injecting a sequence of expressions into a held expression

67

38

Consider the following toy example:

Hold[{1, 2, x}] /. x -> Sequence[3, 4]

It will give

Hold[{1, 2, Sequence[3, 4]}]

because Sequence[] (like Unevaluated) is expanded only in the first level of heads with attribute HoldAll.

How can I obtain Hold[{1,2,3,4}]? What is the simplest way to do this?

Notes:

  • Use case: I am trying to generate a piece of code that will be passed to Compile. I need to inject a variable number of iterators (which I have as a list) into a Do expression:

     Hold[Do[code, iterators]] /. iterators -> Sequence[{i,5}, {j,5}]
    
  • I would prefers solutions that don't match on the expression enclosing x. I would not like to repeat this expression (a Do in this case) in my code.

  • It's perhaps worth pointing out that

     Hold[{1, 2, f[3, 4]}] //. f[x___] :> x
    

    returns

     Hold[{1, 2, Sequence[3, 4]}]
    

    so I can't easily implement a manual sequence-flattening step.


Answers

Based on Leonid's code we can write a flattenSequence[] function that will flatten out all Sequence expressions at any level:

flattenSequence[expr_] := 
 expr //. f_[left___, Verbatim[Sequence][middle___], right___] :> 
   f[left, middle, right]

flattenSequence[Hold[{1, Sequence[2, 3]}]]

(* ==> Hold[{1, 2, 3}] *)

Based on Mr.Wizard's code we can write a general function for injecting subexpressions into other expressions while supporting Sequence:

ClearAll[inject1, inject]

SetAttributes[inject1, HoldFirst]
Quiet[
 inject1[expr_, (Rule|RuleDelayed)[var_Symbol, values : Verbatim[Sequence][__]]] :=
  Replace[Unevaluated[values], Sequence[var__] :> expr];
 inject1[expr_, (Rule|RuleDelayed)[var_Symbol, value_]] :=
  Replace[Unevaluated[value], var_ :> expr],
 
 {RuleDelayed::rhs}
]

SetAttributes[inject, HoldAll]
inject[rules_, expr_] :=
 Internal`InheritedBlock[
  {Rule, RuleDelayed},
  SetAttributes[{Rule, RuleDelayed}, HoldFirst];
  ReleaseHold@Fold[inject1, HoldComplete[expr], rules]
 ]

Usage:

inject[{a -> Sequence[b, 3], b :> 1 + 1}, Hold[{a, b}]]

(* ==> Hold[{1 + 1, 3, 1 + 1}] *)

The replacements are done one after the other, so the second one can use the result of the first. Rule and RuleDelayed are both handled correctly.

Szabolcs

Posted 2012-02-17T18:35:58.163

Reputation: 213 047

1I just read this. You have plenty of answers already. I just wanted to point out that those answers that rely on building the expression on the rhs of a rule, such as the firsts of MrWizard, respect scoping so you can get things renamed if you are injecting inside a scoping construct. This means that your inject might too. Try inject[{aa -> {3, xx}}, Hold@With[{xx = 8}, Hold[{1, 2, aa}]]] – Rojo – 2012-07-17T13:49:42.730

If you don't insist on Sequence it might be easier – acl – 2012-02-17T18:43:27.233

If you can solve this in a direct and robust way I think it will call into question the need for Sequence at all. – Mr.Wizard – 2012-02-17T19:22:06.463

Answers

57

{3, 4} /. {x__} :> Hold[{1, 2, x}]
Hold[{1, 2, 3, 4}]

Leonid Shifrin used this here long before I wrote this answer.


In light of Leonid's comment to halirutan it is worth pointing out that you can inject expressions from an arbitrary head including Hold. You can also use -> rather than :> like this:

expr = Hold[{1, 2, x}];

Hold[6/2, 2 + 2] /. _[x__] -> expr 
Hold[{1, 2, 6/2, 2 + 2}]

Mr.Wizard

Posted 2012-02-17T18:35:58.163

Reputation: 259 163

@Kuba This is not an injection but rather a replacement so it is not quite the same as this question. I think Leonid's method is probably the most applicable, e.g. If[Hold[{a, b}], If[Hold[{c, d}], e]] //. h_[x___, _[{y___}], z___] :> h[x, y, z]. The //. is not efficient on long expressions but handling that will make this longer, making other methods comparatively more attractive. – Mr.Wizard – 2015-03-06T05:57:31.310

@Mr.Wizard Good point. Thanks anyway :) Let me delete those comments then. – Kuba – 2015-03-06T06:00:18.320

very cool actually – acl – 2012-02-17T20:30:34.807

@acl I lied though: it still uses Sequence, just in the form of BlankSequence. halirutan uses SlotSequence. So I think it is not possible without Sequence of one brand or another. – Mr.Wizard – 2012-02-17T20:32:30.797

Yes, but that's beyond the point. One could also do all this by converting to strings and replacing, and to me the other answers seem almost as clumsy as that. You do it with a much lighter touch. – acl – 2012-02-17T20:36:16.263

23

How about this:

ClearAll[inject];
SetAttributes[inject, HoldRest];
inject[Hold[{args__}], new__] := Hold[{args, new}]

This will also accept Sequence[3,4] as a second argument. Sequences are spliced, while arguments themselves not evaluated.

EDIT

You can also use a composite rule, with some head s instead of Sequence (you can localize s if needed):

Hold[{1, 2, x}] /. x -> s[3, 4] /. 
  f_[left___, s[middle___], right___] :> f[left, middle, right]

Leonid Shifrin

Posted 2012-02-17T18:35:58.163

Reputation: 108 027

This is exactly what I meant when I said "I would prefers solutions that don't match on the expression enclosing x. I would not like to repeat this expression (a Do in this case) in my code." This will be ugly with the Do. – Szabolcs – 2012-02-17T19:58:20.277

@Szabolcs Does my update address you needs? You could also wrap s around your Sequence, if you naturally get your results as a Sequence – Leonid Shifrin – 2012-02-17T20:07:29.313

Yes, it does. This can be expanded into a function that will flatten out all Sequence elements in an arbitrary held expression (i.e. it can made into a self-contained and general tool that integrates well with other solutions) – Szabolcs – 2012-02-17T21:08:02.530

12

One way is to use Function and the possibility of SlotSequence. I define an additional function f to be sure nothing gets evaluated:

f[x_] := Print["Evaluated"];
Function[Hold[Do[f[1], ##]]][{i, 5}, {j, 5}]

(*
  Hold[Do[f[1], {i, 5}, {j, 5}]]
*)

halirutan

Posted 2012-02-17T18:35:58.163

Reputation: 109 574

8This will have a problem when i or j have values before evaluation. You can use Function[Null,Hold[Do[f[1],##]],HoldAll] to avoid it. – Leonid Shifrin – 2012-02-17T19:59:08.917

10

Not sure how robust this is, but you could do something like

flattenSequence[expr_, {x_, p__}] := Module[{f, t},
  f[t__] = expr /. x -> t;
  f[p]]

Then for the example above

flattenSequence[Hold[{1, 2, x}], {x, 3, 4}]
Hold[{1, 2, 3, 4}]

Heike

Posted 2012-02-17T18:35:58.163

Reputation: 34 748

6

Ok. Here comes the Inactivate, Mathematica 10's powerful feature.

Inactivate could not solve injecting expression into Hold.

But you mentioned you actually want to use this to inject Do iterator in Compile. This can be done directly by Inactivate without Hold stuff. Use this

Activate[Inactivate[Compile[{}, Do[code, iterators]]] /. 
  iterators -> Sequence[{i, 5}, {j, 5}]]

I personally think that with Inactivate and Activate, we can think many things differently now, especially meta programming.

matheorem

Posted 2012-02-17T18:35:58.163

Reputation: 14 483

@Mr.Wizard Oh, yeah, But my second part solve Compile problem, what do you think? If this is also unnecessary, I will delete my answer : ) – matheorem – 2016-01-14T15:42:01.990

No, I can see value in that part. Here's a +1. :-) – Mr.Wizard – 2016-01-14T16:10:28.487

5

Neat solutions provided, but there are probably more straight forward ways to solve the original problem. One of these might help.

In:= s=0; Apply[Do[s+=i^i,{i,##}]&,Hold[1,12,3]]; s  

Out[75]= 10000823800  


In:= r=Hold[s=0;{1,12,3};s];  
Part[r,1,2,0]=(Do[s+=i^i,{i,##}]&); ReleaseHold[r]  

Out= 10000823800  

Ted Ersek

Posted 2012-02-17T18:35:58.163

Reputation: 5 036

5

In a previous answer Mr.Wizard suggested

Hold[1 + 1, 2 + 2, #] &@Unevaluated[3 + 3, 4 + 4]

However injecting deeper inside a Hold with this technique does not work:

Hold[{1 + 1, 2 + 2, #}] &@Unevaluated[3 + 3, 4 + 4]

returns Hold[{1 + 1, 2 + 2, Sequence[3 + 3, 4 + 4]}].

I would like to point out that a little variation does indeed work:

Hold[{1 + 1, 2 + 2, ##}] &[3 + 3, 4 + 4]

And if one does indeed want the arguments not to be evaluated, then it is possible to use

Function[Null, Hold[{1 + 1, 2 + 2, SlotSequence[1]}], {HoldAll}][3 + 3, 4 + 4]

Federico

Posted 2012-02-17T18:35:58.163

Reputation: 2 483

5

I remarked before that I didn't think this was possible without Sequence, SlotSequence, BlankSequence, etc. (Without using string processing or the like that is.) It seems I was wrong, unless there is an implicit Sequence in here:

Hold[1 + 1, 2 + 2, #] & @ Unevaluated[3 + 3, 4 + 4]
Hold[1 + 1, 2 + 2, 3 + 3, 4 + 4]

Mr.Wizard

Posted 2012-02-17T18:35:58.163

Reputation: 259 163

1Indeed, there is an implicit Sequence: try {#}&@Unevaluated[1, 2] // Trace. – Federico – 2015-08-04T17:22:01.223

@Federico I have this vague memory that I saw that in the trace when I posted this but that I did not consider it a "real" Sequence since it appears later? I don't know, and I haven't given this a lot of thought since then. Anyway, thanks for noting that for future readers. – Mr.Wizard – 2015-08-05T05:04:59.697

This works fine because you are injecting at first level inside Hold. See my following answer for clarifications. – Federico – 2013-03-22T23:05:34.323

5

Perhaps something like this?

ClearAll[replaceFlatteningSequences];
replaceFlatteningSequences[lhs_, pat_ :> rhs_] /; MatchQ[lhs, pat] := 
 lhs /. lhs -> rhs
replaceFlatteningSequences[lhs_, pat_ :> Sequence[repSeq__]] := 
 Module[{tag},
  lhs /. {Slot -> tag, SlotSequence -> tag["Sequence"], 
        Function -> tag["Function"]} /. pat :> ## /. 
     all_ :> (all &[repSeq]) /. {tag["Function"] -> Function, 
     tag["Sequence"] -> SlotSequence} /. tag -> Slot
  ]

To be used

replaceFlatteningSequences[Hold@With[{x = 8}, ## aa &], 
 aa :> Sequence[x, 4]]

Hold[With[{x = 8}, ##1 x 4 &]]

Rojo

Posted 2012-02-17T18:35:58.163

Reputation: 40 993

4

I though it is nice untill I had to add reparse function :)

SetAttributes[mySequence, HoldAllComplete];
mySequence[args__] := RawBoxes[  MakeBoxes[{args}][[1, 2]]  ];
reparse = ToExpression @* FrontEndExecute @* FrontEnd`ReparseBoxStructurePacket @* ToBoxes


y = 7;
Hold[{1, x, 2, x, 3}] /. x :> RuleCondition @ mySequence[y, 1+2] //reparse
 Hold[{1, y, 1 + 2, 2, y, 1 + 2, 3}]

Kuba

Posted 2012-02-17T18:35:58.163

Reputation: 129 207

But the result is not that expression, it just looks like that expression. It cannot be re-used programmatically, and the produced cell cannot even be edited manually within the front end, then evaluated, without producing errors. – Szabolcs – 2015-08-04T10:53:35.180

2@Szabolcs good point, fixed. but it is not so clean now :( – Kuba – 2015-08-04T11:06:57.160