4
$\begingroup$

This is based on A function that modifies functions - well-defined operation?, a previous question of mine.

Lukas Lang's answer of course works very well, but now I encountered the following edge case: What if I have a function with multiple arguments, say f[x, y], and only want to apply my linearize function to the second argument?

I don't know how to approach this. Something like linearize[ f[x_, #]& ] gives me a Tag Function is Protected error, and I don't understand enough wolfram (yet) to get this. Also, I thought maybe Curry could be applied, but I couldn't immediately figure out how.

Is this possible?


Edit while reviewing question: Of course, I could pass an optional position parameter to linearize, which would tell linearize to only linearize in that position. But that seems a bit messy (especially since my input function can have arbitrary arity and possibly optional arguments), and I would like to go for a more functional approach if possible.

$\endgroup$
1
  • $\begingroup$ You need to assign the definition to "f" by: f /; linearize[f[x_, #] &] $\endgroup$ Jul 8 at 9:29
2
$\begingroup$

The immediate problem with linearize[ f[x_, #]& ] is that you are trying to define sub-values for Function: f[x_, #]& is equivalent to Function[f[x_, #]], so linearize will try to do

Function[f[x_, #]][pre___, 0, ___] := 0

This attempts to assign the definition to Function, which is Protected, hence the error.

If you want some kind of a specification of which arguments should be linearized that is more explicit than a second argument with indices, I would suggest something like the following:

symbols = Alternatives[basisElement];
noSymbol := FreeQ[symbols]

Attributes[linearize] = {HoldFirst};
linearize[f_[spec___]] :=
  (ReplaceList[
     FixedPoint[
      Replace[Hold[pre___, ##, post___] :> Hold[pre, ___, #, ___, post]],
      Hold[spec]
     ],
     Hold[pre___, #, post___] :>
       Replace[
        Hold[PatternSequence[pre], PatternSequence[post]],
        # -> _,
        {2}
       ] /. Hold[pre2_, post2_] :> (
        f[pre2, 0, post2] := 0;
        f[pre3 : pre2, v_ + w_, post3 : post2] := f[pre3, v, post3] + f[pre3, w, post3];
        f[pre3 : pre2, a_*v_, post3 : post2] := a*f[pre3, v, post3] /; noSymbol[a];
       )
    ];);
linearize[f_] := linearize[f[##]]

The syntax is linearize[f[spec]], where spec is a sequence of patterns and slots such as f[__,#,_,#,_]. Patterns can be any valid pattern and will not be linearized. # is a placeholder for a single argument that is to be linearized. Finally, ## is a placeholder for 1 or more arguments that should all be linearized, so effectively ___, #, ___.

Some examples of how to use it:

Linearize only the second of two arguments:

Clear[f]
linearize[f[_, #]]

{f[a basisElement, c + d], f[a basisElement], f[a basisElement, c + d, e + g]}
(* {f[a basisElement, c] + f[a basisElement, d], f[a basisElement], f[a basisElement, c + d, e + g]} *)

Linearize the first of 2 or more arguments:

Clear[f]
linearize[f[#, __]]

{f[a basisElement, c + d], f[a basisElement], f[a basisElement, c + d, e + g]}
(* {a f[basisElement, c + d], f[a basisElement], a f[basisElement, c + d, e + g]} *)

Linearize the first of any number of arguments:

Clear[f]
linearize[f[#, ___]]

{f[a basisElement, c + d], f[a basisElement], f[a basisElement, c + d, e + g]}
(* {a f[basisElement, c + d], a f[basisElement], a f[basisElement, c + d, e + g]} *)

Linearize all but the last argument:

Clear[f]
linearize[f[##, _]]

{f[a basisElement, c + d], f[a basisElement], f[a basisElement, c + d, e + g]}
(* {a f[basisElement, c + d], f[a basisElement], a f[basisElement, c, e + g] + a f[basisElement, d, e + g]} *)

As for how it works: The main idea is very similar to the linked question, but we have to be more careful constructing the pre___ and post___ patterns, since we want to restrict what they can match. It's probably best to just look at an example for the rest, and see what the different steps do. Let's look at linearize[f[_, #, _, ##]], which linearizes arguments 2, 4, 5, 6, etc.:

This version of linearize prints some additional information:

Attributes[linearize] = {HoldFirst};
linearize[f_[spec___]] :=
  (ReplaceList[
     EchoFunction["  Replace ## with ___,#,___:", # &]@FixedPoint[
       Replace[Hold[pre___, ##, post___] :> Hold[pre, ___, #, ___, post]],
       EchoFunction["Original spec:", # &]@Hold[spec]
       ],
     Hold[pre___, #, post___] :> (
       Echo[{pre, Style[#, Red], post}, "    Process one slot:"];
       EchoFunction["      Replace remaining slots with _:", # &]@Replace[
          Hold[PatternSequence[pre], PatternSequence[post]],
          # -> _,
          {2}
          ] /. Hold[pre2_, post2_] :> (
          Echo[HoldForm[f][pre2, Style["###", Red], post], "        Pattern:"];
          f[pre2, 0, post2] := 0;
          f[pre3 : pre2, v_ + w_, post3 : post2] := f[pre3, v, post3] + f[pre3, w, post3];
          f[pre3 : pre2, a_*v_, post3 : post2] := a*f[pre3, v, post3] /; noSymbol[a];
          ))
     ];);
linearize[f_] := linearize[f[##]]

Clear[f];
linearize[f[_, #, _, ##]]

enter image description here

$\endgroup$
1
  • $\begingroup$ Oof, this will take me a while to digest. Thanks :) $\endgroup$
    – Jo Mo
    Jul 8 at 10:46

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.