A version of SILK with generic functions

SILK is a compact scheme dialect written in Java that can add powerful capabilites to Java applications, such as:

The original version of SILK was written by Peter Norvig. Tim Hickey added JLIB a system for easily writing applets.

Generic functions have been added to SILK. A paper (html version, Postscript version) describing them has been submitted to Reflection '99. You can also see a live demo of many of the examples presented in the talk.

An earlier version of SILK was described at the Lisp in the Main Stream conference, November 1998:

Scheme API's [Incomplete]:

Running SILK

SILK and your application can have access to each other if they are in the same classpath, or more specifically, come from the same ClassLoader.

SILK's operation and capabiliies depened on which ClassLoader is used and how SILK is to run. See Example Scripts below. Either Down load everything, or if you are at BBN, you can build SILK from CVS. Everything works in JDK 1.2. However most of SILK will run fine in JDK 1.1 either as an application or an applet. What doesn't currently work in JDK 1.1 is related to ClassLoaders.

A JDK 1.2 version of SILK is provided in jar/scheme.jar which also includes scheme software in *.scm files.with generic function extensions in src/generic. As the live demo demonstrates, this jar will also work in JDK 1.1, but some of the classes will not work.

You can build your own version using one of several tiny scripts:

Example Scripts

Here are some interesting variants to use or copy and customize:

Main classes

The two main classes of SILK are silk.Scheme and silk.EmbeddedScheme. Their main(String[] args) method treat the args as a set of files that are loaded. Then a read-eval-print loop is started. The class silk.EmbeddedScheme automatically load "src/generic/load.scm", while silk.Scheme does not. It also static methods that can be useful when SILK is used as part of an application.

File lookup

The file opening done command line processing, and by (open-input-file) and (load) has been generalized to allow relative file naming and use of URL's. This allows *.scm files to be added to a jar file without modification. The lookup strategy for finding a file named name is to try each of the following until one succeeds:
  1. Open the file name (application only).
  2. Prepend each of the elements of the list scheme.roots to name and open that file (application only).
  3. Use the classloader to open name as a resource, for example in a *.jar file.
  4. Open name as a URL.
  5. Open name relative to documentbase (applet only).
When silk.Scheme starts, it looks for a "scheme.eval" system property. The value of this property is evaluated just after the primitives are loaded. The primary purpose of this is to set the scheme.roots variable. So for example, the way i use it is:
java "-Dscheme.eval=(set! scheme.roots '(d:/java/elf d:/java/silk))" silk.EmbeddedScheme src/classgen/compile.scm ulps/jdbc.scm 
So, the file compile.scm is found in the scheme.jar and ulps/jdbc.scm is found relative to d:/java/elf.

Other information

Known problems

JDK 1.1 limitations
The core of SILK with generic functions is supported in JDK 1.1. However, the following features are missing or restricted:
  • Because of access limitations, (describe object) only describes public fields. In JDK 1.2, all the fields of an object can be seen.
  • (get-field) and (set-field) are similarly limited.
  • build.CompilingClassLoader and silk.SchemeCLassLoader are currenly JDK 1.2 specific.
  • Classes that import from the package javax.swing will not compile and will not theirfore be in jar/scheme.jar. elf.GCMonitor is such an example class.
Applet and JLIB issues
SILK will run in an Applet using JLIB. See the examples in the applet directory. However, to use the generic extensions, you must currently put the applet's .html and .scm file in the src/generic directory. To use JLIB in the generic environment, use the libraries/Java2.scm version which has been changed slightly to avoid name conflicts. For a working example, see src/scheme/schemegui.html.
Applet loads slow
On a 266 MHz Pentium II, in Netscape 4.5, the time it takes to start scheme is about 20 to 35 seconds. When using scheme as an application, it takes about a second in JDK 1.2 and JDK 1.1.6.

On an iteration benchmark (essentially a consing benchmark):

