2
$\begingroup$

In Mathematica, I am surprised that the following code does not do what I expect (what I expect is in comments after each evaluation). The problem seems to be due to the fact that when I define test with setDelayed (as I believe it should be?) but call it with k, k is then seen as a symbol and is always considered different than 0, irrespective of the replacement value I use thereafter. Is there a way to define this equality test in such a way that it will use the replacement value for k?

ClearAll[test];
test[k_, x_] := If[k === 0, x, x^2];
test[1, 4]  (* evaluates to 16 as expected *)
test[0, 4]  (* evaluates to 4 as expected *)
test[k, x] /. {k -> 1, x -> 4}   (* evaluates to 16 as expected *)
test[k, x] /. {k -> 0, x -> 4}   (* evaluates to 16 instead of 4 *)

*** EDIT 1 ***

Following the answer by @lericr, here is some more context: I have functions which I have taken the habit of implementing using the SetDelayed notation, as that seems to be the way these are implemented in Mathematica. These functions have a fraction which sometimes has a zero denominator, and sometimes a zero numerator as well, yielding Indeterminate results. However, I know how to manage those situations since when that happens other elements in parent functions treat those cases differently. Therefore, I have implemented these functions with an If clause to capture cases when I might have infinity or undetermined and manage apropriately.

That said, these functions take about 8 arguments, and 3 of those must be used in the conditional If clause, so perhaps the solutions proposed do not adapt well to that situation (I have difficulty understanding why the currently proposed solutions work).

Among the other things I tried as well was using /.{Indeterminate->0,ComplexInfinity->0} for example, but I have abandoned this way of doing things for a reason the specifics of which I forget but it did not seem to do the job in my case. I hope this clarifies a bit: I always find the position of someone not knowing an answer but having to provide a minimal working example for a question a bit difficult, as a good MWE often requires knowing the answer :)

*** EDIT 2 *** This edit corresponds to the latest updates/comments for the answer by @lericr. My full code is an accumulation of beginner-style Mathematica which is too convoluted to post and would likely be cofusing. But the reason why I use replacement can maybe be illustrated (though by no means justified, as I might be using it for the wrong reasons) by the following code.

Clear[subFuncA, subFuncB, mainFunc, otherFunc]
subFuncA[j_, k_, x_] := If[j === 0 && k === 0, x, x^2];
subFuncB[j_, k_, x_] := If[j === 0 || k === 0, 7 + x, 11 + x];
mainFunc[j_, k_, x_] := subFuncA[j, k, x] + subFuncB[j, k, x];
otherFunc[j_, k_, x_] := j + k - x;
Do[
 Do[
  params = {j -> jj, k -> kk, x -> 4};
  Print[otherFunc[j, k, x] /. params];
  Print[Unevaluated[mainFunc[j, k, x]] /. params];
  , {kk, 0, 1}]
 , {jj, 0, 1}]

In the above code, the two essential characteristics of what I am trying to achieve are visble: first, I have a set of parameters which I define once in order to reuse in multiple functions (here mainFunc and otherFunc); second, mainFunc calls two subfunctions which are the ones doing the conditional testing. So this is the reason why I used conditionals and replacement, though perhaps a With statement would have been preferable, I am not too sure...

I have tried all three methods available so far in the answer by @lericr: two are in the post, and one is in a comment by @att, and all three proposed solutions work! So I am happy with that answer. I have to say that the third solution, using Unevaluated, seems like perhaps (?) the most appropriate since from what I gather the other two are hacky? More importantly for me, I sort of understand why Unevaluated would work, whereas using _Integer or Equal still does not make much sense to me.

$\endgroup$
1
  • 1
    $\begingroup$ Regarding "provide a minimal working example for a question a bit difficult, as a good MWE often requires knowing the answer". Obviously, if you had something "working" in the sense of producing correct results, you wouldn't be asking the question. I mean "working" in the sense of "code that displays your actual problem". As it stands now, I don't understand the problem. The If in test seems to be handling the indeterminates already, so I don't understand what problem remains. I want a minimal example that demonstrates the problem. It "works" in the sense of reproducing the problem. $\endgroup$
    – lericr
    Commented Jun 16, 2023 at 23:41

3 Answers 3

4
$\begingroup$

Well, the simplest thing to do would be to use Equal instead of SameQ:

test[k_, x_] := If[k == 0, x, x^2]

It would also work to put a constraint on your argument pattern:

test[k_Integer, x_] := If[k === 0, x, x^2]

But I'm worried about recommending these solutions. They depend on evaluation halting at an intermediate stage and then doing the ReplaceAll, and that doesn't really seem like a rigorous way to do things. If you provided some context, maybe a better solution could be suggested.

Your expectation is wrong, because evaluation happens "bottom up" in the expression tree (very loosely speaking). And so both test[k, x] and {k -> 0, x -> 4} are evaluated before the whole ReplaceAll expression. So, the replacement k->0 doesn't get immediately applied to the test[k, x] expression. It instead gets applied to the result of evaluating test[k, x]. My second suggestion above causes the result of evaluating test[k, x] to be just test[k, x], and that's why it "works" after the replacements are done. My first suggestion above causes the result of evaluating test[k, x] to be If[k == 0, x, x^2], and so again it "works" after the replacements are done.

Update with further explanation

Let's say I have this definition:

funcA[a_, b_] := funcB[ToString[a], ToString[b]]

This is obviously weird, but it will illustrate the sequence of evaluation. Let's consider this:

funcA[x, y] /. {x -> m, y -> n}

