5
$\begingroup$

I have a module which calls number of other smaller helper modules. But those other helper modules are not used by another module, so there is no needed to have them in global context. So I wanted to move them to be inside the main module which is the only one that uses them to help improve the code. But I found two main issue when doing this.

First issue conflict in names of argument input

If the helper function uses the same input argument name, as in the main module, error is generated when moving the helper module inside it.

Here is a MWE. This is the before layout of the code

boo[a_]:=Module[{b=5}, b+a ];
foo[a_]:=Module[{}, boo[a]];

Now foo[3] returns 8 and works. Now moving boo inside foo for better encapulation since boo[] is only needed by foo, the layout becomes

foo[a_]:=Module[{boo},
     boo[a_]:=Module[{b=5}, b+a];  (*boo now local*)
     boo[a]
];

But M does not like the above

Mathematica graphics

You can ask, why not simply rename the argument(s) of boo and rename all references inside the boo code to the new name(s)? Yes, in this simple example I can change the code and do

foo[a_]:=Module[{boo},
     boo[aa_]:=Module[{b=5}, b+aa];
     boo[a]
];
foo[3]
(* 8 *)

Second issue helper function uses same local variable name as input argument of main module

If the helper function happened to have local variable in it, which has same name as the input argument of the main module, then also an error is generated. Here is an example. The before, which works

boo[aa_]:=Module[{b=5,a=3},b+aa+a];
foo[a_]:=Module[{},boo[a]]
foo[3]
(* 11 *)

The after layout

foo[a_]:=Module[{boo},
    boo[aa_]:=Module[{b=5,a=3},
              b+aa+a
    ];
    boo[a]
];
foo[3]

Mathematica graphics

The above examples shows that it is not easy to do what I wanted, without careful code rewrite all the time due to these conflicts.

The above are simple examples. The code I have is much larger which means I have to go make number of changes, renaming variables all over the place each time. So I end up now having many smaller functions all in global space, even though many of them can be put inside the Modules that really uses them.

Is there a trick to bypass this problem? Is there a better way do the above? Would workbench help in this?

Maple have no problem using the same setup

Mathematica graphics

Mathematica graphics

references safe-way-to-put-a-pattern-on-the-right-hand-side-of-a-rule http://reference.wolfram.com/language/ref/message/Rule/rhs.html

$\endgroup$
6
  • 2
    $\begingroup$ Why not just create a package? Also, I think using SetDelayed in this way inside of a module is better avoided. For your example, if boo doesn't depend on the arguments to foo, then foo has to work harder because it defines boo every time it is called, instead of having boo defined once. Also, if boo is ever left unevaluated, either in the output of foo or in a side effect, then boo will never get removed, causing a memory leak. Finally, it is much harder to debug boo when it is defined in a Module. $\endgroup$
    – Carl Woll
    Commented Aug 13, 2017 at 19:15
  • $\begingroup$ @CarlWoll how is using a package will make it easier to move the helper functions inside the module? Will not the same issue be there, but now everything is inside a new context? My main goal was to see if I can have small helper functions inside the module that uses them, not outside. So if I have 2 modules, each uses different helper functions, and there is no shared helper function, I wanted to move these inside. I am not following what you say about memory leak and having to define boo each time parent module is called. Too advanced for me to understand now. Thanks,. $\endgroup$
    – Nasser
    Commented Aug 13, 2017 at 19:23
  • 4
    $\begingroup$ What is the problem with having helper functions outside the module? If you don't like having the helper functions cluttering your namespace, than just create a package so that the helper function clutter the package namespace. If you don't like using helper functions at all, and just like having a single monolithic program, then I think you are headed down the wrong path. It is much easier to develop and unit test individual helper functions. $\endgroup$
    – Carl Woll
    Commented Aug 13, 2017 at 19:27
  • $\begingroup$ @CarlWoll I really like internal functions. They help organize code by putting code where it only needs to be seen. Having everything in flat name space is generally not a good idea IMHO. Using a package will just move the problem from cluttering the global context to the package context. This helps a little, but it will not change the overall design of the program in terms of having helper function inside a module where they are only needed. Ok, I understand this is how Mathematica works. Module in Mathematica sematics are different from traditional programming languages I've used before. $\endgroup$
    – Nasser
    Commented Aug 13, 2017 at 20:38
  • 2
    $\begingroup$ @Nasser It doesn't have to be flat. You can have a hierarchy of packages. If you load a package inside a package, then it won't be available outside the package into which it was loaded. (See "private import" here.) $\endgroup$
    – C. E.
    Commented Aug 13, 2017 at 21:41