(define (f x) (if (= x 0) #f (f (- x 1))))
(time (f 1000000))
Netscape seems to be twice as slow ast JDK 1.2. JDK 1.1.6 is slightly faster. Including this difference, we still have a factor of 10 to account for.
Java Strings
Strings in Scheme are represented as Java char[]. When Scheme calls a Java method the expects a String, a Scheme string or symbol is converted into a Java String. When a Java method returns a String as a value, it is converted into a Scheme symbol (since they are immutable). Thus (toString x) converts x to a symbol, rather than a string. So if you want a string, say (symbol->string (toString x)) or (string-append x).
String memory leak
A memory leak occurs because Java Strings are converted into Scheme symbols, which are interned. So, a application such as src/elf/GCmonitor.scm which essentially generates a random string every half second can continually grow. GCMonior grows at 1MB/hour. The Java version does not grow.
Primitive arrays
A Scheme array is represented as a Java Object[]. Primitive arrays, such as String[] can be constructed using (java-vector CLASS element1 ...) which constructs an array with elements of type CLASS containing the elements element1 .... The procedure (make-java-vector CLASS SIZE) will make a particular class of Java array of size SIZE. The procedures(vector-ref) and (vector-set!) can be used to get or set an array element of any type of array.
Primitive values
However, to insert an element into an primitive array, one must construct an appopriae value. The Scheme side of SILK supports only a few primitive types: boolean, char, int, and double. So, filling an array of a primitive type can be tricky. For example, to produce a value that would go into a byte[], you need to construct a Byte in Scheme. Byte has two constructors:
  public Byte(String);
  public Byte(byte);
The second version can't be used in Scheme unless you have a Byte already. This forces you to use the kludge:
  (import "java.lang.Byte")
  (Byte (string-append 3))  ; returns a Byte representing 3.
See the class silk.Coerce which provides conversion between primitive types.
Method lookup ambiguities
Generic function method lookup uses the runtime types of its arguments to decide which method to invoke. There are some ambiguity to this process. For example, a null argument provides no runtime type information. However, while the lookup process works generally, it has some flaws that cause it to fail occasionally. The workaround for such cases is to use (method)to select the method you want to invoke.
Few numeric types
SILK's Scheme side only support Integer and Double as numeric types. However, SILK's implementation will somewhat handle the other types. For example:
> (import "java.lang.Shor")
importing java.lang.Short in 40 ms.
> (+ (Short "3") (Short "4"))
7.0
Precision can be lost, so when dealing with Long's, convert them to Double's first. Here's code that deals with file cration times:
(define (last-modified file)
  ;; KRA 05AUG99: We need this kludge because SILK does not deal with Longs.
  (Double (toString (lastModified file))))

(define (needs-recompile class-file java-file)
  (if (not (exists java-file)) (error java-file " does not exist!"))
  (or (not (exists class-file))
      (> (last-modified java-file) (last-modified class-file))))
Multiple Schemes
It is possible to have multiple Schemes per a Java application. However, each one will read in primitives and generic extensions. There is currently no way for Schemes to share the same global environment.

Release notes

Sept 20, 1999

  • This version is again JDK 1.1 compatible.
  • build\bootstrap1.bash for building in JDK 1.1.
  • Rewrite of class build.CompilingClassLoader.
  • Get src/scheme/schemegui.html working again.

Sept 15, 1999

  • You can now provide input and output streams to be used by silk.Scheme.
  • SILK now under CVS and builds itself using build.CompilingClassLoader.
  • New classes:
    • build.CompilingClassLoader - Compile as you run your application.
    • silk.EmbeddedScheme - Convenient for hiding Scheme inside your application.
    • silk.Coerce - conversion methods between primitive types.
    • silk.SchemeClassLoader
    • silk.ExitException - thrown by (exit).
  • New Scheme files:
    • classgen/compile.scm - extensive compiling support.
    • build/update.scm - used to build silk.
    • generic/trace.scm - very simple trace facility.

June 15, 1999

  • Draft Reflection '99 slides and demo applet.
  • elf/GCMonitor.scm and efl/jdbc.scm examples

May 21, 1999

  • Less verbose (describe) of a class.
  • (get-field) (set-field) (get-static) and (set-static) field accessors now work with private fields when JDK 1.2 is used.
  • Loading src/classgen/compile.scm provides:
    • (compile-file file) - compiles a Java file.
    • (recompile directory) - recompiles Java files in a directory. This assumes the class file is also in the directory. Only Java files that need compiliation are compiled.

May 3, 1999

  • (describe) generic function that accesses private fields in JDK 1.2. See src/generic/describe.scm.
  • Fixed array bound bug in SchemeUtil.chr().

April 28, 1999

  • Final draft of reflection99 paper.
  • (define-method) for defining methods in scheme.
  • (vector-set!) works with any class of vector.
  • src/elf directory has GCMonitor example.

March 28, 1999

  • Performance of (import) improved by about 20%.
  • Preliminary version of macroexpand-all provided in src/generic/macroexpand.scm.
  • The standard SLIB library is provided in src/slib. To use it, edit the src/slib/silk.init file appropriately. I've tested (require 'pretty-print). Other files may work but may need to be checked for case sensitivity.