Recall on the slide about Instantiating Objects in the REPL, we defined our own object using the Gendl define-object macro similarly to the following:
(define-object my-box-1a (box):input-slots((length 2)(width 3)(height 4)))
In this section we will look a bit deeper into define-object.
The syntax for define-object is:
(define-object definition-name ([mixins*]) [specifications*])
- is a symbol naming the object being defined.
- means zero or more symbols naming other object definitions, from which this object type will inherit characteristics.
- comprise the body of the object definition and consist of a set of keyword-value pairs which describe the characteristics and behaviors of any given instance of the object type.
In the above example, my-box-1a specifies box as a mixin. Because box includes a built-in slot volume , my-box-1a will also include a volume slot. The primitive box also includes length, width and height, but specifies their default values each to be 0. In the example above, because my-box-1a also includes length, width, and height, and assigns default values to them (2, 3 and 4, respectively), these values override the default 0 values in any instances we may make of my-box-1a as long as we do not feed in any other specific values at instantiation-time.
GDL-USER> (setq self (make-object 'box)#<BOX #x210348242D>
GDL-USER> (the length)0
GDL-USER> (the width)0
GDL-USER> (the height)0
GDL-USER> (the volume)0
GDL-USER> (setq self (make-object 'my-box-1a)#<BOX #x210348242D>
GDL-USER> (the length)2
GDL-USER> (the width)3
GDL-USER> (the height)4
GDL-USER> (the volume)24
If you specify multiple mixins, precedence on the slots is left to right (and depth-first).
(define-object my-box-1b ():input-slots((length 2)(width 3)(height 4)))(define-object my-box-2 (my-box-1b box))(define-object my-box-3 (box my-box-1b))
GDL-USER> (setq self (make-object 'my-box-2)#<MY-BOX-2 #x210346742D>
GDL-USER> (the length)2
GDL-USER> (setq self (make-object 'my-box-3)#<MY-BOX-3 #x210349442D>
GDL-USER> (the length)0
The specifications section is what really defines the object. This can be thought of as its computational DNA.
Each section of the specification is identified by one of a few supported keyword symbols. The most common ones are:
:input-slots specify any required and/or optional inputs to the object. Each input-slot may be
- a symbol
- In this case it is a required input for the object.
- a symbol-value pair enclosed in parentheses
- in this case the slot is provided with a default value which may be overridden by passing in a different value, either from the parent object or from a toplevel call to the make-object function.
In the example below, length is required, but width and height each default to 4.
(define-object my-box-4 (box):input-slots(length(width 4)(height 4)))
If my-box-4 is instantiated, and length (or any attribute which depends on length) is evaluated, then an error will result, as length does not have a value. In other words, to be of any use, this object must be instantiated with length passed in explicitly as an input.
GDL-USER> (setq self (make-object 'my-box-4))#<MY-BOX-4 #x2103467C4D>
GDL-USER> (the length)Invoking restart: Return to SLIME's top level.; Evaluation aborted on #<SIMPLE-ERROR #x21056CE7DD>.
GDL-USER> (the volume)Invoking restart: Return to SLIME's top level.; Evaluation aborted on #<SIMPLE-ERROR #x21056B76DD>.
GDL-USER> (setq self (make-object 'my-box-4 :length 3))#<MY-BOX-4 #x21034F4C4D>
GDL-USER> (the length)3
GDL-USER> (the volume)48
:input-slots which have default values may have those values over-ridden when the object is instantiated
GDL-USER> (setq self (make-object 'my-box-4 :length 3 :width 10))#<MY-BOX-4 #x21034F4C4D>
GDL-USER> (the volume)120
:computed-slots can represent known values, intermediate results, or final outputs which may be computed by an object
They are defined as symbol-value pairs enclosed in parentheses. The value can be any Common Lisp value or expression.
:computed-slots can refer to the return values of other :computed-slots using the GendL macro the
(define-object my-box-4 (box):input-slots(length(width 4)(height 4))):computed-slots((density 7800)(mass (* (div (the volume) 1000000000) (the density)))))
In the example above, a computed-slot density has been created and set to 7800 (the density of steel in kg/m3). A further slot has been created, mass, which divides the volume by 1000000 and then multiplies that result by the value of density
A point to note here is the Gendl is dimensionless. It is the responsibility of the programmer to ensure units are correct when performing calculations. Implicit in the example here is that length, width and height are specified in mm and the resultant mass will be in kg
We will cover the use of functions to perform calculations in more detail later in this tutorial
The :objects section is where child objects are specified. This specification includes:
- The object name
- i.e. the name of the slot which will contain this child object instance
- The object type
- This is expected to correspond to an object definition name specified in another define-object. This can be a literal (quoted) symbol or an expression which yields a symbol when evaluated (that is, object type can be determined dynamically at runtime).
- The object input values
- these are keyword-tagged expressions which specify the values to be passed (when and if demanded) into the child object instance. They keywords should match existing input-slots in the child object's type, otherwise a compiler warning will be generated.
In the definition below, the parent object (assembly-1) has two child objects: one called my-box based on my-box-4 with length set to 10, and another called my-sphere, based on the GendL sphere object, with its radius specified as being equal to the width of my-box
Note that when specifying the inputs to an object
- the name must match one of the symbols in the object's :input-slots.
- the name must be specified as a keyword (ie preceeded by a
:) so an :input-slot length is specified as :length when specifying the inputs.
(define-object assembly-1 (base-object):objects((my-box :type 'my-box-4:length 10)(my-sphere :type 'sphere:radius (the my-box width))))
Instantiating assembly-1 in Geysr and drawing the leaves will look like this
The :functions section is where you put the names, argument lists and bodies for :functions which can operate within the context of the Gendl object they are defined in. They shouldn't be confused with Common Lisp functions which are defined with defun, although the syntax is very similar. The biggest difference is that a Gendl :function can access messages within the object it is defined in by using the Gendl the macro
We will cover more on :functions in a later part of this tutorial