00 | Previous | Up | Next

LISP -- a Language for Internet Scripting and Programming

Timothy J. Hickey, Brandeis University, Waltham, MA

Peter Norvig, Junglee Corporation, Sunnyvale, CA

Kenneth R. Anderson, BBN Technologies, Cambridge, MA

01 | Previous | Up | Next

Before we begin

Lisp at BBN:

  • 1963 L. Peter Deutch, then a high school student, wrote a LISP for the PDP 1, like Lisp 1.5.

  • 1970's BBN LISP becames INTERLISP

  • 1980's Lisp Machines Lispm, Dorado, Jerico

  • 1990 Fielded two LISP applications during Desert Shield.

These slides were formatted using CL-HTTP.

02 | Previous | Up | Next

Overview

  • SILK - Scheme in 50K

  • JLIB - Library for Applet development

  • Web programming

  • Scripting

  • Debugging

  • Implementation

  • Other implementations

  • Meta scripting
03 | Previous | Up | Next

Web programming is harder than it looks

  • Using the web - easy, just click.

  • Writing web pages - a little harder, HTML.

  • Applet writing - requires real programming.

  • Server - requires understanding protocols and programming: HTTP, MIME, HTML, CGI, ...
04 | Previous | Up | Next

Design Goals - Students writing applets in 50 minutes

  • Easy access - just visit the web page

  • Simple applet creation
    • Declarative interface to most commonly used parts of Java libraries
    • Students write applets after one 50 minute lecture.
    • In Java this takes 10 hours.
    • Graphical constructs are introduced along with everything else, not as an advanced topic.

  • Simple debugging - better than stack trace.

  • Simple incorporation in web pages - theaded object interpreter.

  • Fast download
    • Tiny runtime and interpreter.
    • 8 second download.
05 | Previous | Up | Next

The Primitive LISP-Java Interface

(class CLASSNAME) (constructor CLASSNAME ARG1TYPE .... ARGNTYPE) (method METHODNAME CLASSNAME ARG1TYPE .... ARGNTYPE)

Example: use the isProbablyPrime() method of the BigInteger class to determine if a number is prime:

(define BigInteger (constructor "java.math.BigInteger" "java.lang.String")) ==> BigInteger (define isProbablePrime (method "isProbablePrime" "java.math.BigInteger" "int")) ==> isProbablePrime (isProbablePrime (BigInteger "1231231231231231231231") 10) ==> false

Big integer demo

06 | Previous | Up | Next

Build your own additional metalevel access

(define getClass (method "getClass" "java.lang.Object")) (define getField (method "getField" "java.lang.Class" "java.lang.String")) (define getFieldValue (method "get" "java.lang.reflect.Field" "java.lang.Object")) (define setFieldValue! (method "set" "java.lang.reflect.Field" "java.lang.Object" "java.lang.Object")) (define field-getter (lambda (class-name field-name) (let ((field (getField (class class-name) field-name))) (lambda (object) (getFieldValue field object))))) (define field-setter (lambda (class-name field-name) (let ((field (getField (class class-name) field-name))) (lambda (object value) (setFieldValue! field object value)))))

Field Getter/Setter

07 | Previous | Up | Next

Declarative GUI programming

Declarative minilanguages are easy in Lisp:

  • uniform syntax - no distinction between method, function, or constructor.
  • multiple arguments - easy grouping.
  • macros
  • constructing a computation is indistinguishable from constructing a value.

JLIB interface to AWT:

(window height width name c1 ... cn) (row c1 c2 ... cn) (col c1 c2 ... cn) (grid rows cols c1 ... cn) (pad component procedure) (read-expr component) (write-expr component expr)

About 250 defining forms provide access to AWT.

08 | Previous | Up | Next

Hello World Example

(define w (window 200 200 "hello" (col (label "Hello World") (pad (button "hide") (lambda (e) (hide w)))))) (show w)

Code follows shape of window.

As easy as Tcl/TK.

Limited, but idea for quick prototypes.

More control can be gained by using the Lisp-Java interface

or extending the Scheme API.

First Example

09 | Previous | Up | Next

Body mass example

BMI example ;; BMI.scm -- a Body Mass Index Calculator (define (bmi-panel) (define height (textfield "" 20)) (define weight (textfield "" 20)) (define result (textfield "" 20)) (define (compute-BMI H W) (define (sq x) (* x x)) (/ (/ W 2.2) (sq (/ H 39.37)))) (col (grid 2 2 (label "Height in inches") height (label "Weight in pounds") weight) (pad (button "Compute BMI") (lambda (e) (write-expr result (compute-BMI (read-expr height) (read-expr weight))))) result)) (define (bmi-window) (define win (window 400 400 "BMI" (bmi-panel))) (validate win) (pack win) (show win))
10 | Previous | Up | Next

