The Foo language

From Tonto Wiki
Jump to: navigation, search
Little bunny foo-foo

Foo is a powerful object-oriented preprocessor language which translates into Fortran95.

Foo stands for "object oriented Fortran" in reverse.

Foo introduces `objects' and `methods' with messages sent via a `dot' notation.

Foo also introduces code inheritance by a simple text substitution model.

Foo's inheritance mechanism works well for template-inheritance or genericity.

With Foo, there is no need to worry about `is a' or `has a' type relationships: if you can use code somewhere else with minimal change, Foo allows you to just do it. Whole `virtual modules' may be constructed by this mechanism.

Foo also allows a simple type of call-back or closure which is very convenient for optimizers and minimizers.

Contents

How to learn Foo

Foo and Fortran are (despite first observation) quite similar.

The basic types of variables, arrays, and array notation are the same.

Therefore, this wiki describes the Foo language in relation to Fortran.

The wiki describes what is done by the translator to convert Foo into Fortran.

For optimum appreciation of this wiki you should be familiar with Fortran95 before looking at Foo programming.

The Australian NCI national facility has a brief and excellent introduction to basic modern Fortran.

Notes for an advanced modern Fortran course are also available.

There are plenty of other free Fortran resources on the web.

Those with exposure to object oriented languages such as Python, Eiffel, and Sather will recognize their influenced in the design of Foo.

You can also learn a lot by browsing the source code at sourceforge using or by Using vim and ctags with Tonto.

Finally, it would be advisable to know something about how Tonto is set up and compiled before reading this.

Foo programs, modules, types and objects

Foo programs

A Foo program is always written in a file whose name is the name of a particular module with "run_" appended: for example "run_atom.foo".

The name of the program must match the name of the file e.g. program "run_ATOM" must appear in the file run_atom.foo".

A Foo program contains only calls to procedures defined in Foo modules: routines are never defined in a program file.

The way Foo routines are called is explained later.

Foo modules, procedures, types

In Foo, the main grouping of code is called a module. Except for the main program, all code belongs to a module.

The module corresponds to the idea of a "class" in object oriented languages. It corresponds to the idea of an "abstract data type".

In Foo, all procedures -- subroutines or functions -- must be written as part of a module. Furthermore, the name of the module must match the name of the file in which it appears, and it must match the name of a data type defined in the "types.foo" file. Only one module definition may appear in a given file.

The only exception to the above rule is for the built-in scalar types INT, BIN, STR, REAL, and CPX, which are not declared in the "types.foo" file, but nevertheless have associated modules. The different kinds of types in Tonto are discussed later.

For example, there is an ATOM module defined in the file "atom.foo", and it corresponds to an ATOM type defined in the "types.foo" file.

The procedures in the ATOM module all concern manipulations of the ATOM type.

There is a hierarchy to each module: more complicated modules rely on the simpler ones. A procedure in a simpler module cannot make use of a procedure in a more complicated module. This is the same behaviour we have in Fortran.

Relation between a Foo module and a Fortran module

A Foo module is the same as a Fortran module. There is a contains statement, before which module data and module interfaces can be defined, while after the contains statement appear the definitions of the procedures.

Foo submodules

A module name may also have a submodule part, comprised of a single dot followed by another name.

For example in the submodules with names ATOM.CLASSICAL and ATOM.QUANTUM the submodule parts have names CLASSICAL and QUANTUM.

In addition, the submodules must be defined in separate files with names "atom.classical.foo" and "atom.quantum.foo".

However, each of the submodules are still associated with the ATOM type. There is no separate ATOM.CLASSICAL or ATOM.QUANTUM type.

Submodules are intended to be used as a way of breaking up large modules into smaller manageable parts. It is intended that the different submodules contain code which is logically related. The special features of submodules are discussed in more detail later.

Foo module interfaces

Interfaces may also be declared just as in Fortran, except that in the Foo language one uses only the word "interface" rather than "module interface" as in Fortran. Also, the interface is may be ended by the "end" keyword rather than "end interface".

