Model Master - User Guide

Introduction

Model Master (MM) is a programming language designed to make spreadsheets easier and safer to program. Instead of typing formulae directly into spreadsheet cells, you write a program using a text editor, and then submit this to the MM compiler. The compiler generates a data file which can be loaded into the spreadsheet.

The compiler can generate data files in various formats and for various common spreadsheets. The options include VisualBasic (for Excel), LotusScript (for Lotus 1-2-3), and SYLK (accepted by many spreadsheets, including Excel and some of the Linux spreadsheets).

Amongst the errors that MM helps you avoid are positioning errors (moving to the wrong cell in a worksheet), cell referencing errors (using the wrong column or row in a formula that refers to another cell), unit errors (such as adding money to interest rates, or inches to centimeters), and undefined or unused cell errors (using a cell that isn't set, or setting a cell that is never used). MM also allows programs to be documented with comments.

MM allows programs to be written in small units which can be compiled and tested separately, then stored in libraries for later use in bigger programs. This is done via a form of inheritance similar to that provided in object-oriented languages such as Java. MM also provides a novel form of object composition which allows groups of objects to be combined into systems. These systems can contain 'gaps' or parameters which can be filled in with a range of different objects. This is a very powerful form of code reuse, allowing complete systems to be defined, verified, and saved for later use.

MM has been demonstrated at economics conferences, and was featured in New Scientist (12th September 1998) and the Oxford University Gazette (4th February 1999). Technically speaking, MM is based on category theory and J.N.Ireson-Paine's System Limit Programming, a mechanisation of J.Goguen's sheaf semantics of objects. It also has a novel type system which allows types to contain units such as inches and pounds, and which enables the compiler to use dimensional analysis to catch unit errors. This is based on work by J.C.Cleaveland.

Language summary

The basic constituents of programs are objects. Objects have attributes, which (by default) are mapped onto columns in a worksheet. For example,

  object company = <profits income outgoings>
defines an object with three attributes. If this were the only object in a worksheet, the MM compiler would map them onto the first three columns, A B and C. In doing so, the compiler also generates header cells with labels to indicate which columns correspond to which attributes. (The angle brackets around attributes are meant to be reminiscent of ordered tuples in mathematics, and also of sorts in CafeOBJ.)

Objects will normally have formulae attached to them. For example

  object company = <profits income outgoings> 
                   where profits = income - outgoings
The compiler will map this onto three columns as before, and put into the "profits" column a formula which defines its value in terms of the "income" and "outgoings" columns. The generated spreadsheet would look like this:
     A         B           C
  1  "profits" "incomings" "outgoings"
  2  "company" "company"   "company"
  3  B3-C3

The layout of attributes in the generated worksheet can be changed. For example,

  object company = <profits income outgoings> down
will dispose the attributes vertically. If this were the only object in the worksheet, they would be placed in the first three rows.

Inheritance allows objects to be extended with new attributes or formulae. For example,

  object company_and_workforce = company * <workforce>
                                 where outgoings = workforce * 163
defines an object which contains four attributes: the three from "company" and an extra one "workforce". if we assume, rather arbitrarily and only for the sake of example, that the company's only outgoings are formed by multiplying the number of employees by 163. This is similar to inheritance in an object-oriented language.

Attributes can be given units. Saying

  /* We shall measure all money in £1000 units */
  unit £K
  object company = <profits   as £K
                    incomings as £K
                    outgoings as £K
                   >
                   where profits = incomings - outgoings

  unit person
  object company_and_workforce = company
                                 * <workforce : integer as person>
                                 where outgoings = workforce * 163(£K/person)
The compiler will check all attributes for the right dimensions, so it will not, for example, allow you to accidentally use money where you wanted a number of people.

Here is another example:

  unit m                            // Basic unit of length.
  unit sec                          // Basic unit of time.
  unit kg                           // Basic unit of mass.
  unit acceleration = cm/sec^2
  unit newton = kg * acceleration   // Basic unit of force.
  constant dud = 1newton + 2kg      // Gives an error.
The last line uses mismatched units, and the compiler generates an error:
  Error: Left-hand side of operator + has units kg*m*sec^-2, but
         right-hand side has units kg.

Objects can be composed into systems. One could define

  object market = <demand_per_consumer number_of_consumers>
and compose it with "company":
  object economy = market * company 
                   where income = demand_per_consumer * number_of_consumers
Here, attribute "income" comes from "company": the other two come from "market".

Parameterised objects allow 'frameworks' such as "economy" to be defined with 'gaps' or parameters that can later be filled in with a variety of different objects. For example

  framework economy ( market, company ) =
            market * company 
            where income = demand_per_consumer * number_of_consumers
Assuming then that we have defined various companies and markets:
  object compaq = company where ...
  object dell = company where ...
  object ibm = company where ...
  object desktop_market = market where ...
  object laptop_market = market where ...
we can use the "economy" framework to combine them:
  object compaq_with_desktops = economy( compaq, desktop_market )
  object ibm_with_laptops = economy( ibm, laptop_market )

Objects can be extended over time, allowing simulations to be built that calculate future values from past at different timepoints. For example,

  base time 1990 .. 2009
  object company = <profits[time] as £ income[time] as £ outgoings[time] as £> 
                   where income[1990] = 5000£ &
                         (t>1990) income[t] = 99% * income[t-1] &
                         outgoings[1990] = 3000£ &
                         (t>1990) outgoings[t] = 102% * outgoings[t-1] &
                         profit = income - outgoings
will model a company whose costs are increasing faster than its income. In this example, the MM compiler would assign the attributes to columns A B and C as before, and the timepoints to successive rows. Notice, incidentally, that MM permits numbers to be written as percentages where this is more convenient.

Attributes that range over time can be combined with those that don't. For example

  base time 1990 .. 2009
  object company = <profits[time] as £ income[time] as £ outgoings[time] as £ 
                    total_income as £
                   > 
                   where income[1990] = 5000 £&
                         (t>1990) income[t] = income[t-1]*1.1 &
                         outgoings[1990] = 3000£ &
                         (t>1990) outgoings[t] = outgoings[t-1]*1.2 &
                         profit = income - outgoings &
                         total_income = sum(income)
Assuming one is compiling for a spreadsheet such as Excel which has a "sum" function for summing ranges, this would simulate the company's history and then calculate the total income over that time.

There are several other features which, while not essential, increase the ease of writing programs. You have already seen onstant definitions, which allow useful constants to be defined and used inside formulae:

  constant year_1_sales = 5460
  object company = <profits[time] income[time] outgoings[time]>
                   where income[1990] = year_1_sales

Constants can be defined in terms of other constants:

  constant monthly_year_1_sales = year_1_sales / 12
The compiler will report the values of all such constants at the end of compilation, allowing you to check that the defining expression was written correctly.

Functions allow common formulas to be defined and reused:

  function as_euros( pounds ) = pounds/0.69
  object company = <profits income outgoings profits_as_euros>
                   where profits_as_euros = as_euros(profits)
MM expands these in-line before writing out the generated spreadsheet code.

Functions can be used with units:

  unit £
  unit euro
  function as_euros( pounds as £ ) as euro = pounds / 0.69(euro/£) 
  object company = <profits as £ income as £ outgoings as £ profits_as_euros as euro>
                   where profits_as_euros = as_euros(profits)
Although this makes programs more verbose, the redundancy enforced by the unit definitions provides the compiler with the information it needs to detect common errors.

It is sometimes useful to pass unit 'variables' to functions. Thus the definition

  mean( a as ?U, b as ?U ) as ?U = (a + b)/2
stipulates that the function "mean" can be applied only to quantities which share the same units. The definition
  product( a as ?U1, b as ?U2 ) as ?U1*?U2 = a*b
allows the quantities "a" and "b" to have any units, but stipulates that the result's units will be the product of those of "a" and "b" parameters.