LISP Applet

LISP Applets are similar to Java Applets:

<applet code = "jlib.Applet.class" archive = "scheme.jar" codebase = ".." height = 600 width = 600 > <param name="program" value="bmi.scm"> You need a Java-enable browser to run the WebSILK applet </applet>

The jlib.Applet class uses the "program" parameter to find what LISP program to load.

An alternative would be to compile LISP to Java

11 | Previous | Up | Next

Interactive Java Debugging and Scripting

  • Typical java debugging:
    public void main(String[] arg) {
      // ... simple testing ...
    }
    

  • Proper testing can take a lot of code.

  • Interactive testing takes even more.

  • Testing tools, such as JUnit, make testing easier.

  • But still may not be interactive enough.

  • LISP provides an interactive test harness for Java.
12 | Previous | Up | Next

Example: interacting with an interval arithmetic solver

Lift Java interval arithmetic solver into LISP: Interval Arithmetic

;;;;;; MAP JAVA METHODS AND CONSTRUCTORS INTO LISP ;; create a table which stores variable-interval pairs (define RealIntervalTable (constructor "ia_parser.RealIntervalTable")) ==> RealIntervalTable ;; parse a string into an internal representation of a constraint (define parseString (method "parseString" "ia_parser.Parser" "java.lang.String")) ==> parseString ;; create a table of variable-interval pairs from the constraint Expression (define storeVarBindings (method "bindVars" "ia_parser.Exp" "ia_parser.RealIntervalTable")) ==> storeVarBindings ;; use the constraint to narrow the intervals it contains (define narrow (method "narrow" "ia_parser.Exp")) ==> narrow
13 | Previous | Up | Next

Example continued: exercise the solver

After defining these procedures, we can interactively call these imported procedures and examine the results.

;;;;;; INTERACTIVELY TEST THE BEHAVIOR OF THESE PROCEDURES (define c (parseString "x = cos(x);")) ==> c c ==> "x = cos(x);" (define T (RealIntervalTable)) ==> T (storeVarBindings c T) ==> () T ==> <x -> [-Infinity, Infinity]> (narrow c) ==> true T ==> <x -> [-1,1]> (narrow c) ==> true T ==> <x -> [0.540302,1]> (narrow c) ==> true T ==> <x -> [0.540302,0.857553]>
14 | Previous | Up | Next

LISP can provide graphical interaction

A few lines of LISP can provide a GUI for interacting or testing Java code. scripting

Here is a GUI for the interval arithmetic solver:

;;;;;; Script a narrowing procedure (define tab (RealIntervalTable)) (define (narrow-expr str) (define con (parseString str)) (storeVarBindings con tab) (if (narrow con) tab "NO SOLUTION")) ;;;;;; Create GUI components for user I/O (define solvewin (window 300 300 "IAsolver")) (define constraint (textarea 10 60)) (define variables (textarea 5 60)) ;;;;;; Lay out GUI components and attach actions (add solvewin (col constraint (pad (button "solve") (lambda (e) (write-expr variables (narrow-expr (read_from constraint))))) variables)) (validate solvewin) (pack solvewin) (show solvewin)
15 | Previous | Up | Next

Scheme Implementations

+ Scheme is a powerful but tiny language.

+ 50 page manual

+ Scheme interpreters are easy to build and relatively efficient.

+ High performance compilation well understood.

+/- Many Scheme implementations with broad range of performance

16 | Previous | Up | Next

SILK design issues

Original version of SILK done by Peter Norvig

Initially, 650 lines written in about 20 hours.

Within a few months i could handle Aubrey Jaffrey's online r4rstest.scm test suite.

Here is the initial class structure:

SchemePrimitives        - Scheme code in a big string.
SchemeUtils             - Scheme like static methods, cons, car, cdr, ...
    Scheme              - Interpreter.
    Environment
    Pair
    InputPort
    Procedure
        Primitive
        JavaMethod
        Continuation
        Closure
            Macro
17 | Previous | Up | Next

Things you need to do to make a Scheme

  • Read InputPort.reader()
  • Write SchemeUtils.stringify() does most of the output work.

    So just use java.io.PrintWriter

  • Eval Scheme.eval() interprets s-expressions. Tail recursive.
  • ApplyScheme.apply() delegates to Procedure.apply()
  • Memory management Java handles this.
  • Run-time stack

    Uses Java's run-time stack.

    No call/cc - just throw like continuations.

18 | Previous | Up | Next

Primitive functions

two approaches:

