In this section we give a tutorial introduction to programming in
scheme, with a slant toward the interesting things that can be done in
Guile. Applets are so chic that they get their own section, but
this section will try to touch on all other interesting and cool aspects
of Guile, showing you how new types of problems can be solved with
Guile. Note that using Guile as a library with libguile.a is
described in its own chapter (see section Guile in a library - using libguile.a). Also note that some small examples are given in section How to start up guile - a quick tour.
To get started you need to know how to program in scheme (a dialect of LISP). Fortunately scheme is a small, clean language and is not hard to learn. It is also used in many undergraduate courses to introduce computer programming.
I will not try to teach you scheme here (although you might end up learning by example), since there are many good books on the subject, listed in section Where to find more Guile/scheme resources. (2)
Our first program is the typical scheme "hello world" program. Put the
following code in a file called hello.scm (this can be find in
`examples/scheme/hello.scm').
#!/packages/bin/guile -qb ; -*-scheme-*- (display "hello world") (quit)
The run guile on it. One way to do so is to start up guile and load this file:
<shell-prompt> guile guile> (load "hello")
Here is some code you can type at the guile> prompt to see some
of the scheme data types at work (mostly lists and vectors).
guile> (define ls (list 1 2 3 4 5 6 7)) guile> ls guile> (vector? ls) guile> (list? ls) guile> (length ls) guile> (car ls) guile> (cdr ls) guile> (car (cdr (cdr ls))) guile> (caddr ls) guile> (append ls (list 8 9 10)) guile> (reverse ls) guile> (memq 4 ls) guile> (memq 4 (reverse ls)) guile> (map sin ls) guile> (map (lambda (n) (expt n n)) ls)
guile> (define v #(1 2 3 4 5 6 7)) guile> v guile> (vector? v) guile> (list? v) guile> (vector-length v) guile> (vector-ref v 2) guile> (vector-ref (list->vector (reverse (vector->list v))) 2) guile> (vector-set! v 4 "hi there") guile> v
Here are some typical examples of using recursion to process a list.
(define (my-reverse l)
(if (null? l)
l
(append (my-reverse (cdr l)) (list (car l)))))
Suppose you have a matrix represented as a list of lists:
(define m (list (list 7 2 1 3 2 8 5 3 6) (list 4 1 1 1 3 8 9 8 1) (list 5 5 4 8 1 8 2 2 4)))
Then you could apply a certain function to each element of the matrix in the following manner:
;; applies the function func to the matrix m element-by-element;
;; returns a matrix with the result.
(define (process-matrix m func)
(map (lambda (l)
(map func l))
m))
This could then be invoked with (process-matrix m sin) or
(process-matrix m (lambda (x) (* x x))).
To print a representation of the matrix, we could define a generalized routine:
;; proc is a procedure to represent the single element,
;; row-proc is a procedure that is invoked after each row.
;; For example, proc could be (lambda (x) (begin (display x) (display " ")))
;; and row-proc could be (lambda (l) (display "\n"))
(define (represent-matrix m proc row-proc)
(for-each (lambda (l)
(begin
(for-each proc l)
(row-proc l)))
m))
And then invoke it with
(represent-matrix m
(lambda (x) (begin (display x) (display " ")))
(lambda (l) (begin (display "\n"))))
Now we write a helper routine that uses scheme closures to make objects with state that then receive messages to draw little squares.
But let us take it one step at a time. I will start by showing you a simple example of object in scheme. The object I make here represents a cell, which could be a cell in a matrix. The cell responds to commands to draw itself, to return the next cell, and so forth. Later I will add graphical messages.
;; cell-object.scm: routines for creating and manipulating cell objects
;; (the-x, the-y) is the initial position of the cell.
;; the-color is a string representing a color; must be something Tk can grok.
;; square-size is the size of the square that gets drawn.
;; (sizex, sizey) is the size of the matrix.
(define (MAKE-CELL the-x the-y the-color square-size sizex sizey)
(define (get-x) the-x)
(define (get-y) the-y)
(define (set-x! new-x)
(set! the-x new-x)
the-x)
(define (set-y! new-y)
(set! the-y new-y)
the-y)
(define (set-color! new-color)
(set! the-color new-color)
the-color)
(define (next!)
(set! the-x (+ the-x 1))
(if (>= the-x sizex)
(begin
(set! the-x 0)
(set! the-y (+ the-y 1))))
(if (>= the-y sizey)
(begin
(display "CELL next!: value of y is too big; not changing it\n")
(set! the-y (- the-y 1))))
(cons the-x the-y))
(define (draw)
(let* ((x0 (* the-x square-size))
(y0 (* the-y square-size))
(x1 (+ x0 square-size))
(y1 (+ y0 square-size)))
(display "I should draw a ")
(display the-color)
(display " rectangle with corners at ")
(display x0) (display y0) (display x1) (display y1)
))
;; self is the dispatch procedure
(define (self message)
(case message
((x) get-x)
((y) get-y)
((set-x!) set-x!)
((set-y!) set-y!)
((set-color!) set-color!)
((next!) next!)
((draw) draw)
(else (error "CELL: Unknown message -> " message))))
;; and now return the dispatch procedure
self
)
What is this procedure doing? It returns another procedure
(self) which receives a message (x, y, set-x!, set-y!, ...)
and takes an action to return or modify its state. The state consists
of the values of variables the-x, the-y, the-color
and so forth.
Here are some examples of how to use MAKE-CELL:
(define c (MAKE-CELL 0 0 "red" 10 7 9)) ((c 'x)) ((c 'y)) ;; change the x coordinate ((c 'set-x!) 5) ((c 'x)) ;; change the color ((c 'color)) ((c 'set-color!) "green") ((c 'color)) ;; now use the next! message ((c 'next!)) ((c 'x)) ((c 'y)) ;; now make things wrap around ((c 'next!)) ((c 'next!)) ((c 'next!)) ((c 'x)) ((c 'y))
You will notice that expressions like (c 'next) return procedures
that do the job, so we have to use extra parentheses to make the job
happen. This makes for somewhat awkward syntax; one way around it is to
define a send procedure:
;; send makes object syntax a bit easier; instead of saying
;; ((my-cell 'set-x!) 4)
;; you can say
;; (send my-cell 'set-x! 4)
(define (send obj . args)
(let ((first-eval (apply obj (list (car args)))))
(if (null? (cdr args))
(first-eval)
(apply first-eval (cdr args)))))
You can see that send passes the message to the object, making
sure that things are evaluated the proper number of times. You can now
type:
(send c 'x) (send c 'set-x! 5) (send c 'color) (send c 'set-color! "green") (send c 'next!) (send c 'x) (send c 'y)
This is the simplest way of introducing objects in scheme, and it does not really allow for full object oriented programming (for example, there is no inheritance). But it is useful for object based programming.
Guile comes with a couple of more complete object oriented extension to scheme: these are part of slib (see section `Object' in SLIB: the portable Scheme library and see section `Yasos' in SLIB: the portable Scheme library).
Applet is a trendy word in the mid-1990s. It is used to denote a (usually small) program which executes within the context of a parent program.
The most common use for applets is in the World Wide Web. In setting up a web page you put an applet in a document. When a browser visits that document, the applet will be executed locally on the browser's machine.
Having a program execute on the client machine allows many more things to be done with the web, but it also opens a pandora's box of safety, security and privacy issues: if you are running a stranger's program on your workstation, couldn't that program erase your files, or mail your files to some other person on the net?
Applet system implementors have solved this problem by creating safe execution enviromnents for the languages they support. Guile and Tcl have safe environments in which access to the operating system has to be authorized by the user.
In section Applets in Guile, I showed you how to create a simple applet and an enclosing html document. Then we saw how to load that into a browser (SurfIt!) and execute the applets.
From the `learn-applet-0.scm' example, we can deduce that an applet must take the following steps (this list will expand as we write more complicated applets).
(require 'Gwish) (use-library tcl) (use-interface tcl) (use-interface tclhack)
(require 'applet).
(define-applet-terminate thunk).
(tk-main-loop), or a read-eval-print loop. This is
important because you should not monopolize the CPU.
Now I will show you some other examples of Guile applets. The first one, `learn-applet-1.scm', is very much like `learn-applet-0.scm', but it creates its button in the browser window, rather than creating a new window.
;; Simple button demo; this version uses the browser window instead
;; of making a new top-level window.
(require 'Gwish)
(use-library tcl)
(use-interface tcl)
(use-interface tclhack)
(require 'applet)
(display "entering learn-applet-1\n") ; just some crap
(display "applet window name is ")
(display applet-window-name)
(display "\n")
(display "browser window name is ")
(display browser-window-name)
(display "\n")
;; Clean up before we start. ??? For some reason, this is necessary
;; or surfit! will die when we enter (tk-main-loop). This is a pity
;; because it would be nice to put the button *inside* the current
;; broser window, at the point where the applet is invoked.
(applet-newpage)
;; put the button under the broser window
(define button-name (string-append browser-window-name ".quit-1"))
(define (quit-1-callback)
(begin
(display "quitting now\n")
(destroy button-name)))
; A callback for when the user terminates the applet (boiler plate)
(define-applet-terminate (tcl-lambda () (quit-1-callback)))
(display "button-name is ")
(display button-name)
(display "\n")
(button (string->symbol button-name) :text "quit-1"
:command (tcl-lambda () (quit-1-callback)))
(display "about to pack the button\n")
(pack (name->window button-name))
(display "about to enter tk-main-loop\n")
(tk-main-loop)
(quit)
The only difference in `learn-applet-1.scm' is that the button is a
sub-widget of the browser-window-name widget, rather than being
in a new top level widget.
There is a program in the examples directory (`examples/tk/cell-object.scm') which shows a simple object based way of representing cells in a 2-dimensional matrix. To draw each cell you send it a message asking it to draw itself on a Tk canvas.
This can be incorportated into an applet, and a more advanced type of Guile applet is being developed which communicates with the web server, asks for the matrix data, and then draws it on the browser's display.
The communications part of this example is not yet written as I write (May 1996), but you can use it as an example of Tk graphical object in Guile. To run it, type
guile> (load "cell-object.scm")
and it will draw the pre-programmed matrix in a Tk window.
Here is the code as it stands right now:
;; This file contains a sort of "object-oriented" definition of a cell.
;; The "cell" can be thought of as place marker for a square on a canvas.
(require 'debug)
;; some object utilities:
;; send makes object syntax a bit easier; instead of saying
;; ((my-cell 'set-x!) 4)
;; you can say
;; (send my-cell 'set-x! 4)
(define (send obj . args)
(let ((first-eval (apply obj (list (car args)))))
(if (null? (cdr args))
(first-eval)
(apply first-eval (cdr args)))))
;; (the-x, the-y) is the initial position of the cell.
;; the-color is a string representing a color; must be something Tk can grok.
;; square-size is the size of the square that gets drawn.
;; (sizex, sizey) is the size of the matrix.
(define (MAKE-CELL the-x the-y the-color square-size sizex sizey the-canvas-w)
(define (get-x) the-x)
(define (get-y) the-y)
(define (set-x! new-x)
(set! the-x new-x)
the-x)
(define (set-y! new-y)
(set! the-y new-y)
the-y)
(define (color) the-color)
(define (set-color! new-color)
(set! the-color new-color)
the-color)
(define (next!)
(set! the-x (+ the-x 1))
(if (>= the-x sizex)
(begin
(set! the-x 0)
(set! the-y (+ the-y 1))))
(if (>= the-y sizey)
(begin
(display "CELL next!: value of y is too big; not changing it\n")
(set! the-y (- the-y 1))))
(cons the-x the-y))
(define (draw)
(let* ((x0 (* the-x square-size))
(y0 (* the-y square-size))
(x1 (+ x0 square-size))
(y1 (+ y0 square-size))
(my-canvas (cond ((string? the-canvas-w)
(eval (string->symbol the-canvas-w)))
((symbol? the-canvas-w)
(eval the-canvas-w))
(else
the-canvas-w))))
(my-canvas 'create 'rectangle x0 y0 x1 y1 :fill the-color)
))
;; self is the dispatch procedure
(define (self message)
(case message
((x) get-x)
((y) get-y)
((set-x!) set-x!)
((set-y!) set-y!)
((color) get-color)
((set-color!) set-color!)
((next!) next!)
((draw) draw)
(else (error "CELL: Unknown message -> " message))))
;; and now return the dispatch procedure
self
)
;; prepare-cell-canvas is a routine that sets up a canvas widget under
;; the specified top level widget.
(require 'Gwish)
(use-library tcl)
(use-interface tcl)
(use-interface tclhack)
(frame '.button-bar) :relief 'raised :bd 2)
(pack '.button-bar :side 'top)
(button '.button-bar.quit :text "quit"
:command (tcl-lambda () (destroy ".")))
(pack '.button-bar.quit)
(define canvas-w 'bogus)
(define square-size 10)
(define (prepare-cell-canvas top-w sizex sizey)
(let* ((top-name (if (string? top-w) top-w (symbol->string top-w)))
(canvas-name (string-append top-name "." "cell-canvas")))
(print top-name canvas-name)
(define canvas-s (string->symbol canvas-name))
(canvas canvas-s :width (* sizex square-size)
:height (* sizey square-size))
(set! canvas-w (eval canvas-s))
(pack canvas-w)
;; (canvas-w 'create 'rectangle 0 0 20 20 :fill 'black)
))
(load "mat")
(prepare-cell-canvas "" (length (car sample-m)) (length sample-m))
(print canvas-w)
;; here's an example of the use of cell objects; other examples
;; can be found in mat.scm.
(define c (MAKE-CELL 0 0 "green" square-size
(length (car sample-m)) (length sample-m)
canvas-w))
((c 'draw))
((c 'next!))
((c 'set-color!) "red")
((c 'draw))
((c 'next!))
((c 'set-color!) "blue")
((c 'draw))
((c 'next!))
((c 'set-color!) "brown")
((c 'draw))
((c 'next!))
((c 'set-color!) "black")
((c 'draw))
((c 'next!))
((c 'set-color!) "orange")
((c 'draw))
((c 'next!))
((c 'set-color!) "yellow")
((c 'draw))
(tk-main-loop)
I have shown you that Guile has facilities that allow the loading and rendering of URLs when it is used in conjunction with the SurfIt! web browser.
You might wonder how this ability to retrieve URLs and do World Wide Web primitive operations was incorporated into Guile. In this case it was done throught vast collection of Tcl/Tk libraries (for HTTP access and HTML rendering) used by the Surfit! browser.
Since the difficult work has been done by SurfIt! and its Tcl libraries, the Guile applet API is implemented quite simply as a set of scheme wrappers for those routines.
If you had to implement a new network-access formalism, such as the Red Hat Linux packager system which allows the upgrade of packages via anonymous ftp, you would have to:
It would be nice to implement the HTTP and HTML libraries with scheme bindings without going through Tcl, but the convenience of using the existing libraries is great.
A good example library to ship with the Guile API would be an ange-ftp type of package which allows transparent retrieval of files by ftp or by local filesystem access. This would give a simple paradigm of how to develop a network access API, similar to but simpler than the Guile applet API.