1

I've used the following code to simulate defining operations between functions:

ThroughFunctions[expr_] := expr//.{
x_?NumericQ[___] -> x,
(f_/;MemberQ[Attributes[f], NumericFunction])[x___][y___] :> Through[f[x][y]]
}

$Pre = ThroughFunctions;

Since it's assigned to $Pre the substitution occurs without having to wrap the expressions in the function ThroughFunctions:

In[10]:=(Cos^2/(3+Sin))[x]

Out[10]=Cos[x]^2/(3+Sin[x])

The problem with this is that it doesn't work when I try to use it in an expression that delays the evaluation:

Plot[(Sin + Cos^2)[x], {x, 0, Pi}]

returns an empty graphic.

Is there a way to implement this in a more robust way so it works better?

jjagmath
  • 411
  • 2
  • 9
  • How would you enter Log[Cos[x]], x + Sin[x], and E^Sin[x] (or 2^Sin[x], x^x,...)? – Michael E2 Apr 15 '21 at 19:33
  • Setting Id = Identity for brevity, I would enter those expression like this: Log[Cos][x], (Id+Sin)[x], (E^Sin)[x], (2^Sin)[x], (Id^Id)[x] – jjagmath Apr 15 '21 at 20:47
  • 1
    Would an approach where you define an alias work? For example, you would enter Log[Cos] alias x that would look like Log[Cos] @ x but would use your evaluation function. – Carl Woll Apr 15 '21 at 21:24
  • @CarlWoll That's an ingenious approach. I think that avoids the problem of delayed evaluation. Unfortunately also introduces others problems, since that wouldn't get along with operators (Map, Apply, ...) – jjagmath Apr 15 '21 at 21:56
  • Do you mean want Map[Sin + Cos, Range[5]] to give {Sin[1]+Cos[1],...,Sin[5]+Cos[5]}? That seems a different problem than rewriting input. I don't think any $Pre method would work on this. – Michael E2 Apr 15 '21 at 22:01
  • @MichaelE2 I'm not sure why, but the $Pre method does work. – jjagmath Apr 15 '21 at 22:13
  • 1
    @jjagmath I believe the input (Map[..]) is evaluating before the transformation in ThroughFunctions is applied, since its arguments are not held. This is the reason Plot[] fails: it is evaluated before the replacement. I don't think a robust solution can operate on the expression after it has been evaluated. If you want (Sin + Cos)[x] to evaluate to Sin[x] + Cos[x] whether it is on input or the result of an intermediate computation, I don't know how that could be done. It could be done with a wrapper like func[Sin + Cos][x], by defining func like ThroughFunctions. – Michael E2 Apr 15 '21 at 22:28

1 Answers1

1

For the example mentioned in the question, we just need to adjust the evaluation order a bit:

$Pre =.
SetAttributes[ThroughFunctions, HoldAll]
ThroughFunctions[expr_] := 
 Hold@expr //. {x_?NumericQ[___] :> 
     x, (f_ /; MemberQ[Attributes[f], NumericFunction])[x___][y___] :> 
     RuleCondition@Through[f[x][y]]} // ReleaseHold

$Pre = ThroughFunctions;

Plot[(Sin + Cos^2)[x], {x, 0, Pi}]

enter image description here

xzczd
  • 65,995
  • 9
  • 163
  • 468
  • There's an evaluation leak: x = 3; Block[{x}, Plot[(Sin + Cos^2)[x], {x, 0, Pi}]]. – Michael E2 Apr 16 '21 at 04:04
  • @MichaelE2 Oops, you're right, and I've no idea how to fix this… – xzczd Apr 16 '21 at 04:10
  • 1
    Yeah, I ran into that myself and found a workaround. But the OP's actual goals seem to be a bit broader, judging from a recent comment. They seem to want this type of evaluation no matter how (Sin + Cos^2)[x] arises (e.g. (Sin + Cos^2) /@ {x}, (Sin + Cos^2) @@ {x}, etc.). I can handle transforming the input, but I don't know how to do it when the expression arises programmatically, in the middle of a computation. – Michael E2 Apr 16 '21 at 04:20
  • @xzczd What is RuleCondition? It's undocumented – jjagmath Apr 16 '21 at 20:59
  • @jjagmath Yeah, it's a handy undocumented function for adjusting evaluation order. (A simple example illustrating its effect: {Hold[1] /. 1 :> 1 + 1, Hold[1] /. 1 :> RuleCondition[1 + 1]}. ) For more info you may refer to https://mathematica.stackexchange.com/a/29319/1871 – xzczd Apr 17 '21 at 02:40
  • @MichaelE2 x_?NumericQ[___] -> x should be x_?NumericQ[___] :> x, right? – Sjoerd Smit Jan 11 '22 at 07:51
  • @SjoerdSmit As long as x is not polluted, -> is not a problem :) . I intentionally choose -> and = instead of :> and := when it's possible, because I see so many people abusing :> and :=. (I even met guys think = cannot be used for defining function! ) – xzczd Jan 11 '22 at 08:24
  • 1
    @xzczd Yes, I understand that it works if x is not polluted, but why would you assume that? :> is absolutely the right choice here. – Sjoerd Smit Jan 11 '22 at 08:55
  • I agree with @SjoerdSmit. I can't explain why I didn't explain it after my first comment. It's almost certainly what I meant. – Michael E2 Jan 11 '22 at 15:21
  • @SjoerdSmit and Michael Oops, I forgot x may be polluted afterwards… Edited. Thx for pointing out. (Perhaps I'm tired… ) – xzczd Jan 12 '22 at 01:57