17
$\begingroup$

Let's say I have a function

formula[x_List] := (x[[1]] - x[[2]]) + (x[[3]] - x[[4]]);

and I want to pass it a variable and get back the unevaluated formula with these inputs in place, so for example with input {1,2,3,4} I would get back

Out[]= (1-2)+(3-4)

I do not want it to return strings; if I copy the output and paste it somewhere, I want it to be executable. I also don't want it to do any math on the function; if x[[1]]==x[[2]], it should not collapse x[[1]]-x[[2]] to 0. All I want is pattern substitution.

I figure the answer involves Hold[], but I have not been able to get the results I want.

$\endgroup$
  • 1
    $\begingroup$ Both the Mr. Wizard and Mike Honeychurch have solutions that come very close to what I want. In fact, at first I thought they both had it. I notice however that there is a case in which both methods fail. Both simplify out certain simple identities. For example, if formula[{a_, b_, c_, d_}] := HoldForm[abc*d], then both versions drop some 1s given as input (oddly, not all). formula[{1,1,1,1}] works as desired but formula[1,2,1,31] does not. $\endgroup$ – Michael Stern Jul 5 '12 at 0:37
  • $\begingroup$ Michael, I have addressed this problem in my answer. $\endgroup$ – Mr.Wizard Jul 5 '12 at 9:42
18
$\begingroup$
formula[{a_, b_, c_, d_}] := Defer[(a - b) + (c - d)]

formula[{1, 2, 3, 4}]
(1 - 2) + (3 - 4)

Addressing your comment below the question, Defer (also HoldForm) does work, but the output formatting engine is changing the appearance of your result. You can see this by using InputForm:

Defer[1 * 2 * 1 * 31] // InputForm
Defer[1*2*1*31]

The hold functions, even HoldComplete, do not prevent the formatting engine form going to work:

HoldComplete[1 * 2 * 1 * 31]

Mathematica graphics

You need to attack the problem at its source, by Blocking Times during Box creation:

SetAttributes[defer, HoldAll]

MakeBoxes[defer[args__], fmt_] :=
  Block[{Times},
    MakeBoxes[Defer[args], fmt]
  ]

You can now use defer as you would Defer but Times will not be formatted:

formula[{a_, b_, c_, d_}] := defer[a*b*c*d]

formula[{1, 2, 1, 31}]
1 * 2 * 1 * 31
$\endgroup$
  • $\begingroup$ +1 for simplicity of interpretation and solution. $\endgroup$ – Leonid Shifrin Jul 4 '12 at 16:38
  • $\begingroup$ Very very close, but per my note above, there are cases in which this collapses 1*x to x. Any way to prevent this? $\endgroup$ – Michael Stern Jul 5 '12 at 1:15
10
$\begingroup$

You are looking for partial evaluation. Perhaps, there is a shorter way, but here is one (rather subtle):

Clear[evalAtPattern]
evalAtPattern[expr_, p_] :=
   expr /. pt : p :> With[{pp = pt}, pp /; True];

Clear[formula];
formula[x_List] :=
  evalAtPattern[
    Defer[(x[[1]] - x[[2]]) + (x[[3]] - x[[4]])],
    HoldPattern[x[[_]]]];

Now,

formula[{1, 2, 3, 4}]

(* (1 - 2) + (3 - 4) *)

And the output code can be executed. The solution combined injecting evaluated pieces inside held expressions via Trott - Strzebonski technique, and the use of Defer.

$\endgroup$
  • $\begingroup$ It looks like Leonid had a different interpretation. $\endgroup$ – Mr.Wizard Jul 4 '12 at 16:37
  • $\begingroup$ @R.M. His answer is simpler. It really depends on how general one wants to be. I tried to have code which can keep the original definition minimally modified. $\endgroup$ – Leonid Shifrin Jul 4 '12 at 16:37
  • 1
    $\begingroup$ +1 for complexity of interpretation and solution. ps why not expr /. pt : p :> RuleCondition[pt] ? $\endgroup$ – Mr.Wizard Jul 4 '12 at 16:40
  • $\begingroup$ @Mr.Wizard Thanks :) No reason, really, just old habits. $\endgroup$ – Leonid Shifrin Jul 4 '12 at 16:41
4
$\begingroup$
formula[{a_, b_, c_, d_}] := HoldForm[(a - b) + (c - d)]

formula[{1, 2, 3, 4}]
(1 - 2) + (3 - 4)

"...if I copy the output and paste it somewhere, I want it to be executable..."

You would need to release the hold.

ReleaseHold[(1 - 2) + (3 - 4)]
$\endgroup$
  • $\begingroup$ This works for me, as does the Defer[] solution above; I've credited Mr.Wizard for the answer only because he got in first. $\endgroup$ – Michael Stern Jul 5 '12 at 0:06
  • $\begingroup$ I accepted, and then withdrew when I found a problem (discussed in an edit to my question, above). Mr. Wizard has since addressed this problem, so scores the point. $\endgroup$ – Michael Stern Jul 5 '12 at 11:07
1
$\begingroup$

How about

formula[x_List] := 
    With[{a = x[[1]], b = x[[2]], c = x[[3]], d = x[[4]]}, Defer[(a - b)/(c - d)]]

?

$\endgroup$
  • $\begingroup$ @rm -rf Thanks! $\endgroup$ – Life Jun 23 '13 at 17:57
  • $\begingroup$ This does not match the specification in the question as you cannot copy & paste the output as evaluable input; that is best done with Defer, introduced in version 6. $\endgroup$ – Mr.Wizard Jun 23 '13 at 20:40
  • $\begingroup$ Thanks!@Mr.Wizard The function Defer is really amazing. It is like HoldForm with ReleaseHold command if copy & paste it. $\endgroup$ – Life Jun 24 '13 at 4:09
  • $\begingroup$ I have modified it by replacing HoldForm into Defer. $\endgroup$ – Life Jun 24 '13 at 11:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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