Foo types: where they are defined, and the four kinds

Foo types are defined in the "types.foo" file.

There are four kinds of types:

  • scalar built-in,
  • array built-in,
  • non-built-in or derived,
  • and array non-built-in or array derived.

The scalar built in types are the integers (INT), the double precision real numbers (REAL), the complex numbers (CPX), strings (STR) and logical variables (BIN). These have the usual Fortran equivalents.

The array built-in types include vectors of integers, VEC{INT}, matrices of double precision numbers, MAT{REAL}, and multidemensional arrays such as 5 dimensional complex number arrays, MAT5{CPX}.

The non-built-in types are aggregations of the basic types --- a standard Fortran derived type e.g an ATOM type. Arrays of non-built-in types are as you might expect e.g. a vector of atoms, VEC{ATOM}.

The built-in types do not need to be declared in the "types.foo" file, but the non-built-in types must be declared, e.g. for an ATOM type

  type ATOM
   
  label :: STR  DEFAULT("?")
  ! The label for the atom (not necessarily unique)
  
  atomic_number :: INT
  ! The atomic number
  
  end

and for an array of atoms, we must have the dummy declaration:

  array type VEC{ATOM}
  
     ! A 1-D array of ATOM's
  
  end

An indentation of 3 characters is mandatory for type declaration and the components of the type should not be further indented.

Foo objects

A Foo object is simply a variable.

By the above rules, the variable must be of a type associated with a module, and we say that the variable is an "object" of this type.

Thus the ATOM object is a variable whose type is

  • Defined in the "types.foo" file.
  • Which has an associated ATOM module, defined in the "atom.foo" file

Note that, by convention, the file names are always in lower case, whereas the object names are in upper case.

A Foo object name may also contain curly braces and commas, to indicate array or type parameterisation, as discussed later on.

Similarities and differences between Foo and Fortran

Foo identifiers or tokens: mostly identical to Fortran

Foo tokens are variable names, control-flow or looping keywords, etc. In Foo, these are identical to Fortran, except that:

  • The name of a Foo module must be in upper case. It may also contain curly braces and commas, indicating an array or parameterised type.
  • When the name of a Foo module is translated into Fortran, the string "_MODULE" is appended to it. Hence in order to generate standard Fortran 95 no variable name may end in the string "_MODULE", and the name of a Foo module (including any submodule parts -- see below) must not exceed 24 characters.
  • When the name of a Foo procedure is translated into Fortran, an underscore "_", or a numeric underscore, e.g. "_1", is appended to it. Hence in order to generate standard Fortran 95 a procedure name must be less than 29 characters and no variable name may end in an underscore or a numerical underscore.
  • When the name of a Foo derived type is translated into Fortran, the string "_TYPE" is appended to it. Hence no token may end in the string "_TYPE". In addition, Foo type names must be the same as module names.

Foo variable declarations

The declaration of the variable types in the module data part is backwards relative to Fortran. Examples are discussed later.

Scoping keywords, control flow-keywords

All the scoping and control-flow keywords in Foo are the same as in Fortran.

Scoping keywords such as "program", "module", "if", or "do" must appear on a line on their own i.e. the semicolon construct which allows multiple statement per line is not allowed on the same line as a scoping keyword.

The end keyword and three space indentation

In general, in Foo, one may use "end" to mean "end module", "end interface", "end subprocedure", and so on. The preprocessor will work out which kind of "end" is meant by the enclosing scope.

In general, in Foo, one may use "end" to mean "end module", Because the "end" keyword does not immediately identify which scoping unit is being ended, it is really quite important to indent your code so that appropriate scoping blocks can be easily indentified. The standard indentation is three spaces. Indentation of three spaces for procedures in modules in mandatory, but apart from this, indentation is not enforced.

Intrinsic functions

Foo intrinsic functions are the same as those in Fortran.

Foo procedures