Inner classes:

  new Primitive("list", env, 0, n) {
    public Object apply(Object args) { return args; }};

  new Primitive("null?", env, 1) {
    public Object apply(Object args) { return truth(first(args) == null); }};

Big switch

  final static int ... LIST = 16, ... NULLQ = 23, ... // Define constants
...
  public static Environment installPrimitives(Environment env)  {

    int n = Integer.MAX_VALUE;

    env.defPrim("list",  LIST,  0, n) // list has 0 to n arguments
      ...
      .defPrim("null?", NULLQ, 1)    // null? has exactly one argument
      ...;
}

public Object apply(Scheme interpreter, Object args) {
  ...
  Object x = first(args);
  switch(idNumber) {
    ...
  case LIST: return args;
    ...
  case NULLQ: return truth(x == null);
  }
}
19 | Previous | Up | Next

Primitive data types

Scheme has a dozen or so data types

SILK uses the simplest simplest possible existing Java types:

Scheme Type Java Type
(version 1)
Java Type
(Version 2)
Notes
pair Pair Pair Java has no equivalent to use.
empty list (null) (null) We could have Pair and Empty as subclasses of List, or EMPTY could be a static var.  But it's easiest to have null for ().  This means only static methods on lists. (It also means we can't use Hashtables to store Scheme objects!)
boolean Boolean Boolean Simple enough. Note that the empty list is not considered false in Scheme, only #f is false.
inexact number
exact number
Double Double
Integer
One could add BigDecimal, but R5RS doesn't requuire it.  
string char[] char[] Can't be String (they're immutable) unless we want to give up on implementing string-set!.  Could be StringBuffer, but char[] is simpler and sufficient.
character Character Character Simple enough.
symbol String Symbol Strings (once interned) have the right properties for symbols, but it gets confusing when we interface to Java code that uses Strings for other things. So version 2 introduces a Symbol class, which makes things less confusing, and allows global variable lookup to be faster.
vector Object[] Object[] Because Vector is more powerful than is needed.
procedure Procedure 
Primitive 
Closure 
SchemePrimitives 
Macro 
Continuation 
JavaMethod
  Procedure is the abstract superclass. 
Primitive is a Scheme primitive. 
Closure is a user-defined function. 
SchemePrimitives holds Scheme code in a string; loaded at start-up. 
Macro is a subclass of Closure for doing macros. It has an expand method. 
Continuation is what you get from call/cc
JavaMethod encapsulates a Java Method.
input port InputPort InputPort It would have been simpler to use Reader, the code for read needs to be put somewhere, and hence this class.
output port PrintWriter PrintWriter We could have had a class to hold the stringify method, but its not really necessary to have another class.
interpreter Scheme  Scheme The class Scheme implements eval, apply, and a few other methods. Note you can instantiate several different Schemes at once. 
environment Environment  Environment
GlobalEnvironment
LambdaEnvironment
An environment has lists of variables and values. It uses linear search, which is slow, especially in the global environment.
continuation Continuation Continuation call/cc builds a Continuation procedure which, when applied, throws a RuntimeException. call/cc then sets up a try/catch block that catches that identical exception only. Got it?
error RuntimeException RuntimeException RuntimeExceptions are used because they do not have to be declared and so the SILK code will not be cluttered up with throws clauses.
symbol table (none) Hashtable 
(in Symbol)
In version 1 we use the internal Hashtable that Java uses in its String. intern() method.  That is, symbols are represented as Strings that have been interned. In version 2, we maintain our own String => Symbol Hashtable as a static variable in Symbol.
variable (none) (none) Variables are refered to implicitly by their position in Environments.

 

20 | Previous | Up | Next

Implementation statistics

Scheme implementation statistics
Implementation java files lines scm files lines
Silk 1.0 12 1905 0 0
Silk 2.0 20 27781 1400
Skij 28 2523 44 2844
Jaja 66 5760 ? ?
Kawa 273 16629 14 708

21 | Previous | Up | Next

Silk 1.0

Designed to be tiny.

Written in a compact Scheme style.

Existing Java classes used wherever possible.

Java null is the empty list.

Extensions to Java are minimal.

22 | Previous | Up | Next

Silk 2.0

Silk 2.0 provided a higher performance evaluator by splitting eval() into two steps:

  /** Evaluate an object, x, in an environment.  First compile the
  * meaning of the object, and then call eval method on the meaning. **/
  public static Object eval(Object x, Environment env) { 
    return Code.eval(Code.toCode(x,env), Frame.EMPTY);
  }

