MTools
is a Wolfram user community contribution kindly offered by Faysal Aberkane on GitHub:
The good
MTools offers a degree (only) of object-orientation for Mathematica. It is not a fully-fledged OO system, nor does it claim to be:
Pro: It's definitely better than nothing. Because OO is very useful together with Mathematica!
Pro: Some basic support for inheritance (promotes reuse, helps organisation of code and logic, and reduces typing).
Pro: You can prompt on the "method" functions available to an object within a Mathematica notebook and get some (not a lot) of info about each "method" function.
Pro: MTools fields can carry MTools objects, and you can chain calls: obj1.obj2.f2
Pro: Given that Objectica seems to be dead, MTools seems to be the only practical contender for use on a substantial modelling project requiring classes and OO in Mathematica (if you don't count Abstract Data Types using TagSetDelayed)
The not so good
Con: It is not clear how well MTools will be supported longer term, although the fundamental mechanisms of the Wolfram Language it is based on are unlikely to change. There is no explicit vendor support; hopefully the existence of MTools has not given Wolfram Research an excuse to not develop a fully-fledged OO system.
Con: No obvious "built in" way to separate interface and implementation (although it is possible to HACK with some coding gymnastics and requires some manual coding discipline).
Con: No obvious (simple) way to achieve visibility control of attributes and function/method visibility (although custom naming conventions can be used to indicate intended visibility). So while you can mimic "by hand" using protected hooks and some of the Design Patterns that rely on visibility control, there is no built-in support or IDE guidance. It might be possible to HACK it in, but it's not elegant.
Con: The Wolfram Workbench editor only has a limited ability to see the method functions (and their arguments), which might be due to limitations in the not-so-spectacular Mathematica function documentation approach, which is not "method friendly".
Con: The (otherwise completely awesome) Wolfram Language Plugin for IntelliJ IDEA also does not recognise MTools methods.
Con: The Mathematica Notebook editor does not see the field accessors, which is particularly annoying if you wish to enjoy chaining MTools objects - yet another reason Mathematica needs fully-fledged vendor-supported OO.
Con: While the MTools dot notation for navigating to fields and methods is nice, it is fragile, and in many cases either parentheses or other notation options must be used (see tips below).
Con: It does not always play nicely with the Mathematica Entity system (which Entity system has many limitations that render it unsuitable as an OO replacement). For example, if you have an MTools field that carries an Entity (so you can wrap an Entity) there are some cases where the MTools notation does not resolve well against the Entity.
Following are some tips for getting the most out of MTools provided in no particular order. This is NOT a tutorial about MTools (please visit the external links).
Use $ContextAliases within Packages to access the main MTools symbols
MTools offers functionsNewClass
and New
and uses o
to refer to the object being handled by a "method" defined on an MTools class. Outside a package a minimal usage would be:
NC = NewClass["Fields" -> {"a"}];
NC .f1[x_] := x + o["a"];
nc = New[NC][{"a" -> 1}];
nc.f1[2]
3
If you try this within a Package it will in fact create new symbols for NewClass
, New
, and o
within the Package context. The clumsy workaround is to reference them fully qualified:
NC = MTools`Core`MPlusPlus`NewClass["Fields" -> {"a"}];
NC .f1[x_] := x + MTools`Core`MPlusPlus`o["a"];
nc = MTools`Core`MPlusPlus`New[NC][{"a" -> 1}];
nc.f1[2]
3
Not a lot of fun. It can be made less horrible using $ContextAliases within the Package:
BeginPackage["LessAwful`", { "MTools`"}]
newNC::usage = "newNC[a]";
Begin["`Private`"]
$ContextAliases["M`"] = "MTools`Core`MPlusPlus`"; (* Super short *)
NC = M`NewClass["Fields" -> {"a"}];
NC .f1[x_] := x + M`o["a"];
newNC[a_] := M`New[NC][{"a" -> a}];
...
Use as:
<< LessAwful`
nc = newNC[1]
nc.f1[2]
3
Beware clashes between field names and inherited "method" names
MTools does not distinguish between "field" names and method names. So if you have a base class with a method "foo[]" and a field "foo" they will clash.Limitations when wrapping an Entity with an MTools class
Because the Mathematica Entity is a bit limited - including EntityFunction currently being limited to scalars (as of MMA13) - it can be useful to wrap an Entity in an MTools class and value add with nicely organised methods (which can then leverage Entity queries behind the scenes), but there are a few "gotchas".
Con: If the MTools field for carrying the Entity is "e", you can't use the dot notation accessors. Say you have an object obj
of class MyClass
:
obj.e
sub::noFct: Class MyClass doesn't have a sub class with member function Entity.
Workaround: Just use the bracketed form instead:
obj["e"]
GOTCHA: Sometimes you need to use parentheses brackets () to quarantine the dot notation
Especially when using Part access [[]]] it's sometimes necessary to use parentheses:
MyClass = NewClass[{"Fields"->"array"}]
MyClass:mod[n_] := Module[
{item},
item := (M`o.array)[[n]];
...
Parentheses are sometimes also needed when mapping:
f[#] & /@ obj.field (* Sometimes fails *)
f[#] & /@ (obj.field)
GOTCHA: Avoid using local Module variable names the same as MTools field names
This can cause trouble that is hard to debug:
MyClass = NewClass[{"Fields"->"sameName"}]
MyClass:mod[] := Module[
{sameName},
sameName := M`o.sameName;
...
One approach is to just avoid the name clash:
MyClass = NewClass[{"Fields"->"sameName"}]
MyClass:mod[] := Module[
{sn},
sn := M`o.sameName;
...
(BTW: The example above is not advocating the use of such convenience variables, it's just an to illustrate the issue.)
In some cases you can use the explicit getter form with the String name of the field rather than the dot form. The following works:
MyClass = M`NewClass[{"Fields"->"sameName"}]
f[ob1_MClass1 := Module[ {obj2},
...
obj2.sameName = obj.get["sameName"];
...
];