4
$\begingroup$

I have some code that uses a list of replacement rules to inject values (physical constants for different materials) into a more complicated expression. This is convenient since I can tweak the values in my rule list independently from the code and they appear in the code as familiar greek letters for the values until substitution.

This all works fine in the normal context of a notebook, but when I move the code to a package the symbols in my expression are in the package context and the are not replaced by applying ReplaceAll with a list of symbols created in the default Global` context.

Is there a simple way to get around this problem without resorting to tedious incorporation of a long list of named options, or explicitly passing the constants in the function code, or using order dependent lists of unlabeled data?

Can this be fixed by applying a pure function to the replacement rule lists? Something that rewrites the symbol contexts just before calling ReplaceAll for the substitution? It should transform something like this

{Global`x->1.0, Global`y->2.*^10, Global`z->0.123}

into something like this:

{MyContext`x->1.0, MyContext`y->2.*^10, MyContext`z->0.123}

with the intention of making substitutions in a call to the packaged code in a way that is transparent to the user who simply inputs a list built in a regular notebook.

Does anyone have a better arrangement for this use case?

Here's a short example of what I mean:

BeginPackage["foo`"]
Unprotect @@ Names["foo`*"];
ClearAll @@ Names["foo`*"];
f::usage = "sol=f[x,plist]"
Begin["`Private`"]
f[x_, plist_] := x + y + z /. plist;
End[]
Protect @@ Names["foo`*"];
EndPackage[]

paramList = {y -> 1, z -> 2};
sol = foo`f[x, paramList]

which gives

(*x + foo`Private`y + foo`Private`z*)

while the desired ouput would be

(*3 + x*)
$\endgroup$
6
  • $\begingroup$ The example you showed shows exactly the problem I was talking about below. You can't mix symbols from different contexts. The way you are implementing this is not correct. The y and z in your example are in package context while the y and z you are passing are from global context. I suggest writing this as I showed below, otherwise, you'll have to do hacks and other such things to fix things. So better do things right from the start (my 2 cents ofcourse) $\endgroup$
    – Nasser
    Apr 3, 2015 at 1:00
  • $\begingroup$ fyi, updated my answer with your example of how I would do it to avoid the context mix problem. $\endgroup$
    – Nasser
    Apr 3, 2015 at 1:20
  • $\begingroup$ @Nasser I get that this isn't best practice, but say that you had 50 symbols to make substitutions for instead of just y and z? Wouldn't it make some sense to have one list that's inserted and avoid typing out 50 symbols in every function definition and function call made by the user? $\endgroup$
    – dionys
    Apr 3, 2015 at 1:31
  • $\begingroup$ I do not get it. You had to type the long replacement rule of 50 replacements? So why not type one variable that is a list of all the symbols? Like this: paramList = {y -> 1, z -> 2} mySymbols = {x, y, z}; sol = f[mySymbols, paramList] You only have to type the list once and then use it. This is for me the clean solution, other than making a wrong API, then try to figure what hacks to do to fix things afterwords. But this is just how I would do it. If you wait more, may be you'll have better suggestions how to handle this. good luck. $\endgroup$
    – Nasser
    Apr 3, 2015 at 1:40
  • $\begingroup$ fyi, added an example how to extract the symbols from the replacement list automatically so no need to write them down. $\endgroup$
    – Nasser
    Apr 3, 2015 at 3:39

3 Answers 3

5
$\begingroup$

It is hard to answer you without you showing a minimal example of the problem.

But my guess is that you are making the mistake of returning symbols from the package back to the user.

The way to handle these things, is to do like all Mathematica functions do, which is pass the symbols needed in the call itself. For example, when using DSolve or Integrate, and others, the symbols x, y etc... are passed in and they are returned back in the solution. This way, you can now to the replacement in the global context with no problem. Here is an example of simple package

  BeginPackage["foo`"]
  Unprotect @@ Names["foo`*"];
  ClearAll @@ Names["foo`*"];  
  f::usage = "sol=f[x,y]"
  Begin["`Private`"]  
  f[x_,y_] := Module[{z=1}, x+y+z];
  End[]
  Protect @@ Names["foo`*"];
  EndPackage[]

In the above, x,y are passed in, from the user, while z is local. Now you can do this:

SetDirectory[NotebookDirectory[]]
Get["foo.m"]
?foo`*

Mathematica graphics

Now call the package function, but pass it the symbols in the call

 sol = foo`f[x, y]

Mathematica graphics

Now the symbols x and y can be replaced as is, since they have the same context as the caller's

  sol /. {x -> 10, y -> 2}

Mathematica graphics

So you have to pass in all the symbols you expect to be part of the result coming back.

Now compare what happens if you return symbols back from the package

  BeginPackage["foo`"]
  Unprotect @@ Names["foo`*"];
  ClearAll @@ Names["foo`*"];  
  f::usage = "sol=f[]"

  Begin["`Private`"]  
  f[] := Module[{z=1,x,y}, x+y+z];
  End[]
  Protect @@ Names["foo`*"];
  EndPackage[]

  Get["foo.m"]
  ?foo`*

Mathematica graphics

  sol = foo`f[]

Mathematica graphics

Now the replacement does not work

  sol /. {x -> 10, y -> 2}

Mathematica graphics

Again, this is how Mathematica functions all do this. If you return a symbol that is local in the package back to the user, only then you'll have the issue you are talking about.

Update

Using the example shown now in the question, this is how I would do it:

BeginPackage["foo`"]
Unprotect @@ Names["foo`*"];
ClearAll @@ Names["foo`*"];
f::usage = "sol=f[p,plist]"
Begin["`Private`"]
f[{x_,y_,z_}, plist_] := x + y + z /. plist;
End[]
Protect @@ Names["foo`*"];
EndPackage[]

Then

Get["foo.m"]
paramList = {y -> 1, z -> 2}
sol = foo`f[{x, y, z}, paramList]

Mathematica graphics

To avoid writing the symbol list explicitly, the symbols can be extracted on the fly from the replacement list itself, using a pattern, like this

paramList = {y -> 1, z -> 2, x -> x}
sol = foo`f[paramList /. Rule[x_, y_] :> x, paramList]

Mathematica graphics

$\endgroup$
2
  • $\begingroup$ Apologies for not being clearer in the question. I'm trying to use a list of rules as an input parameter in a package function. Inside that function I'm simply applying ReplaceAll with that list of rules. It's probably not good practice, but I prefer the list to explicitly passing tens of symbols. $\endgroup$
    – dionys
    Apr 2, 2015 at 23:15
  • 1
    $\begingroup$ Be the way, if you want to keep the synax of function call, you can define an auxiliary function like that: f[{x_, y_, z_}, plist_] := x + y + z /. plist; and f[x_, plist_] := f[{x}~Join~plist /. Rule[a_, b_] :> a, plist]. $\endgroup$
    – Ivan
    Apr 17, 2015 at 18:12
1
$\begingroup$

I have exactly the same project. In my case paramList always has the same variables' names, so I defined a "structure" to handle it in the package and a way to transform it into a rule:

BeginPackage["foo`"]
Unprotect @@ Names["foo`*"];
ClearAll @@ Names["foo`*"];
f::usage = "sol=f[x,plist]"
Begin["`Private`"]
constructPData[y_, z_] := pData[y, z];
pData /: Normal[pData[y1_, z1_]] := {y -> y1, z -> z1};
f[x_, plist_pData] := x + y + z /. Normal[plist];
End[]
Protect @@ Names["foo`*"];
EndPackage[]

pd = foo`constructPData[1, 2];
sol = foo`f[x, pd]

Since the transformation to a rule is invoked inside the package, the variables' names are correct.

The drawback of the method is that you need to know the structure of pData in advance (number of variables and their names).

$\endgroup$
1
$\begingroup$

Consider using strings:

BeginPackage["foo`"];
Unprotect @@ Names["foo`*"];
ClearAll @@ Names["foo`*"];
f::usage = "sol=f[x,plist]";
Begin["`Private`"];
f[x_, plist_] := 
  With[{y = Lookup[plist, "y"], z = Lookup[plist, "z"]}, x + y + z];
End[];
Protect @@ Names["foo`*"];
EndPackage[];

paramList = {"y" -> 1, "z" -> 2};
sol = foo`f[x, paramList]

Or do this:

BeginPackage["foo`"];
Unprotect @@ Names["foo`*"];
ClearAll @@ Names["foo`*"];
f::usage = "sol=f[x,plist]";
Begin["`Private`"];
With[{context = $Context}, 
  makeSymbol[x_String] := Symbol[context <> x]];
f[x_, plist_] := 
  x + y + z /. MapAt[makeSymbol@*SymbolName, plist, {All, 1}];
End[];
Protect @@ Names["foo`*"];
EndPackage[];

paramList = {y -> 1, z -> 2};
sol = foo`f[x, paramList]
Names["foo`*"]
Names["foo`Private`*"]
Names["Global`*"]

The code at the end shows that the contexts contain the correct symbols.

However the latter has the disadvantage of evaluating x + y + z symbolically first which might not always be desirable. Use Hold and ReleaseHold if you don't want a symbolic evaluation to leak.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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