4
$\begingroup$

According to the documentation "$Context is searched after $ContextPath,"

That means that when you define variables within the a package (within Begin["private"]), they will be shadowed (I use the term loosely here) by global variables with the same name.

Two questions:

  1. This seems a bad choice. Why isn't $Context searched first?
  2. How do you ensure you don't grab global variables for whatever code you put in the package.


Example

From the wolfram documentation:

BeginPackage["Collatz`"]

Collatz::usage =
        "Collatz[n] gives a list of the iterates in the 3n+1 problem,
        starting from n. The conjecture is that this sequence always
        terminates."

Begin["`Private`"]

Collatz[1] := {1}

Collatz[n_Integer]  := Prepend[Collatz[3 n + 1], n] /; OddQ[n] && n > 0

Collatz[n_Integer] := Prepend[Collatz[n/2], n] /; EvenQ[n] && n > 0

(* say I want to put a new function right here *)


End[ ]

EndPackage[ ]

What if I wanted to define some new function in the package:

NewFunc[ ...] := ... Collatz[...]...

If there was a Collatz defined in Global, now the NewFunc in the package will behave unexpectedly. Maybe we could do NewFunc[ ...] := ... Collatz`Collatz[...]... to fix it. But it seems non elegant.

And finnally, what if we take a more procedural style in the Private section? Then it will be calling all sorts of global variables.

How do we deal with this?

Example 1 update 1

In response to @naser (Couldn't paste this into a comment) The following gives unexpected results (results listed in comments) BeginPackage["mypkg`"] collatz::usage = "Collatz[n]"
newFunc::usage = "my new function"

Begin["`Private`"]
collatz[x_] := Print["in my package collatz function"]
newFunc[x_] := collatz[x] (*this calls the above, not the global*)
End[]
EndPackage[];

(*"mypkg`"
"Collatz[n]"
"my new function"
"mypkg`Private`"
"mypkg`Private`" *)

collatz[x_] := Print["in global one!"]
mypkg`newFunc[3]
(*"in global one!"*)

Example 2

The following package messes with the global variables $Context thing = 7;

Begin["Private`"]
thing = 9 ;
End[]

$Context
thing

Though it seems if I am less lazy, it doesn't:

$Context
thing = 7;

BeginPackage["mPack`"]
Begin["Private`"]
thing = 9 ;
End[]
EndPackage[]

$Context
thing

I don't understand why wrapping it twice helps.

Evidently, it has to be wrapped with an BeginPackage, because the following will modify the global variables:

$Context
thing = 7;

Begin["mPack`"]
Begin["Private`"]
thing = 9 ;
End[]
End[]

$Context
thing

The following local variable thing does overwrite the global on here:

"_________________________________"
$Context
$ContextPath
thing = 7;

Begin["mPack`"];
"_________________________________"
$Context
$ContextPath

Begin["`Private`"];
"_________________________________"
$Context
$ContextPath

thing = 9 ;

End[];
End[];
"_________________________________"
$Context
$ContextPath

thing
$\endgroup$
4
  • $\begingroup$ Can you be more specific about what you're concerned about? $\endgroup$ Commented May 10 at 1:27
  • 1
    $\begingroup$ If you put your package wide variable inside the Private part of the package, then it will not conflict with global variables with same name. If you put it outside the Private part of the package, then by design, it will exported and be visible. I never use package wide variables myself. I keep variables local to each module. Like you would do normally with Modules. $\endgroup$
    – Nasser
    Commented May 10 at 1:29
  • $\begingroup$ Thanks for the update. I think `Collatz`Collatz` is the best solution. Handily, MMA will let you know with the warning message and also by making all occurances of `Collatz` highlighted Red. But I'm not sure there's much to be done other than to try not to step on your own toes. MMA can't read minds unfortunately. $\endgroup$ Commented May 10 at 1:42
  • $\begingroup$ In other programing languages though, if you are inside a package, everything is assumed local by default. $\endgroup$
    – ions me
    Commented May 10 at 1:44

2 Answers 2

4
$\begingroup$

Simple experiment shows it works as expected. calling collatz from inside a function in the package will call the package collatz function and not the global defined one. Only issue is shadowing, which is expected.

define global collatz

 collatz[x_] := Print["in global one!"]

Now in new cell, define your package

BeginPackage["mypkg`"]
collatz::usage="Collatz[n]"    
newFunc::usage="my new function"

Begin["`Private`"]
collatz[x_]:=Print["in my package collatz function"]
newFunc[x_]:= collatz[x] (*this calls the above, not the global*)
End[]
EndPackage[];

Now you will get the shadowing warnings and red markings (screen shot at bottom)