Subroutines and functions

A Foo procedure starts simply with the name of the procedure indented by three spaces after a module "contains" statement.

When declaring a Foo proceudre, the Fortran keywords "subroutine" or "function" are unnecessary and, in fact, forbidden, since in Foo, functions are distinguished from subroutines by the fact that they are required to use the result syntax of Fortran, e.g.

  bessel_function result (res)
  ! Return the bessel function for "self"
  ...
  end

After the procedure name come the dummy argumentss (if any), local variable declarations, and procedure body, just as in Fortran.

Procedure attributes

Unlike Fortran, following the procedure name may appear an optional triple colon, ":::", and a comma separated procedure attribute specifier.

Allowed specifiers include:

  • "recursive", for recursive procedure
  • "private", for procedures which are not callable from other module
  • "public", for procedures which appear as function arguments or which make use of assumed size arrays.
  • "pure" -- same meaning as in Fortran
  • "elemental" -- same meaning as in Fortran
  • "leaky", for procedures which contain an intentional memory leak
  • "selfless", for procedures which lack the "self" variable. See below.
  • "inlined_by_Foo", for procedures which are inlined by the preprocessor
  • "template", for template procedures
  • "get_from(MODULE, A=>B, C=>D)", for procedures which are text copied from another module MODULE, with substitutions as described. Note that the space after the coma within the brackets ( ) is mandatory.

The last two procedure attributes implement a flexible form of inheritance used in the Tonto system, and are discussed later.

The "inlined_by_foo" specifier is reserved for thos procedures which are explicitly inlined by the preprocessor to avoid procedure call overheads.


Implied "self" argument

Except for those procedures declared with the "selfless" attribute, every Foo procedure automatically has added a first argument with name "self". The "self" dummy argument has the same type as the type of the enclosing module. The added dummy argument declaration is implicit in the Foo code and only appears in the generated Fortran. Nevertheless, the "self" argument can be used like any other procedure dummy argument. However, because of the use of self dot notation (see below) the "self" dummy argument rarely appears in Foo code (it does sometimes explicitly appear in array-type modules).

Overloaded procedures

You may declare procedures with the same name, provided they differ in the types and number of the dummy arguments, and provided that all the procedures which have the same name are either subroutines or functions. The preprocessor will automatically generate the required generic interfaces.

In fact, the generic interface for the generated Fortran code has the same name as the original procedure with an underscore appended; the specific procedures with identical names are appended with "_0", "_1" ... etc in order to distinguish them.

Take care in naming "selfless" procedures

You should be very careful that "selfless" procedures have distinct names, since they cannot always be distinguished by argument list.

"selfless" procedures are typically passed as arguments to functions e.g. optimisers or plotters.

Variable declarations in Foo

Local variable declaration: capitalisation and pointers

The way to declare variables in Foo is backwards relative to Fortran, and a double colon must always be used, e.g.

  a :: INT
  v :: VEC{INT}

This form of declaration is more natural in english, e.g. "a is an integer", or "v is a vector of integers". The double colon is translated as "is a". Further, the types, such as INT and VEC{INT}, are always in capital letters.

Variable declarations for routine dummy arguments

If strings or arrays of string are procedure dummy arguments, e.g. "s" and "t" in

  string_procedure(s,t)
     s :: STR
     t :: VEC{STR}
     ...
  end

then their length is determined from the length of the actual arguments. In most cases, this is what you want. For non-default length string, use

  string_procedure(s,t)
     s :: STR(len=3)
     t :: VEC{STR}(len=3)
     ...
  end

Likewise, for arrays which are dummy arguments e.g. "a" in

  vector_procedure(a)
     a :: VEC{INT}
    ...
  end

the length of "a" is taken from the actual argument. If you want an explicit shape array, then use

  vector_procedure(a)
     a :: VEC{INT}(3)
     ...
  end

