When kawa -C
compiles (see Compiling to a set of .class files) 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); }