collatz::shdw: Symbol collatz appears in multiple contexts {mypkg,Global}; definitions in context mypkg` may shadow or be shadowed by other definitions.

Now call newFunc

newFunc[1]

gives

in my package collatz function

So I do not see what the issue is.

V 14

enter image description here

Update

If global function named collatz is defined after loading the package, then the global one will be used. So to insure that the package correct functions are used, always load the package before using it each time.

This ensures the package own definitions are used each time.

May be someone can provide a more robust solution to this issue. Note, I did all this in noteebook. Not in .m files as I normally do. I have to try this all again using .m file to see if it makes difference. ps. I tried it in m file and no difference. If we define global function of the same name as in the package, it will override it. So again, the best solution to this is to load the package each time before using it.

Update 2

What actually happens is this. By loading the package first, now collatz is defined and since it is exported, it is seen from outside. When after loading the package we define function with same name, outside the package, we are actually redefining the package function itself now.

Since collatz is exported, one can redefine it. We are not creating a new global function collatz but changing the downvalue of the package function collatz.

One way to avoid is, is to always define global functions in the Global context. Like this screen shot show. Now the package function definition is not overwritten. But now to call global function we have to use the explicit Global context each time.

enter image description here

Bottom line, to define function in global context, and to be safe, always add explicit Global to the name when defining it and when calling.

This way it will not redefine some already loaded package exported function by mistake which happened to have the same name.

Update 3

This is my final answer. This is actually what I do in my package. The solution is not to add the package to the context path! This means to call any function in the package one must preappend the package name to the function. But this is what I prefer to do anyway. This makes it more clear where the function I am calling lives.

So this is the new setup

Internal`InheritedBlock[{$ContextPath}, Get["E:/tmp/mypkg.m"]]
$ContextPath

(* {"Wolfram`Chatbook`", "System`", "Global`"} *)

You see, mypkg context is not there. Now defining any function in global context will not change the definition of anything in the package

 collatz[x_] := Print["in global one!"]
 collatz[x]

 (*in global one!*)

  mypkg`collatz[x]
  (* in my package collatz function *)

   mypkg`newFunc[1]
   (*in my package collatz function*)

It worked. Here is again the file mypkg.m

BeginPackage["mypkg`"]
collatz::usage="Collatz[n]"    
newFunc::usage="my new function"
    
Begin["`Private`"]
collatz[x_]:= Print["in my package collatz function"]
newFunc[x_]:= collatz[x] (*this calls the above, not the global*)
End[]
EndPackage[];

Bottom line. Do not add the package to the context path when loading it. This also gets rid of the shadowing problem as well.

You get 2 birds with one stone by using this method.

You just have to add the package name now to each call. Which is a good thing actually. If you have 5 packages loaded, and looking at the code, you know which function is coming from which package, since the name of the package is next to the name of the function. self documenting.

$\endgroup$
9
  • $\begingroup$ Run the package cell first, then run the global definition of colatz. Now if you call newfunc you will get unexpected behavior. $\endgroup$
    – ions me
    Commented May 10 at 2:14
  • $\begingroup$ @ionsme Yes, defining the global function after loading the package, overwrites it. But if you load the package before running each time, this issue does not show up. The package colatz will be used then. i.e. each time you want to use the package, load it before. Even if you had loaded it earlier. I guess this is the nature of Mathematica's design. I do not like it either, but now I see no other way. Even adding packageName``collatz did not help. Only way is to reload the package itself before using it to insure the package function are the ones used. $\endgroup$
    – Nasser
    Commented May 10 at 2:22
  • 1
    $\begingroup$ @ionsme and Nasser Usually we Protect functions provided by package, don't we? $\endgroup$
    – xzczd
    Commented May 10 at 9:09
  • 1
    $\begingroup$ @xzczd yes. good point. This is also what is done in this web page examples of package. But I have never seen any one in this forum does that. Protecting the exported package names will prevent accidental redefinition by a user. $\endgroup$
    – Nasser
    Commented May 10 at 9:15
  • $\begingroup$ Alternatively, starting from 13.0 we can use Needs["context`"->"alias`"] or Needs["context`"->None] to avoid messing with $ContextPath. $\endgroup$
    – Silvia
    Commented May 11 at 7:27
2
$\begingroup$

Oh! This has messed me up before!

you need `Private` instead of Private`

That doesn't answer your question really, but it does matter, and may help clear things up.

If I understand the nature of your question it's that you are seeking to understand how neqFunc gives different results in the following code, which can be run in a single cell in a fresh kernel for the results I find confusing.

BeginPackage["mypkg`"];
collatz::usage = "Collatz[n]";
newFunc::usage = "my new function";

Begin["`Private`"];
collatz[x_] := Print["in my package collatz function"]
newFunc[x_] := collatz[x] (*this calls the above,not the global*)

End[];
EndPackage[];

newFunc[n](*uses the package version*)
collatz[x_] := Print["in global one!"]
newFunc[n](*uses the global version*)

I admit I do find this confusing as well. More worryingly is that the red syntax highlighting for the shadowing does not occur! In fact, we get what I think is kinda pathological output from ??collatz, where the Usage and Full Name are from the package, but the definition is from the Global` context!

enter image description here

A final note: I think it's possible that there are differences between how packages behave when loaded from a separate file as opposed to from within the notebook (or in the same cell in this case), but I don't actually know (or have forgotten) if that is the case.

$\endgroup$
2
  • $\begingroup$ Ah, that explains why I was getting inconsistent results. Still trying to figure out the rest of the question. $\endgroup$
    – ions me
    Commented May 10 at 1:54
  • $\begingroup$ yeah! when I was first dipping my toes into using and making packages, this cost me like a solid two days once! its counterintuitive IMO, since I would expect the private context to be mPack``Private` but, I guess the backticks resolve to just one ` $\endgroup$ Commented May 10 at 1:57

Your Answer

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

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