Note that procedures with the same name which differ only in whether arguments are assumed size or explicit shape are not distinguishable names according to the underlying Fortran language. If you need an explicit string array with a particular length string size, use

  fixed_length_str_vector(s)
     s :: VEC{STR}(len=1,4)
     ...
  end

Variable intents, and optional arguments

The intended use of a variable may be specified by using the IN, OUT, or INOUT type specifiers, e.g.

  fixed_length_str_vector(a)
     s :: VEC{INT}(4), IN
     ...
  end

is an dummy argument which should never be set, just as in Fortran.

Optional arguments are specified in the same was as Fortran,

  fixed_length_str_vector(a)
     s :: VEC{INT}(4), optional
     ...
  end

Variable declaration with default initialisation

Variables may be declared with default initialisation using the DEFAULT() macro, as follows:

     i :: INT  DEFAULT(3)
     t :: VEC{STR}(len=2,2)  DEFAULT(["ok","hi"])

Note that the string array constructor may use square brackets [ ... ] instead of the usual Fortran construct (/ ... /). (Square brackets also have another meaning in a different context, encapsulated array elements, as discussed later).

A default null value for a pointer variable is declared as follows:

     p :: VEC{INT}*  DEFAULT_NULL

Type component declaration: private and readonly attributes

Type components are declared just as for local variables, except that there may be additional attributes, "private" and "readonly", e.g.

  type ATOM
  
  label :: STR  DEFAULT("?")
  ! The label for the atom (not necessarily unique)
  
  atomic_number :: INT
  ! The atomic number
  
  chemical_symbol :: STR, private
  
  long_chemical_name :: STR, readonly
  
  end

The "private" components may only be referred to within the module which has the same type name as the type definition. On the other hand, "readonly" components may be referred to in other modules, but their value may not be changed. Type components which have no other attribute may be refered to and changed by other modules, but this is in general bad programming practice.

Foo control flow keywords: essentially the same as Fortran

Foo control flow keywords are essentially the same as in Fortran. Thus we have "do" loops, "case" and "if" statements.


Parallel programming in Foo

The "parallel do" loop construct

An exception is the "parallel do" loop construct. When the program is compiled using MPI with the option -DMPI (see the document COMPILE_OPTIONS for a description of other compile-time options) then a "parellel do" loop executes in parallel over all the processors. Within the loop there may be BROADCAST operations which broadcast selected variables to all members of the processor. At the end of the parallel loop there is usually usually a PARALLEL_SUM operation, to make sure all the processors have the same variable values.

Foo expressions, dot notation, and procedure calls

Foo expressions: essentially the same as Fortran

Expressions in the Foo language are essentially identical to Fortran. This includes assigments, array slice assignment, and unary operators.

Minor differences include

  • The mandatory use of AND, OR, NOT, NEQV and EQV logical binary operators. This is to avoid confusing dots in, for example, the .and. operator (see below for dot notation)
  • The optional use of square bracket array constructors [ ... ] instead of the corresponding Fortran construct (/ ... /)

More important differences occur with dot notation and explicit function calls.

Foo expressions: object components and dot notation

In Foo, references to type components and procedure calls are both implemented via dot notation.

Consider the following code:

  name :: STR
  a :: ATOM
  
  name = a.chemical_symbol

Here, "chemical_symbol" is an object component of the variable "a", which is of type ATOM. "chemical_symbol" (which is obviously of STR type) may refer to a type component of the ATOM object "a", or it may refer to a procedure call "chemical_symbol(a)" which is defined in the ATOM module. Precisely which of these possibilities the object component "chemical_symbol" corresponds to -- type component or procedure component -- depends on whether there was a type component "chemical_symbol" declared as part of the ATOM type: if there is such a type component defined that is not private, then the above code refers to that type component, otherwise not. In some of the above examples, this was the case, but in the actual Tonto system, it is not so.

Clearly, the use of dot notation hides whether an object component name is implemented as a procedure or type component. In this way, code implementation can be hidden from the user of the code, leading to much more robust implementations.

Multiple dot notation

