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

www.microapl.co.uk

Interfacing to Ruby


Specifying a call to Ruby

You can interface to Ruby by supplying 'ruby' as the environment string (left argument) for ⎕NEW, ⎕GETCLASS, or ⎕CALL. These system functions will allow you to create an instance of a Ruby class, or to call a Ruby static mathod.

Loading modules and setting the Ruby DLL search path

Ruby classes are placed in Modules. When you use ⎕NEW (or ⎕GETCLASS or ⎕CALL) to refer to a Ruby class, the system searches through the modules which have been loaded into the Ruby interpreter to resolve the class name. If the class is not found, you will get an error:

      dt←'ruby' ⎕new 'DateTime'
#<NameError: uninitialized constant DateTime>
DOMAIN ERROR
      dt←'ruby' ⎕new 'DateTime'
         ^

The above example has failed because DateTime is a class defined in the Date module, which has not been loaded. (In a Ruby script, you would get the same error if you had not loaded the Date module by using the Ruby 'require' statement). You can tell the Ruby interpreter to load a module by using the system function ⎕SETUP and the 'require' keyword. For example:

      'ruby' ⎕SETUP 'require' 'Date'
      dt←'ruby' ⎕new 'DateTime'
      dt
[ruby:DateTime]

The effect of using the 'require' keyword is to add a given Ruby module (or script) to the list in which Ruby will search for class definitions. The parameter is a character vector containing the module name. This can be specified either as a full path name, or as just a file name, in which case Ruby will search in its current search path for the script:

      'ruby' ⎕SETUP 'require' 'c:\ruby\myapp.rb'

You can use the 'addpath' keyword to add one or more directories to Ruby's current search path for modules:

      'ruby' ⎕SETUP 'addpath' 'c:\rubyapps' 'c:\rubylibs\version2' 

See the documentation on ⎕SETUP for more options for controlling the Ruby environment.

Conversion of Ruby data types to APL data

APLX by default applies the following data conversion rules to data returned from Ruby:

  • Ruby integer ("Fixnum") values are converted to APL integers.

  • Ruby Float and Bignum values are converted to APL floating-point numbers.

  • Ruby Booleans (true or false) are converted to APL binary values 1 or 0

  • Ruby Strings are converted to APL character arrays, translated to APLX internal representation.

  • Ruby nil values are converted to APL Null objects

  • Simple Ruby arrays are converted to APL arrays, with individual elements converted as above.

Anything else is left as an object in the Ruby environment, and a reference to the object is returned to APL.

There are some special cases to consider. The data might not be convertible at all, or it might lose precision in the conversion. For example, a Ruby Bignum might have a higher precision than an APL double-precision floating point can represent. To handle cases like this, APLX provides the ⎕REF system method. This forces the data to remain as a Ruby object. You can then call Ruby methods appropriate to the Bignum or other data type, to manipulate the data without losing precision.

An example which cannot be represented at all is where a Ruby Double contains a NaN (Not A Number). APL does not handle NaNs, so it cannot be converted to an APL floating-point value. Instead, NaNs are left as Objects. If you try to use the data in an APL expression, you will get a DOMAIN ERROR, but you can see that it is a NaN and use Ruby methods on it.

Supplying Boolean arguments to Ruby methods

Ruby methods which expect true or false as arguments have to be handled in a special way, because Ruby does not allow 1 and 0 as equivalents to Booleans. To work around this, pass a one element matrix (1 1⍴1 or 1 1⍴0) as the argument; the interface will recognize this as a special case and convert the data to a Ruby true or false value.

Using the Ruby interface from multiple APL tasks

Because it is not safe to call the Ruby interpreter from multiple threads, you cannot use the Ruby interface from more than one APL task at a time. If you try to do so, you will get an error message and a FILE LOCKED error:

      dt←'ruby' ⎕new 'Date'
This interface cannot be used by more than one APL task at a time
FILE LOCKED
      dt←'ruby' ⎕new 'Date'
         ^

The lock will be cleared when the APL task which has been accessing Ruby executes a )CLEAR, )LOAD, or )OFF.

Evaluating Ruby expressions

Because Ruby is an interpreted language, it is possible to use ⎕EVAL to run lines of Ruby code, and for setting up variables in the Ruby environment:

      'ruby' ⎕EVAL 's=String.new "Hello there"'
Hello there
      'ruby' ⎕EVAL 's.length'
11
      'ruby' ⎕EVAL 'Math.sqrt(9)'
3

Ruby naming conventions

Ruby allows a method names to include various characters (such as = and ?) which are not valid in APL names. Indeed by convention in Ruby, methods which return a Boolean often end with a question mark. For example, the method leap? of the Ruby DateTime class returns a Boolean indicating whether the date falls in a leap year, and the method responds_to? is a standard way of finding out whether a Ruby object supports a given method call (message).

The problem with this is that the APL parser has a different view of what constitutes a valid name. So if you write:

    is_leap_year←RUBYDATE.leap?

then the APL interpreter will think the rightmost token of the line is the APL ? primitive, separate from the compound identifier RUBYDATE.leap. It will therefore give an error.

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. So the valid way of calling the is_leap? method is:

    is_leap_year←RUBYDATE.leap$?

Example 1

This example shows the use of the Ruby Hash class, which maintains a list of Key - Value pairs.

      h←'ruby' ⎕NEW 'Hash'
      h.length
0
      h.store 'France' 'Paris'
Paris
      h.store 'UK' 'London'
London
      h.store 'Italy' 'Rome'
Rome
      h.store 'Germany' 'Berlin'
Berlin
      h.length
4
      h.to_a 
  UK London   France Paris   Italy Rome   Germany Berlin
      h.sort            ⍝ Sort by key values, return array
  France Paris   Germany Berlin   Italy Rome   UK London
      h.key$? 'France'  ⍝ Does key 'France' exist? (Note use of $ escape character)
1
      h.key$? 'USA'     ⍝ Does key 'USA' exist? 
0
      h.fetch 'France'
Paris
      h.fetch 'USA'
#<IndexError: key not found>
DOMAIN ERROR
      h.fetch 'USA'
      ^

Example 2

This example shows the use of the Ruby Complex class, for manipulating complex numbers:

      'ruby' ⎕setup 'require' 'complex'
      compclass←'ruby' ⎕GETCLASS 'Complex' 
      a←⎕NEW compclass 3 4
      a.⎕DS
3+4i
      b←⎕NEW compclass 2 ¯1
      b.⎕DS
2-1i
      b.conjugate
[ruby:Complex]
      b.conjugate.⎕DS
2+1i
      b.polar
2.236067977 ¯0.463647609
      c←a.$* b    ⍝ Complex multiplication. Note use of $ escape char
      c.⎕DS
10+5i

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