Modern package style of Mathematica

This post was auto converted and may contain formatting errors.

I came across a discussion on Stackexchange about the modern style of organizing code for Mathematica packages. This style is still ‘undocumented and should be used at your own risk’. But I’ve noticed more and more Mathematica internal packages start to use it. This new style improves the modularity compared with the old package style(, albeit not as good as those ones in python). This post is some notes about it.

Suppose the name of the package is MMAPackage. The file structure is pretty much the same:

[nc]MMAPackage
-- Kernel
   -- init.m
-- MMAPackage.m
-- Module1.m
-- Module2.m
-- ...
First you should have a folder /MMAPackage with the same name as the package in the directory opened by
SystemOpen @ FileNameJoin[{$UserBaseDirectory, "Applications"}];

Modern style declaratives

Package and PackageExport

A minimal example would be a single MMAPackage.m with the following content:

(* MMAPackage.m *)
Package["MMAPackage`"]

PackageExport["MySuperAdvancedFunction"]
MySuperAdvancedFunction[x_] := x + 1;

Save this file (in the correct directory). Open another notebook and type:

<< MMAPackage`

MySuperAdvancedFunction[41]
(* 42 *)

Perfect! Here Package opens the package context. And PackageExport["func"] exposes the symbol func to the package user.

PackageImport

It’s a common case that we sometimes need to use functionalities from other packages inside our package. To do this, we can use PackageImport. In the following example, I need to use the EulerSum function from package NumericalCalculus.

(* MMAPackage.m *)
Package["MMAPackage`"]

PackageImport["NumericalCalculus`"]

PackageExport["MySuperAdvancedFunction2"]
MySuperAdvancedFunction2[a_, b_, dx_] := EulerSum[x ^ 2, {x, a, b, dx}];

And the package user can use your function as usual:

<< MMAPackage`

MySuperAdvancedFunction2[1, 2, 0.1]
(* 413.296 *)

PackageScope

Before talking about this declarative, let’s first understand the context generated by this packaging style. All exported symbols will be in the first level, e.g.

MMAPackage`exportedSymbol1
MMAPackage`exportedFunction2

Any other symbols will be in a private context PackagePrivate of corresponding module file name:

MMAPackage`MMAPackage`PackagePrivate`symbol1
MMAPackage`MMAPackage`PackagePrivate`func2
MMAPackage`Module1`PackagePrivate`symbol3
MMAPackage`Module2`PackagePrivate`func4

And using PackageScope can give us another context. PackageScope["func"] will have the func setup in context

MMAPackage`PackageScope`func

About the init.m file

Specify initialization order

The order of evaluating *.m file code files is alphabetical after the file with the package name being evaulated. Suppose we have the following MMAPackage.m, Config.m and Parameters.m files.

(* MMAPackage.m *)
Package["MMAPackage`"]

Print["MMAPackage.m loaded"]
(* Parameters.m *)
Package["MMAPackage`"]

Print["Parameters.m loaded"]
(* Config.m *)
Package["MMAPackage`"]

Print["Config.m loaded"]

Loading the package will print:

MMAPackage.m loaded
Config.m loaded
Parameters.m loaded

If we want to change the default order for some reason, we can make use of the init.m file. If there is such a file existing, it will have the highest priority during evaluation. Let’s create this file under the Kernel directory.

(* init.m *)
$basePath = DirectoryName[$InputFileName, 2];

Get[FileNameJoin[{$basePath, "Parameters.m"}]]
Get[FileNameJoin[{$basePath, "Config.m"}]]
Get[FileNameJoin[{$basePath, "MMAPackage.m"}]]

Now the it will print something like the following. Each file is loaded multiple times but the first three loads are in the order we specify.

Parameters.m loaded
Config.m loaded
MMAPackage.m loaded
Config.m loaded
MMAPackage.m loaded
Parameters.m loaded
MMAPackage.m loaded
Config.m loaded
Parameters.m loaded

Perform some global instructions

In this init.m file example, we perform some global instructions about the package. The first part checks the version of the Mathematica and abort the evaluation of the package if the required version is not met. Then it specifies a initialization order that we described in last section. In the end, it adds the Protect attribute onto all the symbols inside the package.

(* Restrict the runnable version number of Mathematica *)
If[!OrderedQ[{11.0, 0}, {$VersionNumber, $ReleaseNumber}], 
  Print["MMAPackage requires Mathematica 11.0.0 or later."];
  Abort[]
]

Unprotect["MMAPackage`*", "MMAPackage`Private`*"];
(* Specify the initialization order *)
$basePath = DirectoryName[$InputFileName, 2];
Get[FileNameJoin[{$basePath, "Parameters.m"}]]
Get[FileNameJoin[{$basePath, "Config.m"}]]
Get[FileNameJoin[{$basePath, "MMAPackage.m"}]]

(* Protect the symbols used in the package *)
SetAttributes[
  Evaluate @ Flatten[Names /@ {"MMAPackage`*"}],
  {Protected, ReadProtected}
]

Caveat!

There are some important things to remember when using such style of packaging.

DON’T put tailing semicolon (or anything else)

The parsing of such structure is done during runtime by a static code parser and apparently this parser is not yet very fault-tolerant. Things like tailing semicolon will break the parsing process and results in error. So DON’T do the following:

PackageExport["MMAPackage`"];

DON’T use Needs

As just said, the static code parser won’t recognize Needs. If you want to import other packages, use PackageImport.

Module file name should be valid context name

This basically means no whitespace or _ are allowed.

Need patch works in earlier than 11.0 version Mathematica

There are some context evaulation issues in earlies version of Mathematica. See here and here for more information.