Multiple dot notation is supported only if the referred-to object component is a type component. Thus, in the following code

  name :: STR
  a :: ATOM
  
  name = a.chemical_symbol.first_two_characters.to_upper_case

the application of "first_two_characters" via dot notation would be allowed if "chemical_symbol" were a component of "a", but forbidden otherwise. Likewise, "to_upper_case", is only allowed if "first_two_characters" was a type component of the object "a.chemical_symbol". Whether such operations are allowed or disallowed will be made clear when the code is compiled into Fortran, at which point the preprocessor will yield an error.

If you want to avoid any preprocessor problems related to whether an object component is a type or procedure, break the multiple dots into temporaries as such:

  symbol,first_two,name :: STR
  a :: ATOM
  
  symbol    = a.chemical_symbol
  first_two = symbol.first_two_characters
  name      = first_two.to_upper_case

In most cases you will not have to worry about this problem, since you will be well aware of which object components are type compoenents are which are procedure calls. Problems with multiple dot notation usually only arise when re-implementing the internal workings of a module. A future implementation fo the Foo preprocessor may eliminate these problems entirely.

Dot notation for Foo procedure calls

Dot notation can also be used to call procedures, as follows:

  a :: ATOM
  a.put

Here, the procedure "put" is called on the variable "a" of ATOM type. In the Tonto system, "put" usually has the meaning that output is generated to the "stdout" file. There are many other conventional names for proceudres used in Tonto, as discussed later.

self dot notation: cooking recipes

Subroutine calls of the type:

  self.do_something

are very common when using foo. They may be abbreviated simply to

  .do_something

Any line that begins with a single dot, as above, is interpreted as a subroutine component of the "self" dummy variable.

This notation has the advantage that a typical procedure may often be implemented something like a bullet-point cooking recipe, thus:

  .do_something_with(b)
  .do_somthing_else_with(c)
  .make_the_result(a,b,c)
  .put(a)


Complications with dot notation

A complication with using dot notation is that logical operators such as .and. or .or. are confusing for the preprocessor, and are hence forbidden. Use the AND or OR macros.

Another complication occurs with multi-line expressions like this:

 a = .function_of(b) + &
     .special_function(c)

The problem is that ".special_function(c)" will be interpreted by the (line-based) foo preprocessor as a subroutine call instead of the intended function call. This problem can be fixed by shifting the plus sign

 a = .function_of(b) &
   + .special_function(c)

Explicit procedure calls: generic calls

As well as calling routines by dot notation, we may call procedures explicitly by prepending the module name of the desired procedure with a colon. For example,

 a = ATOM:function_of(self,b) &
   + ATOM:special_function(self,c)

In these examples, the functions "function_of" and "special_function" are those found in the ATOM module - and note that the "self" argument must now be explicitly included. Likewise, for routine calls we may have

 ATOM:do_something(self)

where the subroutine "do_something" is the one found in the ATOM module.

To reiterate: with explicit procedure calls, the normally implicit "self" dummy argument must explicitly appear. Of course, any actual argument may substitute for the dummy argument "self" provided the actual argument has the right type, e.g.

 ATOM:do_something(b)

Procedures called with a single colon are generic: there may be other routines which have the same name, but the specific procedure called is selected from the types of the arguments used for the routine.

Explicit procedure calls: non-generic calls

There are also non-generic explicit procedures, which are obtained by prepending the module name of the desired procedure with a double colon e.g.

 a = ATOM::function_of(self,b) &
   + ATOM::special_function(self,c)

As the name suggests, such function calls are non-generic: there must exist functions with exactly the names "function_of" and "special_function" within the ATOM module, and these functions must not be overloaded, and these functions must be declared with the "public" procedure attribute. Likwise for explicit subroutine calls, e.g.

 ATOM::do_something(b)

When are explicit procedure calls needed?

