3

I had something like the following in my notebook.

test1[g_] := (g == 5);
test2[g_] := (g == 6);
tests={"test1", "test2"}
ToExpression[#][5] & /@ tests

When I put this code in a package it doesn't work because test1 is now called MyPackage'Private'test1. How can I modify the last line to make this code run both inside package and inside notebook?

Update Here's why I was doing doing ToExpression as opposed to using Symbols. In retrospect, maybe it's easier to use Symbols instead

I had a function which I call like getGraphs["LeafFree","Planar",!"Tree",...] to get all graphs that are leaf free, planar and not trees. Some of those strings are classes in GraphData, while others were my own classes. For each of my own classes I had a function with identical name, like LeafFree that tested the property. In the notebook, using ToExpression code like above was the quickest way to implement this.

getGraphs[n_Integer, cl__] := getGraphs[{n, n}, cl];
getGraphs[{nmin_Integer, nmax_Integer}, cl__] := 
 Module[{maxgraphnum = 100},
  customClasses = {"isLeafFree", ! "isLeafFree"};
  classes = {cl}\[Backslash]customClasses;
  builtinClasses = 
   GraphData["Classes"] \[Tilde] (Not /@ GraphData["Classes"]);
  Assert[classes \[Subset] builtinClasses];
  isLeafFree[gname_] := 
   FreeQ[GraphData[gname, "DegreeSequence"], 0 | 1];

  posClasses = Cases[classes\[Backslash]customClasses, _String];
  posGroup = 
   If[posClasses == {}, GraphData[nmin ;; nmax], 
    GraphData[posClasses, nmin ;; nmax]];
  negClasses = classes\[Backslash]posClasses;
  negGroups = GraphData[#[[1]], nmin ;; nmax] & /@ negClasses;

  result = Complement[posGroup, Sequence @@ negGroups];
  customTest[g_] := 
   And @@ (ToExpression[#][g] & /@ ({cl} \[Intersection] 
        customClasses));
  (*result=Take[result,Min[Length[result],100]];*)

  result = Select[result, customTest]
  ]
Yaroslav Bulatov
  • 57,332
  • 22
  • 139
  • 197
  • Have you tried `StringJoin[Context[], #] & /@ {"test1", "test2"}` – Dr. belisarius Jan 28 '11 at 04:41
  • 2
    I would consider `ToExpression` to be bad style **ducks**. Is there a reason for using string instead of symbols?? I would consider `testsAsSymbols = {test1, test2}` and `Through[testsAsSymbols[5]]` much nicer -- and it would work in both cases? – Janus Jan 28 '11 at 09:50
  • @Janus: You should make that an answer... – Simon Jan 28 '11 at 10:53
  • Ha! Everybody is shy today :) – Dr. belisarius Jan 28 '11 at 11:05
  • belisarius: yes, doesn't work, see my comments below. Janus: I was using ToExpression because I'm doing something like `MyPackage'doChecks[{"test1","test2"}]` where `test1` is a name of function defined inside the function `doChecks` – Yaroslav Bulatov Jan 28 '11 at 23:10

2 Answers2

4

I agree with the comments above that you should perhaps have a compelling reason to do this, but here goes. Here is a code I use in such cases, which allows your symbol to be parsed in whatever context you like, at run-time:

SetAttributes[ParseTimeNameSpaceWrapper,HoldFirst];
Options[ParseTimeNameSpaceWrapper] = {
  LocalizingContext->"MyLocalizingContext`",
  DefaultImportedContexts:>{"Imported1`", "Imported2`"},
  ExtraImportedContexts:>   {}
};



ParseTimeNameSpaceWrapper[code_,opts:OptionsPattern[]]:=
Module[{result,
  context = OptionValue[LocalizingContext],
  defcontexts = OptionValue[DefaultImportedContexts],
  extraContexts = OptionValue[ExtraImportedContexts],
  allContexts},
  allContexts = {Sequence@@defcontexts,Sequence@@extraContexts};
  BeginPackage[context,If[allContexts==={},Sequence@@{},allContexts]];      
    result = code;  
  EndPackage[];
  result
]; 

You can use options to specify some contexts where these symbols exist, that you want to import during the parse stage. You can call this from any package or notebook, and the symbol will be parsed according to whatever context you specify.

HTH

Edit:

Responding to a comment (since it made the question more specific): There is no question that at run-time Context[] will display whatever is the current context from where the function was called (Global in that case). I meant something else: Context has a syntax Context[symbol], to give a context of any symbol if it is on the $ContextPath. For example, Context[getGraphs] returns Bulatov'showGraphs'. Therefore, if you need to determine the context of some exported function automatically, you call Context[function]. You can use this to construct full names of other (private) functions of that package. Here is a self - contained example:

In[1]:= 
BeginPackage["MyTest`"]; 
f[x_, y_, context_: Context[f]] :=
  Module[{f1str = "function1", f2str = "function2", f1, f2}, 
    {f1, f2} = ToExpression[context <> "Private`" <> #] & /@ {f1str, f2str};
    f1[x, y];
    f2[x, y];];

Begin["`Private`"];

function1[x_, y_] :=  Print["In function1: arguments are ", x, " , ", y];
function2[x_, y_] :=  Print["In function2: arguments are ", x, " , ", y];

End[]
EndPackage[];

Out[6]= "MyTest`Private`"

In[8]:= f[1, 2]

During evaluation of In[8]:= In function1: arguments are 1 , 2

During evaluation of In[8]:= In function2: arguments are 1 , 2

where x,y are just some sample arguments. Then, you never actually supply the last argument, but you can use the context variable inside your function, to construct long names for your other functions, as in the sample code above. Or you could just plain use Context[f] inside body of the function, and not add any arguments to it.

Szabolcs
  • 24,728
  • 9
  • 85
  • 174
Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • The problem is that I don't see a way to get correct context. I have functions `f`,`g`,`h`. `f` is put into `Global` context on `Needs` whereas `g` and `h` remain in `MyPackage'Private`. Ideally `f` would obtain the name of the package programmatically, but it's not in `Context[]` during execution of `f` – Yaroslav Bulatov Jan 28 '11 at 23:02
  • @Yaroslav: Importing a function into a Global` context does not make it belong to Global`. Its context is still the same as before. It is just that for symbols from contexts on the $ContextPath, we can use short names. If I understood correctly and `f` belongs to the same package as `g` and `h` (just not `Private` subpackage), then you can just do Context[f] <> "Private`" to reconstruct the context where `g`and `h` are, and then construct their long names, and feed them to ToExpression (of course, Context[symbol] will work reliably only when there is no shadowing, which I assume is true). – Leonid Shifrin Jan 29 '11 at 00:00
  • If I put `Print[Context[]]` inside f[], and I get `Global` printed even though f is defined inside a package, here's self-contained example -- http://yaroslavvb.com/upload/save/shifrin.zip – Yaroslav Bulatov Jan 29 '11 at 00:41
  • @Yaroslav I updated my post, hopefully this clarifies what I meant. Also, my apologies for leaving now, it is 4:50 a.m. at SPb. currently. – Leonid Shifrin Jan 29 '11 at 01:50
4

ToExpression uses the current binding of $Context when creating symbols, so you can force your expression to be interpreted within a particular context thus:

Block[{$Context="MyPackage`Private`"}, ToExpression[#][5]] & /@ tests

I'm not sure I understand the circumstances of the original question. You can get the current context using $Context or Context[]... but ToExpression will automatically use the current context without intervention.

If I run the exhibited code in a notebook, it works fine. If I run it like this:

Begin["MyPackage`Private`"]
test1[g_] := (g == 5);
test2[g_] := (g == 6);
tests = {"test1", "test2"}
ToExpression[#][5] & /@ tests
End[]

... it also works fine. I can get it to fail if I run it like this:

(* in the package file *)
Begin["MyPackage`Private`"]
test1[g_] := (g == 5);
test2[g_] := (g == 6);
End[]

(* in the notebook *)
tests = {"test1", "test2"}
ToExpression[#][5] & /@ tests

... which not only fails but also creates spurious symbols in the notebook's context. You can work around this problem using the Block recipe from above.

If you want to capture the context that was in effect at the moment the package code was loaded, you can do something like this:

(* in the package *)
Begin["MyPackage`Private`"]
test1[g_] := (g == 5);
test2[g_] := (g == 6);
tests = {"test1", "test2"};
With[{context = $Context},
  runTests[] := Block[{$Context = context}, ToExpression[#][5]] & /@ tests
]
End[]

(* in the notebook *)
MyPackage`Private`runTests[]

runTests uses With to inject the private package context into its definition.

As Janus' noted, it is better to use symbols than strings since they automatically manage the whole context issue -- but this assumes your actual use case will permit the use of symbols.

WReach
  • 18,098
  • 3
  • 49
  • 93
  • The reason `Context[]` doesn't work is because the enclosing function is put into Global` context on `Needs`, so `Context[]` is Global at that point of execution, meanwhile test1,test2 are "internal" functions, so they remain in MyPackage`Private` context – Yaroslav Bulatov Jan 28 '11 at 22:57
  • 1
    The method with `Block` at the top of your answer is simple and elegant, but will (silently) fail if the symbol with the same short (string) name does exist in some of the contexts currently on the $ContextPath (which does not necessarily imply shadowing). A simple example of this would be: `f[x_] := x^2; Block[{$Context = "Test'Private'"}, ToExpression["f"][x]]`. This is one of the reasons why I use `BeginPackage - EndPackage` in my answer - they take care of this problem. Or, you could use `Block[{$Context = "Test'Private'", $ContextPath = {"Test'", "System'"}},...]` explicitly. – Leonid Shifrin Jan 29 '11 at 19:27