Group Abstract Group Abstract

Message Boards Message Boards

5
|
11K Views
|
8 Replies
|
19 Total Likes
View groups...
Share
Share this post:

How to setup and organize larger paclets as of v13+

8 Replies

I put this paclet in GitHub. https://github.com/bobohilario/SampleInterdependentPaclet

It's very similar to what Jason included in his comments.

I mostly created it so that we'd have an actual paclet that someone reading this post can clone and modify.

POSTED BY: Bob Sandheinrich

I just tried to reproduce your sample paclet "from scratch" in Mathematica—and failed using PacletTools`CreatePaclet[]. An issue on GitHub was posted:

https://github.com/bobohilario/SampleInterdependentPaclet/issues/1#issue-2181523182

Thanks. I replied in GitHub.

POSTED BY: Bob Sandheinrich

Hi Jason,

my bad, of course, during development, one needs to start out with:

PacletDirectoryLoad[ paclet_directory ]
PacletDataRebuild[]

before Needs[...] is used to load the paclet. When I tested my setup, I did not have to add the other contexts, but I may have forgotten, that I in fact did. :)

EDIT

Using Get puts contexts on $ContextPath, which is what the recommendation given by Todd Gayley wants to avoid, e.g., we would like to refer to paclet functions using alias`funcname[ ].

EDIT2:

I just tested again on a MacBook Pro M1 Max (ARM64) using v13.1 and it should work as I described without having to provide additional "Context" information.

I tested using Eclipse and it does indeed not work without providing all context information.

Using Get puts contexts on $ContextPath

Using Get or Needs in the private portion of a package does not pollute the user's context path:

In[5]:= cpath = $ContextPath;
In[6]:= Needs["PublisherID`PacletName`"];
In[7]:= Complement[$ContextPath, cpath]

Out[7]= {"PublisherID`PacletName`"}
POSTED BY: Jason Biggs

Interim Conclusion

After having done a bit more testing and updating of my code (see Revisions section in my original post), the following picture emerges:

Using full-references with Needs["context' " -> None] as proposed by OP

  • aliases in main package serve as a “lookup-table”, i.e., it is immediately clear where a function is implemented and can thus serve as a central reference for development
  • full-name access to functions is possible, e.g., we may call PuiblisherID`PacletName`Special`Private`specialFunc directly from a notebook that has loaded the paclet
  • Using full-name external function calls in the sub-packages is a bit cumbersome, albeit (typically) just the main paclet context needs to be called
  • In Workbench/Eclipse we can use F3 from the main package to immediately jump to the code that is implementing the function in a sub-package
  • This implementation is completely compatible with Workbench/Eclipse, i.e., for development all you need to do is to comment out the Needs[ ] in the main package. Run as Wolfram will then work (if you have gotten rid of things like <<init.m and what have you in the run configurations).

Loading sub-packages into the main private context as proposed by Jason Briggs

  • it is not clear where a function has been implemented from looking at the main package
  • full-name access to functions is not possible, e.g., we cannot call PuiblisherID`PacletName`Special`Private`specialFunc from a notebook any more
  • short name references in the sub packages for external functions are more convenient
  • In Workbench/Eclipse it is not possible to use F3 to immediately jump to a functions implementation
  • The implementation is not compatible with Workbench/Eclipse (at least I have not managed to get it running with Run as Wolfram).

So far, these points make this a "matter of taste". Am I missing something? Is there something else that will clearly tilt the scales to one side?

EDIT

I added Workbench/Eclipse specific detail (F3) I addressed Workbench/Eclipse compatibility regarding runs during development

To wrap this up: Both approaches appear valid, but only the one proposed by the OP currently works out in Workbench/Eclipse, which is imo an important feature as larger projects call for the refactoring and integrative power of a full-fledged IDE.

What is further worth mentioning: The setup proposed by the OP can be used during development in Workbench/Eclipse and can then be converted into the form proposed by Jason Briggs for deployment:

  • Out-comment the „aliases“ in the Private context of the main package and add Needs or Get expressions to load the sub-packages
  • Add Needs statement to call the common core and de-endent packages in any subpackages (if deemed necessary)
  • Remove full context prefixes in the subpackages,e.g., most often PublisherID`PacletName`Core`

These changes can be easily and quickly done in an IDE. :)

I get an error when loading the paclet using the structure you've laid out, the line

Needs["PublisherID`PacletName`Core`" -> None]

because the package can't be found

In[3]:= FindFile["PublisherID`PacletName`Core`"]
Out[3]= $Failed

If I modify the kernel extension in the paclet info file to use

"Context" -> {
    {"PublisherID`PacletName`", "PacletName.wl"},
    {"PublisherID`PacletName`Core`", "Core.wl"},
    {"PublisherID`PacletName`Special`", "Special.wl"}
}

then it works as you describe.

I don't know if the method I lay out below is the 'best' practice but it works for me. I added another .wl file with package-internal functions that can be used by other subpackages that aren't exported to the user.

PacletName.wl

BeginPackage["PublisherID`PacletName`"]

(* implemented in Core.wl *)
coreFunc::usage = "..."

(* implemented in Special.wl *)
specialFunc::usage = "..."

Begin["`Private`"]

(* load all sub-packages here in the private context *)
Get["PublisherID`PacletName`Internal`"]
Get["PublisherID`PacletName`Core`"]
Get["PublisherID`PacletName`Special`"]

End[]

EndPackage[]

Core.wl

BeginPackage["PublisherID`PacletName`Core`"]
(* Exported symbols added here with SymbolName::usage *)

Begin["`Private`"]
Needs["PublisherID`PacletName`"] (* so that coreFunc is on the context path *)

coreFunc[k : (_Integer | _List)] := 2 * k (* simple example *)

End[]
EndPackage[]

Special.wl

BeginPackage["PublisherID`PacletName`Special`"]
Begin["`Private`"]

Needs["PublisherID`PacletName`"]
Needs["PublisherID`PacletName`Internal`"] (* for helperFunc *)

specialFunc[ arg_Integer ] := coreFunc @ helperFunc @ arg

End[]
EndPackage[]

Internal.wl

BeginPackage["PublisherID`PacletName`Internal`"]
(* introduce internal symbols of the paclet *)
helperFunc::usage = "..."

Begin["`Private`"]

helperFunc[x_] := {x, x^2}

End[]
EndPackage[]

Using this structure I get the same results,

In[5]:= Needs["PublisherID`PacletName`"]

In[6]:= coreFunc[4]
Out[6]= 8

In[7]:= specialFunc[4]
Out[7]= {8, 32}

But it avoids the function aliases you are using, which I find confusing

POSTED BY: Jason Biggs
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard