Smalltalk India Meetup

Gave a short talk at the Smalltalk India Meetup. It was a live presentation of “concurrent objects” in Spark-Scheme:

Concurrent objects are represented as closures. All computations take place by message-passing. There is no shared state and as a consequence you are free from all problems associated with explicit locking (or lack of it). If you are familiar with Erlang, you know what I am talking about. Here is the definition of a tiny object that compute areas of various geometric objects:

(import (match)) ;; for pattern matching messages

(define pi 3.14159)

(define (area-server self) ;; self is a unique integer that 
                           ;; identifies this object within the system.
   (let loop ((message (receive self)))
     (unless (eq? message 'exit)
            (match message
                   (('circle r)
                     (printf "area of circle = ~a~n"  (* pi (* r r))))
                   (('rectangle h w)
                     (printf "area of rectange = ~a~n"  (* h w)))
                    (_ (printf "unknown message - ~a~n" message)))
              (flush-output) ;; We need this because the server
                             ;; is running in its own process.
              (loop (receive self)))))

A new instance of area-server is created by calling the spawn function:

> (spawn area-server)
=> 1

The integer id returned by spawn is used to send messages to the concurrent object:

> (send 1 '(circle 10))
 => area of circle = 314.159

As the number of objects in the system grows, it might become hard to keep track of the object ids. It is convenient to map the id to a name and use that name in send and receive:

> (register 2 "area-server")
> (send "area-server" '(rectangle 3 4))
=> area of rectange = 12

The object prints out the result. Let us see how we can make it to actually return the result. We need some changes to the protocol so that the object receives the id of the client that made the request. It will use this id to send back the result to that particular client. Here is our modified area-server:

(define (area-server self)
  (let loop ((message (receive self)))
    (unless (eq? message 'exit)
            (match message
                   (('circle r client-pid)
                    (send client-pid (* pi (* r r))))
                   (('rectangle h w client-pid)
                    (send client-pid (* h w)))
                   (_ (printf "unknown message - ~a~n" message)))
            (flush-output)
            (loop (receive self)))))

Let us define a client to test our new server:

(define (area-client self)
    (send "area-server" (list 'circle 20 self)) ;; Note that we append 
                                                ;; our id to the message.
    (printf "result = ~a~n" (receive self)) ;; area-server sends back 
                                            ;; the result and we print it.
    (flush-output))

Make sure that everything works fine:

> (spawn area-server)
=> 3
> (register 3 "area-server")
> (spawn area-client)
=> 4 ;; id of client
=> result = 1256.636 ;; result received from area-server.

To make it more useful, let us wrap our client in another function:

(define (spawn-area-client server-id message)
  (spawn
    (lambda (self)
      (register self "area-client")
      (send server-id (append message (list self)))
      (printf "result = ~a~n" (receive self))
      (flush-output))))

Now it is easier to test various message patterns:

> (spawn-area-client "area-server" (list 'circle 3.4))
=>10
=> result = 36.31678039999999
> (spawn-area-client "area-server" (list 'rectangle 4.5 6.7))
=> 11
=> result = 30.150000000000002

Concurrent objects are location agnostic. You can develop and test components in a single VM and later deploy them across a network, with little ceremony. To enable the area-server to receive messages from the network, you just need to start the remoting service:

> (remoting!)

The client should append its location to its name:

(send server-id (append message (list "area-client@node2")))

That’s all we need to make objects at different locations communicate with each other:

> (remoting!)
> (spawn-area-client "area-server@node1" (list 'circle 10.56))
=> 1
=> result = 350.330010624

Before we conclude, a word about fault tolerance. A process can exit the message loop on its own or it could get killed if the VM sends it the kill signal. We can assign a process to watch other processes (or concurrent objects). When an object dies or gets killed, all its watchers are notified. Here is a watcher process that restarts area-server whenever it dies as a result of a kill signal:

(define (watcher pid proc proc-name)
  (spawn
   (lambda (watcher-id)
     (watch pid watcher-id) ;; a watcher can watch any number of processes.
     (let loop ((message (receive watcher-id)))
         (if (eq? (car message) 'killed)
            (let ((new-id (spawn proc)))
              (register new-id proc-name)
              (watcher new-id proc proc-name)))))))

The watcher is assigned to watch the running area-server:

> (watcher 3 area-server "area-server")

3 is the id of area-server process to watch. If that process gets killed, the watcher process will restart it and bind it to the name “area-server”. We can kill a running process by calling the kill function:

> (kill 3)

Now if you go to the client node (node2) and send a message, you will still get the result from the new process spawned by the watcher. The re-spawning happens quite quickly as the processes are very lightweight objects living in the VM itself. We can run tens-of-thousands of such concurrent objects efficiently in a single instance of Spark.

Well, that’s all there is about concurrent objects!!

PS: If you are wondering why I was allowed to talk about a Lisp implementation at a Smalltalk event, there could be many reasons for that:

  • Lisp is the grand-daddy of all dynamic languages
  • Message-passing is of great interest to Smalltalkers
  • Alan Kay is the biggest fan of Lisp!

Looking forward for the next meetup of the Smalltalk India Group. Found some guys there who are really passionate about what they are doing in this amazing language!

Defining a new class in the Squeak Workspace

You can create a new class in Squeak, without using the class browser. The following session in the Workspace shows how:

Object subclass: #Person 
       instanceVariableNames: 'name age'
       classVariableNames: '' 
       poolDictionaries: '' 
       category: 'vijay'.

Person compile: 'name ^name' classified: #accessing.
Person compile: 'age ^age' classified: #accesing.
Person compile: 'age: a age := a' classified: #accessing.
Person compile: 'name: n name := n' classified: #accessing.

p := Person new.
p name: 'Vijay'; age: 32.
p name => "Vijay".
p age => 32

A web development framework for me!

I had an aversion for almost all web development frameworks/technologies I came across: JSP, ASP, PHP, Struts and all that. Mixing hypertext with code, heavy use of design patterns, blah!! Recently I had a prejudiced look at Seaside, which is one of those continuations based web frameworks. Then I found this tutorial. After reading the first few chapters and trying out some code - surprise! Here is a web application framework that I am in love with! All those new-age JavaScript libraries may soon throw server managed sessions out of fashion. Still, if a framework allows web development in a very high level, dynamic, live language like Smalltalk, saving us from the horrible markup-code spaghetti, it must have a place in the programmer's toolbox.