Modern object-oriented language frameworks such as Java, .Net and Ruby provide a consistent mechanism for exploring class libraries, discovering what classes are defined in the library, and detailing the class methods and properties. This allows objects to be created and used from outside the specific environment in which they were written. (These facilities, which provide so-called metadata about a given class together with the ability to create new classes at runtime, are sometimes described by the term Reflection.)
The underlying object-oriented model in APLX is broadly similar to that of the .Net languages, Java, or Ruby. This makes it possible to access all the powerful facilities of the .Net and Java class libraries, as well as custom software written in the mainstream object-oriented languages in use today, directly from APLX.
You can create instance of external classes in much the same way as you create instances of APL (user-defined) classes, by means of the system function ŚNEW. The right argument of ŚNEW can be either a class reference (which is typically the case when an APL user-defined class is being used), or a class name as a character vector. In the latter case, the optional left argument of ŚNEW determines the environment in which the class is to be found. For example:
Create an instance of the .Net DateTime class, defined in the .Net class libraries, specifying the initial date to the constructor of that class:
NETDATE„'.net' ŚNEW 'System.DateTime' 2007 6 20 9 32 3
Create an instance of the Ruby DateTime class, defined in the Ruby class libraries:
RUBYDATE„'ruby' ŚNEW 'DateTime' 2007 6 20 9 32 3
Create an array of complex numbers in the R statistical language:
C„'r' ŚNEW 'complex' (3 2˝(1 2) (3 4) (5 6) (7 8) (9 10) (11 12))
The left argument to ŚNEW is the environment identifier. For external classes, it corresponds to a shared library or DLL, as follows:
Left arg | Environment | Windows DLL | Macintosh bundle | Linux shared library |
(Omitted) | User-defined APL class | None | None | None |
'Ś' | System class | None | None | None |
'.net' | Microsoft .Net | aplxobj_net.dll | Not supported | Not supported |
'java' | Java | aplxobj_java.dll | aplxobj_java.bundle | aplxobj_java.so |
'r' | R | aplxobj_r.dll | aplxobj_r.bundle | aplxobj_r.so |
'ruby' | Ruby | aplxobj_ruby.dll | aplxobj_ruby.bundle | aplxobj_ruby.so |
Other | Customized environment | aplxobj_XXX.dll | aplxobj_XXX.bundle | aplxobj_XXX.so |
Left arg | Environment | Windows DLL | Linux shared library |
(Omitted) | User-defined APL class | None | None |
'Ś' | System class | None | None |
'.net' | Microsoft .Net | aplx64obj_net.dll | Not supported |
'java' | Java | aplx64obj_java.dll | aplx64obj_java.so |
'r' | R | aplx64obj_r.dll | aplx64obj_r.so |
'ruby' | Ruby | aplx64obj_ruby.dll | aplx64obj_ruby.so |
Other | Customized environment | aplx64obj_XXX.dll | aplx64obj_XXX.so |
What actually happens 'under the hood' here is that user-defined and system classes are handled directly by the APLX interpreter. Operations to create and use object classes written in other environments are passed to the external library (Windows dynamic link library, Macintosh bundle, or Linux shared library) whose name is given in the table. This provides an extensible interface, allowing further environments to be added in the future. For example, to add an interface to Mono (the open-source equivalent of .Net) it would not be necessary to change the APLX interpreter at all; all that would be required is to supply a new interface library aplxobj_mono.dll.
It is also possible to create instances of external classes by using a class reference rather than a character vector as the right argument to ŚNEW, but first you need to obtain the reference from the external system using the ŚGETCLASS system function:
NETDATECLASS„'.net' ŚGETCLASS 'System.DateTime' NETDATE„ŚNEW NETDATECLASS 2007 6 20 9 32 3
Obtaining and using a class reference can be much more efficient than supplying a class name to ŚNEW if you need to create a large number of objects.
As well as being extensible to further public object-oriented environments, the mechanism also allows the same APL syntax to be used for accessing custom class libraries written in languages (such as C++) which do not support metadata and Reflection. For example, if a financial institution wanted to make use of a timeseries analysis class library written in C++, it could write a simple interface DLL aplxobj_ts.dll which would allow classes contained in that library to be used from APL:
TS„'ts' ŚNEW 'TimeSeries'
The APLX interpreter, seeing this line, would pick up the environment identifier 'ts' which would cause it to search for aplxobj_ts.dll to handle the creation and use of the classes within the custom library. Unlike the generalized interfaces to .Net, Java, and Ruby, this custom interface would of course support only the specific set of classes for which it had been written, with the names and other details of the supported classes and their methods hard-coded into the interface DLL rather than being obtained at runtime. Nonetheless, it is a powerful extension of the ability of APL to use external code.
Once you have a reference to an object, you can use a consistent syntax to access its methods and properties. It makes no difference whether the object is an instance of an internal APL class, or of an external Ruby, Java, or .Net class. For external calls, the APL interpreter automatically marshals any parameters supplied to the form required by the external class method (assuming that such a conversion is possible). For example, if the method requires a parameter which is of type signed 16-bit integer, the APL interpreter will convert any supplied binary, integer, or floating-point data type to the required 16-bit form, provided that the number is integral and is in range. Where the required parameter is a string, APLX will automatically convert from an APL character vector. Where the required parameter is itself an object reference, the user can supply an APL reference to an object (in the same environment, of course - you cannot pass a Ruby object reference to a .Net method).
The information which allows this conversion to happen successfully is the metadata which describes the external class. Depending on the external environment, you can see this metadata in human-readable form by using the system method ŚDESC, which is like ŚNL except that it returns types for properties, and the prototypes of methods:
NETDATE.ŚDESC 3 System.String ToString() System.String ToString(System.String) System.String ToString(System.IFormatProvider) System.String ToString(System.String, System.IFormatProvider) System.Type GetType() System.DateTime Add(System.TimeSpan) System.DateTime AddDays(Double) System.DateTime AddHours(Double) ... etc
This means, for example, that the AddDays method of the .Net System.DateTime class takes a double-precision floating point value as an argument, and returns a new DateTime object which represents the original date-time value plus the number of days (or parts of days). We can see this by calling the method with a suitable parameter, but without assigning the result:
NETDATE.AddDays 1 [.net:DateTime]
What has happened here is that the .Net class library has created a new DateTime object, and a reference to it has been passed back to APL. Because we have not assigned or used the object reference, the default display form of the object has been written to the session window, and the object has then been deleted.
To display the date/times for exactly one, two, and three days following, we can use the APL 'each' operator to run the AddDays method three times, with three separate arguments, and then run the ŚDS method on each of the three new temporary DateTime objects which will be returned:
(NETDATE.AddDays¨Ľ3).ŚDS 21/06/2007 09:32:03 22/06/2007 09:32:03 23/06/2007 09:32:03
Properties are handled in a similar way to methods, although not all external classes really have properties as such; some only have 'getter' and 'setter' functions. You can read properties back directly, and assign to them using the normal APL assignment arrow.
Object-oriented languages like C# and Java include support for so-called static or shared class methods. These are methods which belong to a class but which do not manipulate an individual object. You can call these static methods from APLX in one of two ways:
If you have an object of the appropriate class, or a reference to the class, you can call the static method as though it were a normal object method. The object is just ignored when the call is made.
Alternatively, you can use the ŚCALL system function. As an example, consider the Java class 'java.lang.Integer', which includes a static method to convert an integer into a binary string. You can call it from APLX as follows:
'java' ŚCALL 'java.lang.Integer.toBinaryString' 37 100101
One common feature of modern object-oriented languages is that they support overloaded methods, that is to say the same method name is used for more than one method, but with different numbers or types of arguments. An example is shown above in the ŚDESC listing of methods for the .Net System.DateTime object; there are four versions of ToString(), one of which takes no arguments, two of which take a single argument of different types, and one of which takes two arguments.
Normally, APLX is able to handle this unambiguously by examining the parameters supplied, and choosing the correct match amongst the possible overloaded methods. Ambiguities are sometimes possible, however. The most important of these is the use of strand notation with niladic methods.
Consider the following line of traditional APL:
FORMAT 'THIS STRING'
It is impossible, looking at this line in isolation, to know whether this is a call to a monadic function FORMAT with 'THIS STRING' as the argument, or is a reference to a variable FORMAT, or is a call to a niladic function FORMAT. In the latter two cases, the returned data would be joined with the character vector 'THIS STRING' to produce a length-2 nested vector.
In traditional APL, the interpreter is able to resolve this ambiguity by looking at the type of the symbol FORMAT. The same name cannot simultaneously refer both to a monadic function, and to a niladic function or a variable.
When calling an external method, it would in many cases be possible to resolve the ambiguity in the same way. However, if the method is overloaded and exists in a form which take no arguments as well as in a form which takes arguments, it may not be possible to know which was intended.
APLX therefore assumes that if an external method call syntactically could take arguments, then it does take arguments. Hence, the line:
NETDATE.ToString TEXT
(where TEXT is a character vector) is always assumed to be a call to the version of ToString() which takes a String argument (the second form in the list), not to any niladic version of ToString().
If you really want to call the niladic version of the method, and join the result to the variable TEXT, then you can force this behavior by using parentheses:
(NETDATE.ToString) TEXT
Note that you cannot, in APL, tell the difference syntactically between a call to a niladic function or method, and a reference to a variable or property. The interface code will examine the metadata to make the right call. Note also that only internal APL user-defined methods can be dyadic functions, or APL operators.
Internally, APLX uses a reference-count method to ensure that objects are deleted when they are no longer needed (this is supplemented by special code to handle the problem of circular references, which might otherwise make it impossible to delete certain objects). Most of the external object-oriented environments which APLX interfaces to, including .Net, Ruby and Java, use a mark-and-sweep garbage collect system. In this system, a periodic sweep through memory is carried out to find all objects which are no longer referenced, and which therefore can be deleted.
The approach taken is for APLX to be the arbiter of object lifetimes. When an external object is created (either explicitly using ŚNEW, or as a result of some other operation), a reference to the external object is retained in the external environment (for example, the .Net runtime), and a copy of this reference is passed back to the APL interpreter. Because there is a reference to the object held in memory in the external environment, it will not be deleted by the mark-and-sweep garbage collect.
When APL's own reference count for the object falls to zero, APL deletes from the workspace its own data structure which describes the external object. Just before doing this, it calls the external sub-system to indicate that the object is no longer needed. Any cleaning-up required before deletion is then done, and the local reference to the object is deleted or replaced by a NULL. As a result, when the next garbage collect takes place in the external system, the memory used by the object will be reclaimed (unless of course there is another reference to the same object somewhere else in the external system).
For cases where the external environment does not use a garbage-collect system (for example, a custom interface to a class library in C++), the mechanism is similar; the only difference is that when APL notifies the external interface that the object is no longer needed, the external interface carries out an explicit delete rather than relying on replacing the reference with a NULL.
A special case arises if a reference to an external object is saved, for example when you )SAVE the workspace. When you re-load the workspace, the saved reference is no longer valid. APLX will issue a warning and set it to NULL.
One problem which arises with trying to unify the way in which external classes are accessed is that of naming conventions. For example, Ruby allows question marks and exclamation marks in method names. To work around this problem, the $ character can be used as an escape character in external names. It has the effect of treating the next character as part of the name. (If you are interfacing to a language in which $ itself is a valid character in a name, you can escape the dollar itself by writing $$).
If the external environment raises an error (or exception), APLX will normally try to print the error message or exception text on the session window, and then raise an APL error (typically DOMAIN ERROR). For example:
f„'ruby' Śnew 'ftp' #<NameError: uninitialized constant ftp> DOMAIN ERROR f„'ruby' Śnew 'ftp' ^ f„'.net' ŚNEW 'ftp' Class ftp not found in current search list DOMAIN ERROR f„'.net' ŚNEW 'ftp' ^ dt„'.net' ŚNEW 'DateTime' 'Bastille day' Constructor on type 'System.DateTime' not found. DOMAIN ERROR dt„'.net' ŚNEW 'DateTime' 'Bastille day' ^
For most external environments, you can find out more about what caused the the exception by using the ŚLE Last Exception system function.
Your own classes (written in APL) cannot inherit directly from an external class. However, you can achieve much the same result by using mixins, which allow you to 'mix in' the properties and methods of one or more external classes into your APL objects.