Topic: APLX Help : Interfacing to other languages : Using external classes
[Next | Previous | Contents | Index | APL Home ]

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 ⎕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:

For 32-bit implementations of APLX:

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
'r' R aplxobj_r.dll aplxobj_r.bundle
'ruby' Ruby aplxobj_ruby.dll aplxobj_ruby.bundle
Other Customized environment aplxobj_XXX.dll aplxobj_XXX.bundle

For 64-bit implementations of APLX:

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
'r' R aplx64obj_r.dll
'ruby' Ruby aplx64obj_ruby.dll
Other Customized environment aplx64obj_XXX.dll

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.

Customized interfaces

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 ⎕DESC, which is like ⎕NL except that it returns types for properties, and the prototypes of methods:

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

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:

 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:

  • 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

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 ⎕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:


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.

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 ⎕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.

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>
      f←'ruby' ⎕new 'ftp'
      f←'.net' ⎕NEW 'ftp'
Class ftp not found in current search list
      f←'.net' ⎕NEW 'ftp'
      dt←'.net' ⎕NEW 'DateTime' 'Bastille day'
Constructor on type 'System.DateTime' not found.
      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.

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 mixins, which allow you to 'mix in' the properties and methods of one or more external classes into your APL objects.

Topic: APLX Help : Interfacing to other languages : Using external classes
[Next | Previous | Contents | Index | APL Home ]