You seem to be expecting that evaluation will follow this sequence:

(* incorrect evaluation sequence! *)
funcA[x, y] /. {x -> m, y -> n}
funcA[m, n]
funcB["m", "n"]

It actually evaluates like this:

(* correct (simplified) sequence *)
funcA[x, y] /. {x -> m, y -> n}
funcB["x", "y"] /. {x -> m, y -> n}
funcB["x", "y"] (* there were no matches, so nothing was replaced *)

Update with another suggestion

I don't understand why the ReplaceAll is necessary, but it occurs to me that maybe you're trying to sort of localize things. If that's the case, then maybe you want With instead:

With[{k = 0, x = 4}, test[k, x]]
(* test[0, 4] -> 4 *)

With[{k = 1, x = 4}, test[k, x]]
(* test[1, 4] -> 16 *)
$\endgroup$
8
  • $\begingroup$ Thanks, I have edited the question to add more context. That said, I have trouble understanding why your two solutions "work": in particular, how does saying k_Integer make it that the expression is evaluated to what (I think) I want? In my case, it is more complex and I actually have 3 parameters (say i_, j_ and k_) which must all be tested to assert if I am in one case of the conditional or another. $\endgroup$
    – tyogi
    Commented Jun 16, 2023 at 22:04
  • $\begingroup$ Well, as I said, I worry about recommending these "fixes" because they feel hackish, and we should really understand the semantics that you want first. I'll look at your edit... $\endgroup$
    – lericr
    Commented Jun 16, 2023 at 23:11
  • $\begingroup$ I've read your edit. I guess it would help to actually see your function, or a simplified version of it. In the meantime, tell me if this is correct: Your function test sometimes produces indeterminate results, and you're using ReplaceAll in the "parent" functions to handle that. You're doing this because handling Indeterminate (the output from test) in the parent functions didn't work. You're trying to "intercept" the arguments that might lead to indeterminate results before they get passed to test. $\endgroup$
    – lericr
    Commented Jun 16, 2023 at 23:16
  • 1
    $\begingroup$ Okay, re-read your edit. I'm not understanding why you're using ReplaceAll at all. I guess I glossed over it before, but after re-reading, I don't see any explanation for why you need ReplaceAll. It seems like you can just feed your arguments to test since test handles the indeterminate cases with the If. $\endgroup$
    – lericr
    Commented Jun 16, 2023 at 23:25
  • 1
    $\begingroup$ Another easy workaround (keeping ReplaceAll) would be to wrap the left side of /. with Unevaluated. $\endgroup$
    – att
    Commented Jun 17, 2023 at 6:07
4
$\begingroup$

See Operator Precedence in Tech Note Operator Input Forms

  1. test[k_, x_] is evaluating before the ReplaceAll.
  2. In test since k is not initialized then k===0 returns False
  3. test[k,x] returns x^2
  4. ReplaceAll replaces x with 4

The above sequence happens for both

  • test[k, x] /. {k -> 1, x -> 4}
  • test[k, x] /. {k -> 0, x -> 4}

Thus giving you the same result in each case.

Hope this helps.

$\endgroup$
1
  • 1
    $\begingroup$ Thanks for this explanation and the link. The accepted answer alludes to this operator precedence by giving a didactical example, and it also provided the key for an actual solution, but I appreciate learning about this as well. $\endgroup$
    – tyogi
    Commented Jun 17, 2023 at 18:17
3
$\begingroup$

Okay, first I'll acknowledge @att's comment. Using Unevaluated is probably the most aligned with what you were trying to achieve with your original code.

Maybe I'm still confused, but just based on what I'm observing in your updates, you are unnecessarily adding a layer of indirection. What you're wanting to do is get results of some function being applied to various sets of arguments. The most straightforward way to do that would be Table, since your argument lists are derived by simple iteration.

Table[mainFunc[j, k, 4], {k, 0, 1}, {j, 0, 1}]

Your x is just a constant 4, so I just "hard-coded" it. But you could add another level to the table:

Table[mainFunc[j, k, x], {k, 0, 1}, {j, 0, 1}, {x, {4}}]

Now, the output has some level structure to it:

{{15, 27}, {27, 31}} and {{{15}, {27}}, {{27}, {31}}} respectively. But you could just flatten everything:

Flatten[Table[mainFunc[j, k, x], {k, 0, 1}, {j, 0, 1}, {x, {4}}]]

If your argument lists weren't simple iterations, but arbitrary sets, then you could use MapApply (I'm using the same arguments, for clarity, but specifying them explicitly instead of iteratively):

args = {{0, 0, 4}, {1, 0, 4}, {0, 1, 4}, {1, 1, 4}};
mainFunc @@@ args

{15, 27, 27, 31}

Of course, the same idea works with otherFunc. If you want to apply them both simultaneously, you could add Through to the mix:

Through[{MapApply[mainFunc], MapApply[otherFunc]}[args]]

{{15, 27, 27, 31}, {-4, -3, -3, -2}}

At that point, you can manipulate that structure however you need.

$\endgroup$
1
  • $\begingroup$ This is nice as well, I have never really used Table, thanks! After some tinkering on my end, I have started using With, whch you suggested in your earler answer as well... I guess that when I first noticed ReplaceAll did what I wanted for a specific problem, I started using it in cases where it does not make as much sense as well... There is a trove of info in all three answers so far and I am grateful for what each one brings to the table (no pun intended). $\endgroup$
    – tyogi
    Commented Jun 17, 2023 at 19:34

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