0
$\begingroup$

In the spirit of a previous question, this is a self-assigned exercise in order to understand how we can control expression evaluation with Mathematica.

So, given the following function definition:

f[x_] := Sin[Pi x] + Cos[Pi x]

By default, Mathematica will evaluate expressions as far as possible. So:

f[3]
-1

However, and without having tho change the definition of f, I would like to obtain only a partial evalutation of the expression where the x argument is substituted, but where the Cos and Sin function aren't evaluated. In clear, I want that:

Cos[3 π] + Sin[3 π]

After some trials and errors, I came to a Block-based solution given as an answer below. But do you have other suggestions to achieve that result?

This question is really to learn how to control evaluation with Mathematica. I would prefer solutions that demonstrate how to apply the functions given in the docs on that particular case, rather than a more powerful and generic solution like Mr. Wizard's step function.

$\endgroup$
  • 4
    $\begingroup$ Does this answer your question? How do I evaluate only one step of an expression? - In particular, step[f[3]] returns Sin[π 3]+Cos[π 3] (using step from @MrWizard's answer) $\endgroup$ – Lukas Lang Dec 10 '19 at 18:02
  • $\begingroup$ Thanks @Lukas, I indeed missed that answer in my list. From all the similar questions I saw, you make use of relatively complex functions to achieve the result I want. Is there something inherently wrong with the one-liner I suggested below? $\endgroup$ – Sylvain Leroux Dec 10 '19 at 18:13
  • 1
    $\begingroup$ Nothing is inherently with with your approach. The big advantage of the TraceScan solution is that it will work for all functions, without you having to list everything that shouldn't be evaluated. But i guess it depends on what exactly you want to achieve (also, I wouldn't necessarily call it a "complex" function - it's essentially only two lines) $\endgroup$ – Lukas Lang Dec 10 '19 at 18:17
  • $\begingroup$ You're right @Lukas, Mr Wizard's step function isn't that long. But for a newcomer, it is not obvious to understand. I tried to apply TraceScan in my particular case. I tried variations along the lines of TraceScan[HoldForm, f[3]]. But it returns the fully evaluated function eaach time. Whereas TraceScan[Print, f[3]] display the individual evaluation steps. Obviously, I missed something important here :/ $\endgroup$ – Sylvain Leroux Dec 10 '19 at 18:30
  • 1
    $\begingroup$ Do you really want to control the order of evaluation, or do you just want to view the order of evaluation? $\endgroup$ – Carl Woll Dec 10 '19 at 21:56
1
$\begingroup$

If you have a whitelist of symbols, here's a flexible way to do it with Inactivate and some code-injection trickery:

$inactiveSyms = 
  Hold[{Sin, Cos, Tan}];(* in case you want to localize a symbol with OwnValues *)
withInactiveSymbols~SetAttributes~HoldAll
withInactiveSymbols[expr_, symList : Hold[{___Symbol}] | Automatic : Automatic] :=
 Replace[
  Thread[
   Replace[
    Thread[Replace[symList, Automatic :> $inactiveSyms]], 
    Hold[s_] :> Hold[s = Inactive[s]],
    1
    ],
   Hold
   ], 
  Hold[assigns_] :> Block[assigns, expr]
  ]

And it replaces the syms with their Inactive forms:

withInactiveSymbols[f[3]]

Inactive[Cos][3 \[Pi]] + Inactive[Sin][3 \[Pi]]

Note that you can easily change what to Inactive-ify on the fly:

withInactiveSymbols[f[3], Hold[{Sin, Tan}]]

-1 + Inactive[Sin][3 \[Pi]]

Here's a different layer of trickery:

partialEval[f_, var_ -> val_] :=
 With[
  {
   d = DownValues[f], 
   tag = Unique[partialEvalTag],
   v =
    ToExpression[
     Context[var] <> Block[{Internal`$ContextMarks = False}, ToString[var]]
     ]
   },
  SetAttributes[partialEvalTag, Temporary];
  Pick[
    Thread@Extract[d, {All, 2}, HoldForm],
    Map[
     Not@FreeQ[#, partialEvalTag] &,
      Keys[d] /. v -> partialEvalTag
     ]
    ] /. v -> val
  ]

This requires the variable name to be the same as expected, and so you can have f[x_] partially evaluate but f[y_, z_] not. Not sure if it's useful, but it is fun at a minimum.

f[x_] := Sin[Pi x] + Cos[Pi x]
f[y_, z_] := y + 1

partialEval[f, x -> 3]

{HoldForm[Sin[\[Pi] 3]+Cos[\[Pi] 3]]}
$\endgroup$
0
$\begingroup$

Here is an alternate solution based on the Trace function:

Trace[f[3]][[2]]
Sin[\[Pi] 3]+Cos[\[Pi] 3]

Or better maybe:

 Trace[f[3], _[_] +_[_]]
{Sin[\[Pi] 3]+Cos[\[Pi] 3]}
$\endgroup$
0
$\begingroup$

Here is my initial solution:

Block[{Sin = HoldForm[Sin], Cos = HoldForm[Cos]}, f[3]]
Cos[3 \[Pi]]+Sin[3 \[Pi]]

The idea is to use the Block function to evaluate f[3] in an environment where the Sin and Cos symbols are temporarily rebound to their "unevaluable" equivalent1


1I'm not sure this is the correct wording. Please correct me if necessary!

$\endgroup$

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