From: Matthias R. <mat...@so...> - 2002-06-02 10:24:35
|
Hi JSchemers, I've been following the recent "SISC and JScheme java interfaces" thread. As the author of SISC's Scheme-to-Java interface I figured it might be a good idea to explain some of its features and the design rationale behind them. The main differences between the SISC and JScheme approach are the following: 1) JScheme uses special syntax to invoke Java methods. SISC uses a set of primitives, procedures and macros to represent Java methods as generic procedures. The special syntax approach makes JScheme code very compact and readable, at the expense of chewing up a portion of the name space. In SISC every Java method has to declared using (define-generic foo ...) One pervasive concept throughout SISC's generic procedure mechanism is that of chaining - the chainee procedure gets invoked when the chainer procedure cannot find a matching method on invocation. The most common place where this is used is in order to add Scheme-level methods "in front" of Java methods, basically performing a kind of overloading. This means you can do things like this: (define-generic app (generic-java-procedure 'append)) (define-method (app (next: next-method) (<jstringbuffer> buf) . rest) (for-each (lambda (x) (next-method buf x)) rest) buf) (define sb (make <jstringbuffer> (->jstring "foo"))) (app sb (->jstring "foo") (->jint 1) (->jstring "bar")) ;==> stringbuffer now contains "foofoo1bar". Note that because SISC uses no special notation for calling Java methods, the same name as before can be used to call the new procedure. Here's a list of some other interesting features of SISC generic procedures: * they are lexically scoped; One can define the same generic procedure name in multiple scopes/modules withouth them inteferring with each other. * the first parameter is not special. It participates in method selection just like any other parameter. * The names of generic procedures by default are mapped to Java method names via an "unschemification" conversion, e.g. foo-bar-baz? becomes isFooBarBaz. This further blurs the distinction between doing "schemy" things and "javay" things. * constructors are generic procedures too. * next-method parameter for calling next matching method Note that at the moment SISC generic procedures can only operate on Java objects, not Scheme objects (unless, of course, you use their Java representation). That will change soon though with the introduction of a SISC type system and object system. 2) JScheme uses special syntax to access static and instance variables. Field access in SISC is done by calling the Java object with a slot name and optional value. SISC consequently allows one to write (map 'foo (list obj1 obj2 obj3)) to get the value of the field "foo" of three objects. I'm not sure what the equivalent would look like in JScheme. 3) JScheme defines special Listener classes that allow the definition of Java listeners in Scheme. SISC has a generic proxy mechanims that allows any Java interface to be implemented in Scheme. SISC's generic proxy mechanism is somewhat clunky at the moment but this will improve considerably with the introduction of the type and object system. 4) Many JScheme types map directly to standard Java types, making type conversion unnecessary when objects cross the Scheme/Java divide. In SISC explicit conversion is needed. The JScheme solution results in incredibly concise code at the expense of losing some standards compliance (e.g. mutable strings) and generality. The precise reasons, straight from the SISC manual, why SISC opted for explict rather than implict conversion are: * For some Scheme types, such as numbers, the mapping to Java types is one-to-many, e.g. a Scheme number could be converted to a byte, short, int, etc. This causes ambiguities when automatic conversion of parameters is attempted. * Some Java types have several corresponding Scheme types, e.g. a Java array could be represented as Scheme list or vector - this causes ambiguities when automatic conversion of results is attempted. * Conversion carries an overhead that can be significant. For instance, Java strings have to be copied "by value" to Scheme strings since the former are immutable and the latter aren't. In a chained-call scenario, i.e. where the results of one method invocation are passed as arguments to another, the conversion is unnecessary and a wasted effort. * Conversion breaks the object identity relationship. In a chained-call scenario, the identities of the objects passed to the second call are different from the ones returned by the first. This causes problems if the called Java code relies on the object identity being preserved. * Conversion conflicts with generic procedures. The method selection mechanism employed by generic procedures relies on objects having exactly one type. Automatic conversion effectively gives objects more than one type - their original type and the type of the objects they can be converted to. While it would be technically possible to devise a method selection algorithm that accommodates this, the algorithm would impose a substantial overhead on generic procedure invocation and also make it significantly harder for users to predict which method will be selected when invoking a generic procedure with a particular set of arguments. Matthias |