Explicit non-generic preocedure calls are required in two contexts:

  • When passing procedures as arguments. Fortran does not allow generic proceudres to be passed, so you must use the double colon notation in this case, e.g.
     minimum = VEC{REAL}::optimise(REAL::my_function)
  

Here the procedure "my_function" from the REAL module must be explicit and non-generic. The call to "optimise" in module VEC{REAL} need not be, though in practice, many compilers may have problems with such code if the call is generic. We mention in passing that the procedure "my_function" will typically have the "selfless" proceudre attribute (see the section on closures, below)

  • When calling routines which expect assumed size arrays. Generic Fortran interfaces do not distinguish between assumed size and assumed shape arrays: to access assumed size procedures you must use the double colon notation. Typically, you need assumed size arrays when writing efficient code which avoids multidimensional array bounds.

Inheritance

The get_from attribute and text substitutions

Foo implements inheritance using a very simple text-based inclusion mechanism.

This is simple idea let's you avoid writing and maintaining unnecessary code.

In Foo, an inherited procedure (the so-called parent procedure) is taken from the specified module (the parent module) and textually copied into a procedure (the child procedure) in the module you want (the child module). There may also be some additional text substitutions that you can specify. In most cases these are substitutions pertaining to the types of procedure dummy arguments.

Let's look at an example.

In the MAT{REAL} module there may be the following matrix multiplaction code:

  to_product_of(a,b)
  ! Multiply two matrices
     a,b :: MAT{REAL}
     self = matmul(a,b)
  end

The "self" variable is implicit and has the type MAT{REAL}. In the MAT{INT} module we might write

  to_product_of(a,b)
  ! Multiply two matrices
     a,b :: MAT{INT}
     self = matmul(a,b)
  end

The procedures are nearly the same. The only difference is in the declaration of the types of the dummy arguments "a" and "b".

We can avoid writing the second routine in MAT{INT} by using inheritance, as follows

  to_product_of(a,b) ::: get_from(MAT{REAL})
  ! Multiply two matrices
  end

This is what happens:

  • the code from the procedure "to_product_of" in module MAT{REAL} is textually copied to the procedure of the same name in the MAT{INT} module.
  • Further, every occurence of MAT{REAL} is replaced with MAT{INT}, and every occurence of REAL is replaced with INT. (This is a depth-first, inside-out replacement of types within the curly braces).

Although in this case it is not necessary, we could also explicitly write this procedure as:

  to_product_of(a,b) ::: get_from(MAT{REAL}, MAT{REAL}=>MAT{INT}, REAL=>INT)
  ! Multiply two matrices
  end

The text substitutions indicated by the "=>" arrows apply throughout the textually inherited code. Such substitutions may affect not only the types of variables (such as we have done here for "a" and "b") but also for procedure calls and variables. However :

  • there must be a space after each comma in the "get_from" attribute.
  • there may be no spaces within the two entities before and after the "=>".

Note that, in order to find the matching routine in parent module MAT{REAL} the name of the procedure must match and also any comments or variable declarations which occur in the child module procedure.

Thus both the procedure name, the procedure arguments, and the textual comments, including any blank space which does not lie at the end of the line form the target of the textual inheritance. For example, the following routine in the parent module would not be an inheritance match :

  to_product_of(a,b)
  ! Multiply two matrices avoiding "matmul"
     a,b :: MAT{INT}
     i,j,k :: INT
     do i = 1,a.dim1
     do j = 1,b.dim2
        do k = 1,a.dim2
           self(i,j) = self(i,j) + a(i,k)*b(k,j)
        end
     end
     end
  end

Although it might seem that little coding has been saved in this example, in other cases the code saving might be more significant. More importantly, bugs which are fixed in the parent are automatically fixed in all the inherited code used by the children.

Template procedures

Template procedures are those declared with the "template" procedure attribute.

Template procedures are never compiled into Fortran, but are used purely for the purposes of inheritance. Thus, template procedures usually include one or more dummy types or text sequences, by convention always in upper case, which are substituted using the text substitution arrow, "=>", within a get_from attribute of the inheriting procedure.

