Loading Java functions into Scheme

When kawa -C compiles (see Files compilation) a Scheme module it creates a class that implements the java.lang.Runnable interface. (Usually it is a class that extends the gnu.expr.ModuleBody.) It is actually fairly easy to write similar "modules" by hand in Java, which is useful when you want to extend Kawa with new "primitive functions" written in Java. For each function you need to create an object that extends gnu.mapping.Procedure, and then bind it in the global environment. We will look at these two operations.

There are multiple ways you can create a Procedure object. Below is a simple example, using the Procedure1 class, which is class extending Procedure that can be useful for one-argument procedure. You can use other classes to write procedures. For example a ProcedureN takes a variable number of arguments, and you must define applyN(Object[] args) method instead of apply1. (You may notice that some builtin classes extend CpsProcedure. Doing so allows has certain advantages, including support for full tail-recursion, but it has some costs, and is a bit trickier.)

import gnu.mapping.*;
import gnu.math.*;
public class MyFunc extends Procedure1
{
  // An "argument" that is part of each procedure instance.
  private Object arg0;

  public MyFunc(String name, Object arg0)
  {
    super(name);
    this.arg0 = arg0;
  }

  public Object apply1 (Object arg1)
  {
    // Here you can so whatever you want. In this example,
    // we return a pair of the argument and arg0.
    return gnu.lists.Pair.make(arg0, arg1);
  }
}

You can create a MyFunc instance and call it from Java:

  Procedure myfunc1 = new MyFunc("my-func-1", Boolean.FALSE);
  Object aresult = myfunc1.apply1(some_object);

The name my-func-1 is used when myfunc1 is printed or when myfunc1.toString() is called. However, the Scheme variable my-func-1 is still not bound. To define the function to Scheme, we can create a "module", which is a class intended to be loaded into the top-level environment. The provides the definitions to be loaded, as well as any actions to be performed on loading

public class MyModule
{
  // Define a function instance.
  public static final MyFunc myfunc1
    = new MyFunc("my-func-1", IntNum.make(1));
}

If you use Scheme you can use require:

#|kawa:1|# (require <MyModule>)
#|kawa:2|# (my-func-1 0)
(1 0)

Note that require magically defines my-func-1 without you telling it to. For each public final field, the name and value of the field are entered in the top-level environment when the class is loaded. (If there are non-static fields, or the class implements Runnable, then an instance of the object is created, if one isn’t available.) If the field value is a Procedure (or implements Named), then the name bound to the procedure is used instead of the field name. That is why the variable that gets bound in the Scheme environment is my-func-1, not myfunc1.

Instead of (require <MyModule>), you can do (load "MyModule") or (load "MyModule.class"). If you’re not using Scheme, you can use Kawa’s -f option:

$ kawa -f MyModule --xquery --
#|kawa:1|# my-func-1(3+4)
<list>1 7</list>

If you need to do some more complex calculations when a module is loaded, you can put them in a run method, and have the module implement Runnable:

public class MyModule implements Runnable
{
  public void run ()
  {
    Interpreter interp = Interpreter.getInterpreter();
    Object arg = Boolean.TRUE;
    interp.defineFunction (new MyFunc ("my-func-t", arg));
    System.err.println("MyModule loaded");
  }
}

Loading MyModule causes "MyModule loaded" to be printed, and my-func-t to be defined. Using Interpreter’s defineFunction method is recommended because it does the righ things even for languages like Common Lisp that use separate "namespaces" for variables and functions.

A final trick is that you can have a Procedure be its own module:

import gnu.mapping.*;
import gnu.math.*;
public class MyFunc2 extends Procedure2
{
  public MyFunc(String name)
  {
    super(name);
  }

  public Object apply2 (Object arg1, arg2)
  {
    return gnu.lists.Pair.make(arg1, arg2);
  }

  public static final MyFunc myfunc1 = new MyFunc("my-func-2);
}