From: Timothy H. <tim...@ma...> - 2002-06-03 04:28:30
|
Hi Matthias, Thanks for giving us your insights on the SISC java interface implementation... On Sunday, June 2, 2002, at 06:16 AM, Matthias Radestock wrote: > 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. The name space is chewed up, but not fully digested! In fact, Jscheme just provides initial bindings to the javadot symbols, but you can easily redefine them. e.g., > (define Date. (let ((d Date.)) (lambda() (string-append "the date is " (d))))) > (Date.) "the date is Sun Jun 02 23:25:02 EDT 2002" > (define (f Date.) (* Date. Date.)) {jsint.Closure f[1] (Date.)} > (f 4) 16 Thus, javadot can be though of as a loading a large module of primitives (although the bindings are not made until the first time the symbol value is looked up) > 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. Very nice! The chaining idea is an interesting way to add functionality to java classes or to other scheme procedures. Its seems to be similar to inheritance, except that the parent procedures have priority rather than the other way round. Jscheme does have a form of multi-methods similar to SISC's define-method, but rather than using chaining it lets you bind multiple functions to the same name and then selects the best match using dynamic dispatch, e.g., the following code (take from jscheme/src/elf/iterate.scm) defines an iterate method (iterate CompoundObject Action) which is a "for-each" for all sorts of compound objects: (import "java.lang.Object") (import "java.lang.String") (define-method (iterate (mapper jsint.Procedure) action) (mapper action)) ;;; Hashtable and Vector specialization are only needed for JDK 1.1 (define-method (iterate (items java.util.Hashtable) action) (iterate (.elements items) action)) (define-method (iterate (items java.util.Vector) action) (iterate (.elements items) action)) (define-method (iterate (items java.util.Enumeration) action) (let loop () (if (.hasMoreElements items) (begin (action (.nextElement items)) (loop))))) (define-method (iterate (items jsint.Pair) action) (let loop ((items items)) (if (pair? items) (begin (action (car items)) (loop (cdr items)))))) (define-method (iterate (items Object[]) action) (let loop ((i 0) (L (vector-length items))) (if (< i L) (begin (action (vector-ref items i)) (loop (+ i 1) L))))) (define-method (iterate (items String) action) (let loop ((i 0) (L (string-length items))) (if (< i L) (begin (action (string-ref items i)) (loop (+ i 1) L))))) ....etc... (see jscheme/src/elf/iterate.scm for more) > > 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. Nice. Jscheme generic functions as defined using "define-method" are globally scoped. The javadot symbols are also defined in the outermost scope. > > * the first parameter is not special. It participates in method > selection just like any other parameter. Jscheme's define-method also does not give any special significance to the first parameter. Jscheme's javadot does give special signficance to the first parameter for instance methods and instance fields. > > * 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. This is cute! But its not essential, is it? Couldn't you just as well have used straight Java syntax here... > > * constructors are generic procedures too. ?????????? Do you use chaining for selecting the appropriate constructor or method for a straight Java call? e.g. if there are five different constructors, does SISC select the one that best matches the arguments or the one that "first" matches the arguments (with respect to some ordering...) ?????????? > > * 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. I look forward to seeing this, but it may be confusing having two type systems (Java and SISC) to consider. I think Jython has some experience dealing with these issues. > > > 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. We would use a similar syntax. > (map .first$ '((a b) (c d) (e f))) '(a c e) The .first$ is an accessor if called with one argument and a setter if called with two. Static variables work differently and are bound to the classname, e.g. Math.PI$ Math.E$. > > > 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. Jscheme can also access the proxy listener directly, but we don't yet have any special syntax, e.g. one can define the following procedure which creates a proxy object given a list of interfaces and a handler: (define (make-proxy-object interfacelist handler) (Proxy.newProxyInstance (.getClassLoader (first interfacelist)) (list->array Class.class interfacelist) (elf.SchemeInvocationHandler. (lambda (proxy method argv) (handler proxy method argv))))) This can then be used to create proxy objects on the fly: Here's an example of creating a comparator for the sorting procedure: (define (mycompare fn) (make-proxy-object (list java.util.Comparator.class) (lambda (proxy method argv) (case (.getName method) (("compare") (let ((L (array->list argv))) (cond ((apply fn L) -1) ((apply fn L) 0) (else 1)))) (else (.invoke method 'myCompare argv)))))) (define (mysort array) (java.util.Arrays.sort array (mycompare <))) (define z #(2 7 1 8 2 8 1 8 2 8 4 5 9 0 4 5)) (mysort z) > > 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. Indeed, Jscheme is probably best thought of as a Scheme-skin for Java. It really is programming in Java, but the syntax is different and some higher order procedure patterns have been incorporated into the syntax. We're currentlly working on a syntax for implementing Java classes using Jscheme, which would complete this analogy. > > 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. Makes sense (but couldn't a best fit mapping be made by default, i.e. choose the smallest applicable type ....) > > * 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. The main problem is conversion from Scheme to Java (for procedure calls), i.e., what do Strings, Lists, and Vectors maps to. Converting back to Scheme could just do the reverse. We chose to map lists to a new Java class (jsint.Pair), vectors to Object[], and procedures to a new Java type jsint.Procedure. This strategy is to simply embed Scheme types in the Java types. This wouldn't work for SISC as easily as Strings must be mutable. Pair's and Vectors might map naturally though.... > > * 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. Conversion is indeed a tricky problem. It seems mandated by the need to have mutable Strings and I don't see a way around it for SISC. For a while we toyed with the idea of having Scheme Symbols get mapped to Java Strings. Both are immutable and both can be interned.... Maybe that would work for SISC as well.... Then you could just use a straight embedding of all other Scheme objects into Java by adding the Pair, Procedure, and Continuation classes to the public Java API for SISC.... Do you think this might work??? Anyway, I want to thank you for taking the time to write this explanation. It helps clarify the similarities and differences. I want to look at the way we handle define-method, perhaps we should allow it to be used over javadot names and to let it use the " . rest)" notation as SISC's define-method does... (e.g. (define-method (.append (sb StringBuffer) . rest) ....) I'm still interested in the possibility of adding javadot notation to SISC. It would be a pretty simple extension and could be though of as loading a program that lazily defines 100,000 new primitives ..... It doesn't strictly violate the Scheme semantics, but it does greatly increase the size of the namespace... I think Geoff's suggestions was to look into a way of finding a canonical method for converting SISC values into Scheme values (e.g. numbers go to ints if possible, the longs if possible, then doubles, the floats, then BigIntegers or BigDecimals, but inexacts always goto doubles, floats, and BigDecimals. Converting back is more straightforward...... Thus, SISC could support a version of javadot with automatic conversion. It might also be interesting to allow the use of Java literals (e.g. 5b, 7.2f, 30L) which could be converted into SISC-wrapped Java objects.... It might even be possible to have javadot and the current SISC/java interface work concurrently... ---Tim--- > > > Matthias > > > _______________________________________________________________ > > Don't miss the 2002 Sprint PCS Application Developer's Conference > August 25-28 in Las Vegas -- http://devcon.sprintpcs.com/adp/index.cfm > > _______________________________________________ > Jscheme-devel mailing list > Jsc...@li... > https://lists.sourceforge.net/lists/listinfo/jscheme-devel > |