18
$\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$
2
  • 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$ Commented Jul 5, 2012 at 0:37
  • $\begingroup$ Michael, I have addressed this problem in my answer. $\endgroup$
    – Mr.Wizard
    Commented Jul 5, 2012 at 9:42

4 Answers 4

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$
2
  • $\begingroup$ +1 for simplicity of interpretation and solution. $\endgroup$ Commented Jul 4, 2012 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$ Commented Jul 5, 2012 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$
4
  • $\begingroup$ It looks like Leonid had a different interpretation. $\endgroup$
    – Mr.Wizard
    Commented Jul 4, 2012 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$ Commented Jul 4, 2012 at 16:37
  • 1
    $\begingroup$ +1 for complexity of interpretation and solution. ps why not expr /. pt : p :> RuleCondition[pt] ? $\endgroup$
    – Mr.Wizard
    Commented Jul 4, 2012 at 16:40
  • $\begingroup$ @Mr.Wizard Thanks :) No reason, really, just old habits. $\endgroup$ Commented Jul 4, 2012 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$
2
  • $\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$ Commented Jul 5, 2012 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$ Commented Jul 5, 2012 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$
4
  • $\begingroup$ @rm -rf Thanks! $\endgroup$ Commented Jun 23, 2013 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
    Commented Jun 23, 2013 at 20:40
  • $\begingroup$ [email protected] The function Defer is really amazing. It is like HoldForm with ReleaseHold command if copy & paste it. $\endgroup$ Commented Jun 24, 2013 at 4:09
  • $\begingroup$ I have modified it by replacing HoldForm into Defer. $\endgroup$ Commented Jun 24, 2013 at 11:30

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.