Template routines may be inherited from the same module or from a different module.

It is usually good programming practice to make a template procedure if it is being inherited by another module. Without the template attribute it may not be clear that the code in one procedure is being used in another module. On the other hand, heavy use of the get_from directive can slow down code translation from Foo into Fortran.

Named get_from directive

The name of the parent procedure need not match the name of the child procedure when using a named get_from directive, e.g.

  to_product_of(a,b) ::: get_from(MAT{REAL}:matrix_multiply)
  ! Multiply two matrices
  end

Here the procedure code is copied from procedure "matrix_multiply" in the MAT{REAL} module. Although the name of the procedure may differ, everything else, such as the comments and and variable declarations, must be the same. Text substitution can also occur with name get_from inheritance.

Virtual modules

Sometimes there may be so many routines which are inherited, and they are all largely related, that it becomes practical to collect them all together into a virtual module.

A virtual module is identical to a normal module except that it is declared with the words "virtual module". The proceudres within a virtual module are never compiled directly into Fortran. Instead, every routine in the virtual module is a template which is inherited by other modules.

Examples of virtual moduls in the Tonto library are OBJECT (which provides generic routines available for all objects), VEC{OBJECT} (generic routines for lists or "vectors" of objects), MAT{INTRINSIC} (matrices of intrinsic type), and HASH{VEC{KEY},VEC{VAL}} (associative arrays with keys of type KEY and values of type VAL).

Procedures as dummy arguments: declaration

Declaring a procedure dummy argument just as you would a module interface: Use the "interface" keyword, and declare all dummy arguments as you would a normal procedure. In the example below (and despite its name) "func" is a subroutine procedure argument:

  cubify(func) ::: leaky
  ! Generate the isosurface using the marching cube algorithm.
     interface
        func(values,pt) 
           values :: VEC{REAL}, OUT
           pt :: MAT{REAL}, IN
        end
     end
     ...
  end

Procedures as dummy arguments: effective use

In Foo, the first dummy argument of almost all procedures is "self", and "self" argument has the type of the module.

This is a serious problem for procedure dummy arguments.

It is a problem because, if we want to write a procedure which accepts another dummy procedure as an argument, then we would have to account for the many different possible dummy procedure first argument types -- for example, those with "self" declared as an ATOM, those with "self" declared as a MOLECULE, and so on.

One way to solve this problem would be to use a template procedure with the type of the first argument in the dummy procedure as a variable, changed by text substitution in a get_from directive. At least, one would not have to write very much new code to cope with all the dummy procedures which differ in the first argument type --- but we would still have to write new code.

Another more elegant solution is to declare the actual procedure to be passed as an argument to have the "selfless" attribute, and then store the value of the "self" variable in a temporary module variable which is restored when the dummy procedure is called.

For example, suppose in some procedure in the MONEY module we have the following code:

     saved_self => self ! saved_self is a module variable
     
     minimum = REAL::optimise(MONEY::my_taxes)

In the procedure "my_taxes", also in the MONEY module, we would then have

  my_taxes(salary) result (res) ::: selfless, public
  ! Return the taxes for a given "salary".
     salary :: REAL, IN
     res    :: REAL
     self   :: MONEY*
     self => saved_self ! Restore self from saved_self
     ...
  end

Thus just before "my_taxes" is minimised, "self" is saved in "saved_self", a module variable. When "my_taxes" is called, "self" is restored from "saved_self", as if it were an argument to "my_taxes", but without the burden of it actually appearing in the dummy argument list. In this way, the interface for the function "optimise" in the REAL module does not need to change, e.g. if MONEY::my_taxes was replaced by another routine, e.g. COMPANY::total_taxes. The optimise routine never changes and it always remains as:

  optimise(func) result (real}
  ! Return the minimum value of the function "func"
     interface
        func(val) result (res)
           val :: REAL, IN
           res :: REAL
        end
     end
     ...
  end