2 Answers 2

4
$\begingroup$

[First answer was wrong - deleted.]

New answer

I rather like Carl Woll's advice, but the problem is an interesting exercise nonetheless.

Here's an approach that leverages the fact that Mathematics rewrites pattern parameters (adds a $) when code is injected. Wrap all the definitions in Hold and add the list of symbols defined by the definitions:

Hold[code, symbols]

Then inject code into the definition of the top-level function, with symbols localized with Module. Here's an example that has several definitions to show better how it works (and was a problem in my original answer):

ClearAll[foo];
Hold[
   boo[a_Integer] := Module[{b = 5}, b + a];
   boo[a_Real] := Module[{b = 5}, b - a];
   boo[a_] := boo2[a];
   boo2[a_] := Module[{b = 10}, a*b],
   {boo, boo2}
 ] /.
  Hold[code_, symbols_] :>
   (foo[a_] := Module[symbols,
      code;
      {boo[2 a], boo2[a]}]);

Tests:

foo[3]
(*  {11, 30}  *)

foo[3.]
(*  {-1., 30.}  *)

foo[x]
(*  {20 x, 10 x}  *)

Check the definition of foo:

? foo

Mathematica graphics

$\endgroup$
6
  • $\begingroup$ I should probably add that this is not the way to create new code, as the OP also remarked. It's an ad hoc fix to existing code. And it's not bulletproof because Mathematica's renaming by adding a $ is not bulletproof: With[{code = a$}, f[a_, a$_] := code;]; ? f. $\endgroup$
    – Michael E2
    Commented Aug 13, 2017 at 21:25
  • 2
    $\begingroup$ It could also be a moving target, should that mode of renaming change. Hard-wiring code based on internal variable rewriting specifics is dangerous. $\endgroup$ Commented Aug 16, 2017 at 22:41
  • $\begingroup$ @DanielLichtblau At least they're documented, and they have been that way since at least V2 (1991). Incompatible changes have happened before...one hopes always for the better. $\endgroup$
    – Michael E2
    Commented Aug 16, 2017 at 23:06
  • 1
    $\begingroup$ They are renamed, yes, but there is no guarantee as to the specifics of the renaming. Examples indicate a $ will be appended. But as they say in investment counseling, past behavior is not necessarily indicative of the future. $\endgroup$ Commented Aug 17, 2017 at 15:57
  • 1
    $\begingroup$ @DanielLichtblau I don't see how the code above depends on the specifics of renaming, except in what turns out to be an unintended clash (and the warning/description can be updated as needed, I suppose). I mentioned the $ to point out what to look for in the example, for those who did not realize that happened. Now if in a future change patterns are not renamed at all, then that would be a problem and the usefulness of this approach would have expired. But as I intimated in the answer, I don't think the usefulness of this sort of code-fixing is going to be that broad anyway. $\endgroup$
    – Michael E2
    Commented Aug 19, 2017 at 2:19
2
$\begingroup$

There is a pattern that is if not a direct answer to the question, then at least related to it:

foo = Module[{bar, baz},
   bar[a_] := Module[{b = 10}, b + a^1];
   baz[a_] := Module[{b = 20}, b + a^2];
   Function[{a}, bar[a] + baz[a]]
   ];

foo[4]

50

By returning a pure function, you get the scoping of the inner functions that you want and you get to use whatever input variable names you want for your main function.

$\endgroup$

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.