Darwin is an object oriented language. Object oriented programming is supported by several features. To illustrate these notions we will use the implementation of complex numbers in Darwin. The features supporting OO programming are:
Data types/Classes - Arbitrary data types can be created dynamically by using a functional notation, where the function name is the data type name and the arguments are the components.
Complex( real_part, complex_part ) will be our data type to hold complex numbers. The number 1 is then represented as Complex(1,0). Complex(0,1) denotes the imaginary unit. See Complex for full details of this example.
Constructors - A constructor of the Data type is any function/method or operation that will produce as a result a new object of the given type. It is customary to use the name of the data type as a constructor. This has several advantages: readability, simpler name space, and the possibility of having a checker/normalizer.
When the data type has type restrictions in its components, this type checking can be done automatically by defining the contructor function as a function with argument type checking. For example, if we want our arguments of the Complex type to be numeric, we can do this by defining:
Complex := proc( Re:numeric, Im:numeric )
. . . .
end:
The result of a call to Complex(a,b) (which is now a function too) should be the structure Complex(a,b). Technically, Complex(a,b) must evaluate first, to do parameter checking and other normalizations, and then return unevaluated. This is achieved with the noeval() function. Noeval assembles a data type without calling the function. The above example becomes:
Complex := proc( Re:numeric, Im:numeric )
noeval( Complex(Re,Im) )
end:
Normalizers - The constructor function/method could perform extra checks or simplification of the data type if this is desired. In the case of complex numbers, it may be desirable to simplify Complex data types with a 0 imaginary part to a simple numerical value. E.g.
Complex := proc( Re:numeric, Im:numeric ) if Im=0 then Re else noeval( Complex(Re,Im) ) fi end:
Selectors - Selectors are used in two main modes, to select part of the data type or to modify part of the data type. Selectors are handled by the kernel or by user functions. Integer selectors or selectors with the names of the parameters of the data type are handled by the kernel. Other selectors are handled by a function/method named like the data type concatenated with the string '_select'. The selector function is passed the object and the selection argument and, optionally, the value to be assigned. Selectors which are positive integers or a range of positive integers are computed directly, and operate on the corresponding component of the data type.
Complex := proc( Re:numeric, Im:numeric ) ... end:
a := Complex(7,-3);
a[xxx] - Identical to Complex_select(a,xxx)
a[1] - Is 7, without any function calls.
a[Re] - is 7, without any function calls.
a[Im] := -1; - Will change a so that the second component is changed to -1. This is done by the kernel.
a[yyy] := 1; - Will be handled by calling Complex_select(a,yyy,1). The return value is ignored in this case. Complex_select will normally modify the data type.
a[2] := 3; - The value 3 is assigned to the second component of a without any call.
a[1..2] - Is the expression sequence 7,-3, without any function calls.
a[Im] := []; - Gives an error, since the type of the second argument does not match the assigned value.
It is clear that using integer selectors will prevent the use of generic data types and object orientation, and should not be encouraged. Note that the function 'op' is equivalent to selecting with integers.
Using the names of the parameters as the selectors provides type checking (on assignments) and is performed by the kernel, hence it is very efficient. If the integrity of the whole data structure needs to be checked, then the user must write a xxx_select function to run any desired check and/or the option NormalizeOnAssign should be specified in the constructor.
Converters - A converter is a function/method which converts one data type into another. For data types A and B, the function B_A should convert a B object into an A object. When the data type A is defined with option polymorphic, then this conversion (calling B_A), is done automatically for any use of A(B(..)). It is common, and very useful, to have converters to basic types in the system, like string. The function/method B_A will be called with the object B as argument. E.g.
Complex_string := proc( C:Complex )
sprintf( '%g+%gi', C[Re], C[Im] ) end:
Polar_Complex := proc( p:Polar )
Complex( p[rho]*cos(p[theta]),
p[rho]*sin(p[theta]) ) end:
Operations - A function/method which is defined with option polymorphic is able to handle arbitrary objects. If the function is named f, then when f is called with a single object of type A, A_f will be called. E.g.
f := proc( x:numeric ) option polymorphic; x+1 end:
Complex_f := proc( x:Complex )
Complex( x[Re]+1, x[Im] ) end:
Most system functions have option polymorphic. In particular all arithmetic operations. Complex_plus, Complex_times and Complex_power will handle all arithmetic operations with Complex data types. (Subtraction and division are handled by multiplication by -1 and powering to -1). It is very useful to implement the following methods for a data type A:
A_plus A_times A_power A_print A_printf A_string A_equal A_Rand A_type A_example A_Description
Type testing - Type testing can be done by a type-testing expression or by a type-testing procedure. In both cases, the symbol Complex_type is assigned a value. Type testing expressions are powerful enough for most uses. E.g.
Complex_type := noeval( Complex(numeric,numeric) );
Notice that a noeval is needed, since the arguments of the Complex type are not valid as a complex number, and hence would give an error if evaluated. With this definition type testing can be done explicitly or implicitly. E.g.
if type(a,Complex) then .... Complex_plus := proc( a:Complex, b:Complex ) ...
Inheritance - Inheritance is the ability of instructing the system that a certain data type is equivalent or a super-set of another, and hence operations do not need to be redefined. More precisely, let A and B be data types and assume that A is either equivalent or a super-set of B. The command
Inherit( A, B );
is interpreted as: A will inherit any operation from B which is not defined for A. This operation will be appropriately modified so that it works with A objects. For example, we can define the data type Polar, which is also a complex number. So Polar is equivalent to Complex. Polar will have some special selectors, and some operations which can be performed more efficiently in this representation (e.g. multiplication, powering and absolute value). The rest of the operations can be inherited from Complex. The definition of Polar could be:
Polar := proc( rho:numeric, theta:numeric )
option polymorphic;
... normalizations, error checking, etc. ...
noeval( Polar(args) ) end:
Polar_select := proc( a:Polar, s, val )
# selectors must include Re and Im so that it
# can work as a Complex
. . . . end:
Polar_times := proc( a:Polar, b:Polar )
Polar( a[rho]*b[rho], a[theta]+b[theta] ) end:
Polar_abs := proc( p:Polar ) p[rho] end:
Inherit( Polar, Complex );
CompleteClass( Polar );
The function CompleteClass performs checking and some level of completion of a class. For example, it will find that there is no type definition for Complex, but there is enough information (from the types of the parameters) to construct a primitive checker. In this example it will also create a Complex_Rand function which creates random instances of Complex. It is recommended that CompleteClass is run after a class is defined.
Organization - All functions/methods related to a data type, i.e. all functions with names Complex_xxx, should be stored in the library in a single file, ideally named "Complex". The symbol Complex should be assigned an unevaluated ReadLibrary command. E.g.
Complex := noeval( ReadLibrary(Complex) ):
or if the functions are stored in 'mylibrary/Complex',
Complex := noeval( ReadLibrary( 'mylibrary/Complex',
Complex )):