Tail recursion is accomplished by letting eval return a Code object:

  static Object eval(Object expr, Frame f) {
    if (expr instanceof Code) {
      expr = ((Code)expr).eval(f); 
      while (expr instanceof Code) 
        expr = ((Code)expr).eval(Frame.EMPTY);         
    }
    return expr;
  }

Thus the inner loop of the interpreter is replaced by method dispatch.

Currently, a relatively small number of Code inner classes are defined.

Further performance can be gained by generating more refined classes to handle common special cases more efficiently.

This is a simple form of peephole optimization.

23 | Previous | Up | Next

Skij

Skij is a Scheme advertised as a scripting extension for Java.

Similar in capabilities to Silk 1.5

Extensive Java support including generic operations:

(peek) and (poke)            - reading/writing slots
(invoke) and (invoke-static) - invoking methods
(new)                        - object construction

Scheme handles Java exceptions.

Serializable

24 | Previous | Up | Next

Jaja

Based on Christian Queinnec's wonderful book "Lisp in Small Pieces"

Compiler written in Scheme 1/3 the size of a Java version.

Comparable to Silk

Written in a object oriented style.

Like SILK, uses the shared superclass trick.

Each Scheme type is one or more Java classes.

nil is prepresented as an instance of Emptylist.

All Jaja objects are serializable.

25 | Previous | Up | Next

Kawa

Ambitious Scheme implementation.

Java classes useable for implementing other languages.

Scheme to Java byte code compiler.

Each function becomes a Java class compiled and loaded at runtime.

26 | Previous | Up | Next

Future

Generic Functions

Compiler

Meta scripting

Network Citizen

27 | Previous | Up | Next

Generic Functions

  • Initially based on Tiny CLOS, partly in Java.

  • Generic acccess to Java objects as in Skij

  • Scheme methods specialized on Java objects.

  • Dynamic Java (redefinable Java methods)
28 | Previous | Up | Next

Compiler

Silk 1.4:

  • File based compiler

  • Basically partial evaluation of the interpreter.

Silk 2.0

  • Compiles to threaded objects.

  • Similar to Marc Feeley's closure compiler.

  • Can be further optiized.

  • File based interface compiler used by Jlib.

Scheme to Java compiler based on?

  • Jaja LiSP

  • Kawa

  • Hobbit
29 | Previous | Up | Next

Meta Scripting

A simpiler variant of a compiler:

write Scheme procedures that generate new Java classes from old ones.

Use reflection of existing class to construct a new one with additional capabilites.

Example: Class Primitive implements 150 SILK primitives. Representing this knowledge in Scheme would let the class and its necessary glue code generated automatically.

SILK would become even smaller.

30 | Previous | Up | Next

Example: add tracing get and put of a specific hashtable

;;; Access your Java application:
(define app (.. your java application ...))

;;; Extract a component of the applcation, here a table.
(define table (get-field app "table"))

;;; Define a metawrapper class.
(define wrapper-class (metawrap (class table)))

;;; Create new wrapped table.
(define new-table (new wrapper-class table))

;;; Trace the methods you want:
(trace new-table "get")
(trace new-table "put")

;;; Reinstall the table 
(set-field app "table" new-table)
31 | Previous | Up | Next

What's going on?

  • (metawrap (class table)) - constructs a subclass of class that
    • delegates to an instance of class
    • has method invocation controlled by a metaobject.

  • (new wrapper-class table) - constructs object delegating to table

  • (trace new-table "get") - informs metaobject to trace method get().

    Java can do this because it has reflection.

    Scheme can do this interactively.

    We understand metaobjects.

32 | Previous | Up | Next

Network citizenship and Java integration

  • Thread support.
  • Serialization.
  • Networking.

33 | Previous | Up | Next

Conclusion

  • Don't under estimate the power of a tiny LISP

  • Hybrid LISP systems work
    • embedded in applications - EMACS, GIMP
    • Client/server - CORBA, CL-HTTP
    • web - SILK, Kawa, Jaja Skij
    • Meta is better

34 | Previous | Up | Next

35 | Previous | Up | Next

The class structure for SILK 2.0 is shown below:

Object
  Frame
  SchemeUtils            - Scheme like static methods.
    Code                 - What things compile into.
    Environment
      GlobalEnvironment
      LambdaEnvironment
    InputPort
    Pair
    Procedure             - Something you can apply().
      Closure
      Continuation      
      JavaArgs            - Deals with Java arguments.
        JavaConstructor   - Java constructor, see (constructor).
        JavaMethod        - Java method, See (method).
      Primitive           - Primitive Procedure defined as big switch.
    Scheme                - Scheme main loop.
    Syntax
  SchemePrimitives        - String containing scheme code.
  Symbol
  Throwable
    Exception
      RuntimeException
        SchemeException