Wednesday, May 8, 2013

How to Write a Program in Mathematica: Basic Programming Principles

If you look at the code of the creators of Mathematica's programming language, Stephen Wolfram (e.g. Ch. 5., Cellular Automata, of A New Kind of Science) and Roman Maeder (see his superb books), you will see simplicity and organization. They are master programmers. So if the method of Wolfram and Maeder is to keep things simple and thoughtfully organize their programs, let us follow their example. With that in mind, here are some principles to create any program, step by step.
  1. Write and debug a series of one liners to execute your program. In other words, start by just doing what you want to do "by hand," one line at a time. Then when you're done and it works, assemble the series of one liners into a program.
  2. If the input or output of any step is at all complicated, first solve a toy version. If your entire program is complicated, first solve a toy version of it. Then try each step on the actual problem.
  3. A Functional programming principle is write functions few inputs and outputs as possible–ideally just one output. If you have only one input and one output per function, each is easy to write and debug. Sometimes you have to carry related parameters together. In the function under construction, it's still advisable to have as few parameters affect a single output as possible. 
  4. To assemble the program, first create the shell, i.e. just a Module (or With or Block) with the name of the program function and those of its arguments. Then put each one-liner in the program one at a time, on a separate line, and run the program. 
  5. A Print statement is the simplest form of debugging. So Return or Print the output of any command that you need to see. If you have a long set of arguments or if any are complex, start by just returning the arguments to see if you get what you expected. Leave a Print statement in the program if you need to monitor each output, or if not, delete it. If you think you might need it, comment it out (*Print@variable1."*).
  6. And in general, per the 2000-year old army saying, Clean As You Go (which derives from the law of increasing entropy). Precede your function with Clear@function. Delete things you don't need. Re-organize for clarity. 
  7. There is no shame in clarity. Someone else may need to read your code. And you may be that someone else. As an acquaintance of mine, Gary Drescher, once said, "The 'I' of the future is only loosely coupled to the 'I' of the present." You, 6 months from now, will not have an easy time of deciphering your own code unless you go for clarity. 
  • Use long variable names (like master programmers Hoare and Trott). 
  • Format a cell for text (Alt+7) above your program and comment freely. 
  • Use inline comments (* comment here *) judiciously alongside lines of your code. Too many comments break up the readability.
  • Use the combination of Prefix and Postfix with pure Function that I advocate (here in this blog), which simplifies your code and gives you total control over which functions to emphasize and which to subordinate. 
  1. As you put each one liner in the program, be sure to include any variable names in the local variable List in the Module. If variable names are not listed Mathematica will flag them in a color (blue in my version). You can save space and simplify things by putting assignment functions in the local variable List as long as they're not dependent on a variable that came earlier in the List. If they are dependent, the assignment has to be put in the program body. Put the local variable names in the List in the order that they appear in your program.
  1. Remember the fundamental data structure principles:
  • Write and debug a special function, called a Selector, to extract each subset of what you need from your data. Only use Selectors to touch the data; never touch it directly with the program. When the structure of your data changes, re-write the Selector.
  • Write and debug a special function, called a Constructor, to build each construct from the data. Only use Constructors to build the constructs. Leave the original data alone.
  • If need be, write and debug a special function to export or format the output of your program. Only use the special function to export or format your output.
  • Two other arch-typical data structure functions are constants, that always return the same thing either with no input or no matter what the input, and predicates, that take an input and return True or False.
  1. Whether to define a function in your program, or outside of it and then call it in the program, is a judgment call. A simple but valid answer is if you plan on using the function outside of your program, define it outside to facilitate that usage. But an equally important reason for defining a function outside of your program is to simplify it.
  2. With a medium to large program, consider just throwing out the original version if you see a better approach. At an IEEE seminar on object-oriented programming, the speaker said often you just need to start coding for a bit to get a feel for what you want to do before you can envision a better architectural solution.
Obviously I need to provide examples to illustrate all these principles and will do so.

1 comment:

  1. Great post. I look forward to your examples of Selectors & Constructors.

    I read published notes on your Functional Procedural Fusion dialect after you gave your talk and the effect on me can only be described as intense elation!

    Thank you also for providing me with appropriate vocabulary to describe those closing ]'s. "Distal",(in one of your posts); that made my day, I can handle the proximals, but...

    ReplyDelete