Using External Classes
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.
Creating instances of external classes
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
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
For 32-bit implementations of APLX:
For 64-bit implementations of APLX:
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
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
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.
Calling methods and accessing properties
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
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
(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.
Calling 'static' methods
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:
Overloaded methods and syntactic ambiguity
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
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:
(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:
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.
Object lifetimes and garbage collection
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
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.
Issues with naming conventions
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 $$).
Errors reported from the external environment
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
Inheriting from external classes
Your own classes (written in APL) cannot inherit directly from an external class. However, you can achieve much the same result by using
Copyright © 1996-2010 MicroAPL Ltd