Design‎ > ‎

Classes and Modules

Class variables and methods

It is often useful to have class variables and methods.  These are for
functionality that is common to all objects created from the class, but not
related to one specific object.  For example: A method to create a Date object
by parsing a String such as "Feb 12 2009".

In Java these are defined with "static".  These do not have access to object
variables and methods, since they are not associated with any specific object.
Thus they actually live in a completely different scope.  Mixing static
(class) and non-static (object) items causes confusion.  When instantiating an
object from a class all the static items are ignored.  It messes up the
meaning of the items in a class.  Many Java teachers need to spend time
on explaining this to students.

One could say that class variables and methods make the class itself an
object.  Would it be a solution to avoid classes and only use objects?  This
means one would make a new object by copying the declared object.  Every
object then has a reference to the class methods.  That only makes things
more complicated, especially for class variables.

Alternative: Instantiate a class.  That also allows having multiple of them.
We should call it something else then.  ObjectClass?  ClassObject?  This
introduces a new mechanism, while it's not really needed.  One can always
put everything in an object.  So let's not do this.

Observation: A class is to make objects from.  It cannot contain global
variables (static in Java).  The class variables and methods do have a
relation with the objects, so they need to be defined together somehow.
Thus let's keep the class restricted to variables and methods for the object,
and put the rest in the same scope but outside of the class.

1. Put methods and variables in a namespace.

   CLASS Date
      int time
      string toString()
         ...
      }
   }
   NAMESPACE DateFactory
      Date dateFromString(string entry)
         ...
      }
   }
   ...
   Date d = DateFactory.dateFromString(line)

        + Clear separation.
         - Need two names, even though the two things are bound very tight.

1a. Like 1, but give the namespace the same name as the class.

        + Clear separation.
         - When using Date.abc it's not clear what Date refers to.

2. Use one name for both class and namespace.  Keep all items together in
    one module.

           MODULE Date
              FUNC dateFromString(string entry): Date
                 ...
              }
              CLASS Date
                 int time
                 FUNC toString(): string
                    ...
                 }
              }
              ...
           }
           ...
           Date d = Date.dateFromString(line)

        + Somewhat clear separation.
        + Need only one name.
        + Less confusing than Java (mixing static and non-static methods)
        + No need for a class that can't be instantiated.
        o Having one name for class and module may cause a bit of confusion.
           The compiler will be able to give good error messages though.
        - When extending the class, what happens with the module members?
        - In a large module it can be difficult to find the class with the
          same name.

3.  Like 2, but put MODULE and CLASS at the same level.  Classes defined in
     the MODULE are local to that module scope.
     The compiler checks that an imported file only defines one identifier at
     the toplevel, it can be a MODULE, a CLASS or both.

           MODULE Date
              FUNC dateFromString(string entry): Date
                 ...
              }
              CLASS Month
                 ...
              }
           }

           CLASS Date
              int time
              Month month
              FUNC toString(): string
              ...

           }

        + It's easier to locate the Date class, it can't be halfway the module.
         - The scope of a local class, "Month" in the example, is messy.
         - When extending the class, what happens with the module members?

4. Like 2, but require that the class is the first item in the module.

           MODULE Date
              CLASS Date
                 int time
                 FUNC toString(): string
                    ...
                 }
              }
              FUNC dateFromString(string entry): Date
                 ...
              }

              ...
           }
           ...
           Date d = Date.dateFromString(line)

        + All the advantages of 2.
        o Having one name for class and module may cause a bit of confusion.
           The compiler will be able to give good error messages though.
        + In a large module it is immediately clear whether a module has a
           class with the same name.
         - When extending the class, what happens with the module members?

5. Turn it around, use the class as the highest level.  Put members shared between
    all objects in a shared section, which must come last:

            CLASS Date
               int time
               FUNC toString(): string
                  ...
               }
               SHARED
                 FUNC dateFromString(string entry): Date
                    ...
                 }
                 ...
               }
            }
            ...
            Date d = Date.dateFromString(line)

        + Clear separation between instance and non-instance members.
        + Need only one name, no confusion.
        + Less confusing than Java (mixing static and non-static methods)
        + No need for a class that can't be instantiated.
        + When extending the class, it's clear that the shared section is included.
        + A shared section can be added to a class later without other changes.
        - In a large class it can be a difficult to see if a line is in the shared section.

6. Put items shared between objects at the same level as the class, prepend the class name:

            CLASS Date
               int time
               FUNC toString(): string
                  ...
               }
            }
              
            FUNC Date.dateFromString(string entry): Date
               ...
            }
            ...
            Date d = Date.dateFromString(line)

          + advantages from 5.
          + easy to see that an item is not part of the object.
          + item name at definition same as where it's used.
           - private class members are visible to shared items, this isn't clear from the layout.
           - need to repeat the class name many times.

7. Like 5, but mark all member variables and methods with $.  Here $ is (more or less)
    short for "THIS.".

           CLASS Date
               int $time
               FUNC $toString(): string
                  RETURN $time.toString()
               }
               SHARED
                 FUNC dateFromString(string entry): Date
                    ...
                 }
                 ...
               }
            }
            ...
            Date d = Date.dateFromString(line)

          + advantages from 5.
          + easy to see that an item is part of the object and not in the SHARED section.
          + object members clearly stand out from arguments and local variables.
          + allows using function arguments with the same name as an object member:
               PROC $setTime(int time)
                  $time = time
               }
           - Have to type $ quite often

8. Like 7, but allow using SHARED multiple times.  This is to allow for class members
    to be located close to the methods where they are used. It is still possible to put
    common functionality at the end of the class.

           CLASS Date
               FUNC $monthName(int month) string
                  RETURN monthList[month]
               }
               SHARED
                 list<string> monthList = ["Jan", "Feb", "Mar" ... ]
               }

               FUNC $weekdayName(int day) string
                  RETURN weekdayList[day]
               }
               SHARED
                 list<string> weekdayList = ["Monday""Tuesday" ... ]
               }
            }

Choice: 8.  This matches with how inheritance works and works best in practice.

Shadowing members of the SHARED section

When nesting classes, each class can have its own SHARED section.
What if the same variable or method appears in both?
Example

        CLASS Outer
          int $someNumber
          CLASS Inner
            int $someNumber
            SHARED
              FUNC createFromString(string s): Inner
                Inner i = NEW()
                i.someNumber = s.toInt()

                RETURN i
              }
            }
          }
          SHARED
            FUNC createFromString(string s): Outer
              Outer o = NEW()
              o.someNumber = s.toInt()
              RETURN o
            }
          }
        }

The Inner.createFromString() shadows the Outer.createFromString()

1. Allow shadowing.  When the Inner.createFromString() method would
   not be there then the Outer.createFromString() will be used in the
   Inner class.
   - In a large class it becomes unclear what method is invoked, this leads to errors.

2. Disallow shadowing.  In the above example Inner.createFromString()
   cannot be declared.
   - Often use method names cannot be used, some undesired name has to be used.

3. An inner class cannot use members of the SHARED section of an outer class
   directly, it must be prepended with the class name.
   + directly clear what method is used
   o slightly longer names in rare cases

Choice: 3.

Comments