-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
At yesterday's MSLUG meeting Dominique showed us how easy
distribution can be done using Erlang. There was a question asking
how Erlang and Termite compare. To give a quick idea, here is an
example of how distribution can be done with Gambit-C/Termite.
Assuming you have built the Gambit runtime to include Termite, you
need to start a Termite instance on a machine (say "dynamo") and
initialize it:
dynamo% gsi -e '(node-init (make-node "dynamo" 7777))' -
>
This instance of termite is a "node" which is identified by the IP
address "dynamo" and the IP port 7777. Note that a REPL is started,
but we will not be using it.
Now let's start a Termite instance on machine "neo" to get a REPL:
neo% gsi -e '(node-init (make-node "neo" 8888))' -
>
This second node is identified by the IP address "neo" and the IP
port 8888. We could have started two nodes on the same machine, as
long as the IP ports are different. All the remaining REPL
interaction is on neo. First, let's create a reference to the first
node on "dynamo":
> (define dynamo (make-node "dynamo" 7777))
Synchronous remote procedure call is done with the "on" function. It
executes a thunk remotely and returns the result:
> (on dynamo host-name)
"dynamo.iro.umontreal.ca"
Let's develop an asynchronous remote procedure call in stages. First
a simple example of local process creation and communication:
> (define s (self))
> (define p (spawn (lambda () (! s (expt 2 (?))))))
> (! p 10)
> (?)
1024
The process p uses the "?" function to wait for and retrieve the next
message in p's mailbox. The function "!" sends a message back to the
primordial process (whose PID was saved in global variable s). This
code can easily be extended to get a simple "power of 2" server:
> (define p (spawn (lambda () (let loop () (! s (expt 2 (?)))
(loop)))))
> (! p 16)
> (! p 32)
> (?)
65536
> (?)
4294967296
Note that the results are retrieved asynchronously (i.e. the mailbox
queues the messages received). Moreover the results are always sent
back to the primordial process. To generalize, the request can be a
pair containing, in the car, the PID of the process which the result
should be sent to, and in the cdr, a function that is executed by the
server to generate the result:
> (define p (spawn (lambda ()
(let loop ()
(let ((x (?)))
(! (car x) ((cdr x)))
(loop))))))
> (! p (cons (self) host-name))
> (?)
"neo.iro.umontreal.ca"
In this example the server is running locally. By changing the
"spawn" to a "remote-spawn" the server can be started on "dynamo":
> (define p (remote-spawn dynamo
(lambda ()
(let loop ()
(let ((x (?)))
(! (car x) ((cdr x)))
(loop))))))
> (! p (cons (self) host-name))
> (?)
"dynamo.iro.umontreal.ca"
Any serializable Scheme object can be passed in a message. This
includes closures. Here is an example:
> (! p (cons (self)
(let ((a (host-name)))
(lambda ()
(let ((b (host-name)))
(lambda ()
(list a b (host-name))))))))
> (define f (?))
> (f)
("neo.iro.umontreal.ca" "dynamo.iro.umontreal.ca"
"neo.iro.umontreal.ca")
Note that the cdr of the pair sent to p is a closure which remembers
the binding for a, which is "neo.iro.umontreal.ca" because that's
where the first (host-name) was evaluated. When the server calls
this closure, the second (host-name) is evaluated, giving
"dynamo.iro.umontreal.ca". The binding of b is remembered in the
closure created by the second lambda. This closure is sent back to
the primordial process, which binds f to it. When this closure is
called the third (host-name) is evaluated, resulting in
"neo.iro.umontreal.ca".
This example shows that the meaning of the global environment is node
dependent. The global environment is not automatically kept
consistent on all nodes, it is the programmer's responsibility to
ensure this where needed. So for example:
> (on dynamo (lambda ()
(with-exception-catcher
(lambda (e) e)
(lambda () (fact 10)))))
#<unbound-global-exception #2>
> (on dynamo (lambda () (load "test.scm")))
"/Users/feeley/test.scm"
> (on dynamo (lambda ()
(with-exception-catcher
(lambda (e) e)
(lambda () (fact 10)))))
3628800
> (fact 10)
*** ERROR IN (console)(a)24.2 -- Unbound variable: fact
1>
Erlang's autoloading behavior is not built in to Termite. However it
could be implemented on top of Gambit's exception handling system.
The idea would be to extract from a fully qualified name (such as
test#fact) the filename of the file to load, like this:
(define (char-pos str c)
(let loop ((i 0))
(if (< i (string-length str))
(if (char=? c (string-ref str i))
i
(loop (+ i 1)))
#f)))
(define base-eh (current-exception-handler))
(current-exception-handler
(lambda (e)
(if (unbound-global-exception? e)
(let* ((sym (unbound-global-exception-variable e))
(str (symbol->string sym))
(i (char-pos str #\#)))
(if (not i)
(base-eh e)
(let ((mod (substring str 0 i)))
(load (string-append mod))
(let ((val (##global-var-ref (##make-global-var sym))))
(if (##unbound? val)
(base-eh e)
val)))))
(base-eh e))))
(namespace ("test#" fact))
(pp (fact 10))
The call (fact 10), due to the namespace declaration, is rewritten
into (test#fact 10). When the interpreter evaluates the reference to
test#fact the exception that is raised is caught by the exception
handler which extracts the module name "test" and loads that file to
access the definition of "fact". The file "test.scm" should contain:
(namespace ("test#" fact))
(define (fact n)
(if (= n 0)
1
(* n (fact (- n 1)))))
Marc
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.3 (Darwin)
iD8DBQFGKNrp//V9Zc2T/v4RAvEnAKCIvr+IXNxs+sEFkhWtBcEHB0YIsACeKUCn
WRg15dYlEDtKTAGBHutqeV4=
=uxwr
-----END PGP SIGNATURE-----