asymptote: Structures

 
 6.8 Structures
 ==============
 
 Users may also define their own data types as structures, along with
 user-defined operators, much as in C++.  By default, structure members
 are 'public' (may be read and modified anywhere in the code), but may be
 optionally declared 'restricted' (readable anywhere but writeable only
 inside the structure where they are defined) or 'private' (readable and
 writable only inside the structure).  In a structure definition, the
 keyword 'this' can be used as an expression to refer to the enclosing
 structure.  Any code at the top-level scope within the structure is
 executed on initialization.
 
    Variables hold references to structures.  That is, in the example:
 struct T {
   int x;
 }
 
 T foo;
 T bar=foo;
 bar.x=5;
 
    The variable 'foo' holds a reference to an instance of the structure
 'T'.  When 'bar' is assigned the value of 'foo', it too now holds a
 reference to the same instance as 'foo' does.  The assignment 'bar.x=5'
 changes the value of the field 'x' in that instance, so that 'foo.x'
 will also be equal to '5'.
 
    The expression 'new T' creates a new instance of the structure 'T'
 and returns a reference to that instance.  In creating the new instance,
 any code in the body of the record definition is executed.  For example:
 int Tcount=0;
 struct T {
   int x;
   ++Tcount;
 }
 
 T foo=new T;
 T foo;
 Here, 'new T' produces a new instance of the class, which causes
 'Tcount' to be incremented, tracking the number of instances produced.
 The declarations 'T foo=new T' and 'T foo' are equivalent: the second
 form implicitly creates a new instance of 'T'.  That is, after the
 definition of a structure 'T', a variable of type 'T' is initialized to
 a new instance ('new T') by default.  During the definition of the
 structure, however, variables of type 'T' are initialized to 'null' by
 default.  This special behaviour is to avoid infinite recursion of
 creating new instances in code such as
 struct tree {
   int value;
   tree left;
   tree right;
 }
 
    The expression 'null' can be cast to any structure type to yield a
 null reference, a reference that does not actually refer to any instance
 of the structure.  Trying to use a field of a null reference will cause
 an error.
 
    The function 'bool alias(T,T)' checks to see if two structure
 references refer to the same instance of the structure (or both to
 'null').  In example at the beginning of this section, 'alias(foo,bar)'
 would return true, but 'alias(foo,new T)' would return false, as 'new T'
 creates a new instance of the structure 'T'.  The boolean operators '=='
 and '!=' are by default equivalent to 'alias' and '!alias' respectively,
 but may be overwritten for a particular type (for example, to do a deep
 comparison).
 
    Here is a simple example that illustrates the use of structures:
 struct S {
   real a=1;
   real f(real a) {return a+this.a;}
 }
 
 S s;                            // Initializes s with new S;
 
 write(s.f(2));                  // Outputs 3
 
 S operator + (S s1, S s2)
 {
   S result;
   result.a=s1.a+s2.a;
   return result;
 }
 
 write((s+s).f(0));              // Outputs 2
 
 
    It is often convenient to have functions that construct new instances
 of a structure.  Say we have a 'Person' structure:
 struct Person {
   string firstname;
   string lastname;
 }
 
 Person joe;
 joe.firstname="Joe";
 joe.lastname="Jones";
 Creating a new Person is a chore; it takes three lines to create a new
 instance and to initialize its fields (that's still considerably less
 effort than creating a new person in real life, though).
 
    We can reduce the work by defining a constructor function
 'Person(string,string)':
 struct Person {
   string firstname;
   string lastname;
 
   static Person Person(string firstname, string lastname) {
     Person p=new Person;
     p.firstname=firstname;
     p.lastname=lastname;
     return p;
   }
 }
 
 Person joe=Person.Person("Joe", "Jones");
 
    While it is now easier than before to create a new instance, we still
 have to refer to the constructor by the qualified name 'Person.Person'.
 If we add the line
 from Person unravel Person;
 immediately after the structure definition, then the constructor can be
 used without qualification: 'Person joe=Person("Joe", "Jones");'.
 
    The constructor is now easy to use, but it is quite a hassle to
 define.  If you write a lot of constructors, you will find that you are
 repeating a lot of code in each of them.  Fortunately, your friendly
 neighbourhood Asymptote developers have devised a way to automate much
 of the process.
 
    If, in the body of a structure, Asymptote encounters the definition
 of a function of the form 'void operator init(ARGS)', it implicitly
 defines a constructor function of the arguments 'ARGS' that uses the
 'void operator init' function to initialize a new instance of the
 structure.  That is, it essentially defines the following constructor
 (assuming the structure is called 'Foo'):
 
      static Foo Foo(ARGS) {
        Foo instance=new Foo;
        instance.operator init(ARGS);
        return instance;
      }
 
    This constructor is also implicitly copied to the enclosing scope
 after the end of the structure definition, so that it can used
 subsequently without qualifying it by the structure name.  Our 'Person'
 example can thus be implemented as:
 struct Person {
   string firstname;
   string lastname;
 
   void operator init(string firstname, string lastname) {
     this.firstname=firstname;
     this.lastname=lastname;
   }
 }
 
 Person joe=Person("Joe", "Jones");
 
    The use of 'operator init' to implicitly define constructors should
 not be confused with its use to define default values for variables
 (SeeVariable initializers).  Indeed, in the first case, the return
 type of the 'operator init' must be 'void' while in the second, it must
 be the (non-'void') type of the variable.
 
    The function 'cputime()' returns a structure 'cputime' with
 cumulative CPU times broken down into the fields 'parent.user',
 'parent.system', 'child.user', and 'child.system'.  For convenience, the
 incremental fields 'change.user' and 'change.system' indicate the change
 in the corresponding total parent and child CPU times since the last
 call to 'cputime()'.  The function
 void write(file file=stdout, string s="", cputime c,
            string format=cputimeformat, suffix suffix=none);
 displays the incremental user cputime followed by "u", the incremental
 system cputime followed by "s", the total user cputime followed by "U",
 and the total system cputime followed by "S".
 
    Much like in C++, casting (SeeCasts) provides for an elegant
 implementation of structure inheritance, including virtual functions:
 struct parent {
   real x;
   void operator init(int x) {this.x=x;}
   void virtual(int) {write(0);}
   void f() {virtual(1);}
 }
 
 void write(parent p) {write(p.x);}
 
 struct child {
   parent parent;
   real y=3;
   void operator init(int x) {parent.operator init(x);}
   void virtual(int x) {write(x);}
   parent.virtual=virtual;
   void f()=parent.f;
 }
 
 parent operator cast(child child) {return child.parent;}
 
 parent p=parent(1);
 child c=child(2);
 
 write(c);                       // Outputs 2;
 
 p.f();                          // Outputs 0;
 c.f();                          // Outputs 1;
 
 write(c.parent.x);              // Outputs 2;
 write(c.y);                     // Outputs 3;
 
    For further examples of structures, see 'Legend' and 'picture' in the
 'Asymptote' base module 'plain'.