69
$\begingroup$

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.

$\endgroup$
3
  • $\begingroup$ If you don't insist on Sequence it might be easier $\endgroup$
    – acl
    Feb 17, 2012 at 18:43
  • $\begingroup$ If you can solve this in a direct and robust way I think it will call into question the need for Sequence at all. $\endgroup$
    – Mr.Wizard
    Feb 17, 2012 at 19:22
  • 1
    $\begingroup$ I 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}]]] $\endgroup$
    – Rojo
    Jul 17, 2012 at 13:49

10 Answers 10

59
$\begingroup$
{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}]
$\endgroup$
5
  • $\begingroup$ very cool actually $\endgroup$
    – acl
    Feb 17, 2012 at 20:30
  • $\begingroup$ @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. $\endgroup$
    – Mr.Wizard
    Feb 17, 2012 at 20:32
  • $\begingroup$ 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. $\endgroup$
    – acl
    Feb 17, 2012 at 20:36
  • $\begingroup$ @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. $\endgroup$
    – Mr.Wizard
    Mar 6, 2015 at 5:57
  • $\begingroup$ @Mr.Wizard Good point. Thanks anyway :) Let me delete those comments then. $\endgroup$
    – Kuba
    Mar 6, 2015 at 6:00
23
$\begingroup$

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]
$\endgroup$
3
  • $\begingroup$ 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. $\endgroup$
    – Szabolcs
    Feb 17, 2012 at 19:58
  • $\begingroup$ @Szabolcs Does my update address you needs? You could also wrap s around your Sequence, if you naturally get your results as a Sequence $\endgroup$ Feb 17, 2012 at 20:07
  • $\begingroup$ 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) $\endgroup$
    – Szabolcs
    Feb 17, 2012 at 21:08
12
$\begingroup$

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}]]
*)
$\endgroup$
1
  • 8
    $\begingroup$ This 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. $\endgroup$ Feb 17, 2012 at 19:59
10
$\begingroup$

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}]
$\endgroup$
6
$\begingroup$

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.

$\endgroup$
2
  • $\begingroup$ @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 : ) $\endgroup$
    – matheorem
    Jan 14, 2016 at 15:42
  • $\begingroup$ No, I can see value in that part. Here's a +1. :-) $\endgroup$
    – Mr.Wizard
    Jan 14, 2016 at 16:10
5
$\begingroup$

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  
$\endgroup$
5
$\begingroup$

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]
$\endgroup$
3
  • $\begingroup$ This works fine because you are injecting at first level inside Hold. See my following answer for clarifications. $\endgroup$
    – Federico
    Mar 22, 2013 at 23:05
  • 1
    $\begingroup$ Indeed, there is an implicit Sequence: try {#}&@Unevaluated[1, 2] // Trace. $\endgroup$
    – Federico
    Aug 4, 2015 at 17:22
  • $\begingroup$ @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. $\endgroup$
    – Mr.Wizard
    Aug 5, 2015 at 5:04
5
$\begingroup$

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 &]]

$\endgroup$
5
$\begingroup$

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]
$\endgroup$
5
$\begingroup$

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}]
$\endgroup$
2
  • $\begingroup$ 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. $\endgroup$
    – Szabolcs
    Aug 4, 2015 at 10:53
  • 2
    $\begingroup$ @Szabolcs good point, fixed. but it is not so clean now :( $\endgroup$
    – Kuba
    Aug 4, 2015 at 11:06

Your Answer

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

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