6
$\begingroup$

I have got a code made of several .m files, and I would like to turn it into a package. I have a few beginner questions

  1. Can I force the loading order of the files by adding all of them in the init.m file?

  2. How can I access global variables from the package? For example, I could like to define a couple of variables that are settings used by the package. I usually do it in my main notebook, before loading the .m files with all functions. But as far as I understand, if I define it in the notebook variables will be in namespace global, and using the same variable in the package will instead search for the local variable. If I prepend Global` to the variable in the package, will it be able to find it?

  3. I have a very long settings file where I store most settings that I change very rarely. Now, these are one of the few things that actually needs to get shared in between all of the package fragments. As far as I understood one needs to write

    PackageScope[variable]
    

for each variable one wants to share. Is there any way to automatise this for all variables in the file?

$\endgroup$
8
$\begingroup$

Here are some answers

  1. That's a bad idea, and here is why: what will actually happen if you do

    Get["YourPackage`FirstFragment`"]
    ...
    Get["YourPackage`LastFragment`"]
    

    is that the loader will effectively (re)load all fragments as many times, as there are fragments. That's just how it works.

    The better options are:

    • Pick one fragment, which will serve as the initialization fragment, and call Get on it in init.m - this will prompt all fragments to load, but this one will be guaranteed to load first. And restructure your code in such a way that the loading order of other fragments does not matter.

      I think this is the best option, since in practice I have never seen the case where this could not be done, and OTOH, the dependence on the loading order for several files may indicate a fragile and insufficiently thought out design.

    • Rename your fragments, adding letters A, B etc. at the beginning of fragment names, so that the alphabetical order of new names will be the loading order you want. This should work, since the loader loads fragments in alphabetical order, except the one that has been explicitly used in Get always loads first.

  2. You surely can access global variables by using the long symbol names, as you suggested, like Global`someGlobalVar. I would however strongly discourage that, apart possibly from a testing stage, since this would make your package not self-contained, and will pollute the Global` namespace for no good reason.

    Instead, why don't you simply declare those variables in your package's main context? You can do this in your initialization fragment, like e.g.

    PackageExport["MyGlobalVar"]
    PackageExport["MyOtherGlobalVar"]
    ... 
    

    And then assign them some initial values there, and use them both from within your package, and from the outside. This is what I've been doing all the time.

  3. Unfortunately, I don't know of a good way to do this, given that directives like PackageScope are not normal WL symbols or functions, but rather tokens for the parser / loader. So they have to be present verbatim, and before the code loading actually happens, since they are processed in a separate pass, before any code is evaluated.

    The only possible option I can imagine here would be to have some build step / build process for your app, where you could have a separate file containing those definitions, which would be automatically generated from a more concise spec.

$\endgroup$
7
  • $\begingroup$ "what will actually happen if you do <snip> is that the loader will effectively (re)load all fragments as many times, as there are fragments. " Is that also true if you don't use an init.m file (For example, when you use the "Context" property in the "Kernel" extension)? $\endgroup$ May 19 '21 at 14:09
  • 1
    $\begingroup$ @SjoerdSmit I would have to check to be sure, but if memory serves, paclet mechanics is completely separate from the new-style package loader, so it should not matter. When the loader is invoked via Get, it searches for all fragments that match, and loads them all. Multiple Gets will prompt the loader to do it for every Get. I have run into this issue multiple times when I was only starting with the new format, but I have not retested this now. I don't expect that anything has changed in that regard though. I will re-test when I get a moment. $\endgroup$ May 19 '21 at 15:07
  • $\begingroup$ 1. I realised that when I started adding some Print around to make logs of what was happening... I fixed this issue by adding just one main file to init and the reordering the names of all other files like M01originalname1, M02originalname2, M03originalname3 and so on. This is fixed. Thanks $\endgroup$ May 19 '21 at 18:41
  • $\begingroup$ 2. The reason why I want to do this is that these are settings the end user should decide, and the code needs to know while loading their value. This is why i usually declare them in the laoding notebook, and would like to do that before loading the package. 3 I was trying to do something but got a problem: is there any way to load a file within a fragment package and get the loaded content in the context of THAT fragment? Cause if I use Get, it seems to get loaded in the Global context. $\endgroup$ May 19 '21 at 18:45
  • $\begingroup$ I would like to do this because I want to keep a copy of the software as it is, so not in a package. So my idea was for each file file1.m or group of files that need a separate context, I create a new file module1.m where i declare package, then get file1.m. But this does not work, the file contents are all loaded in global $\endgroup$ May 19 '21 at 18:47

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.