User Tools

Site Tools


at:introduction

What is AmbientTalk?

:at:atlogo.jpg AmbientTalk is a distributed programming language developed specifically for writing programs to be deployed on mobile ad hoc networks. A distributed programming language is often characterised as a programming language with built-in support for aiding the programmer in dealing with the difficulties engendered by distribution. If you wonder what those difficulties would be, a good starting point are the well-known fallacies of distributed computing.

For the programming language adepts: AmbientTalk is:

  • dynamically typed, which is not the same as being untyped: AmbientTalk values are typed, but variables are not
  • object-oriented, but prototype-based: AmbientTalk provides full support for objects, delegation (a form of inheritance) and polymorphic message dispatch. Objects are not instantiated from classes, as in Smalltalk, but rather cloned from existing objects or created ex-nihilo, as in Self.
  • flexible: like Scheme, Smalltalk, Self and many other languages, AmbientTalk embraces the use of elegant and expressive block closures to achieve a level of reusability far exceeding that of Java or similar languages lacking true closures.
  • event-driven: AmbientTalk has built-in support for actor-based concurrency, which operates based on entirely asynchronous and event-driven communication. No threads, no locks, no deadlock and no data-level race conditions!
  • distributed: AmbientTalk has built-in language constructs to make objects discover and talk to one another in a peer-to-peer manner across a TCP/IP network.
  • symbiotic: built on top of the Java Virtual Machine, AmbientTalk exploits the Java reflection API to enable AmbientTalk objects to collaborate with Java objects. This enables reuse of existing Java libraries while not abandoning the dynamic programming style of AmbientTalk.

A word of warning, though: AmbientTalk is not:

  • a stable, optimized, scalable development platform. Rather, it is a research artifact in the form of an interpreter used as a “programming language laboratory” to experiment with and discover new language abstractions for distributed computing in mobile ad hoc networks.
  • a secure distributed computing platform.
  • a customisable or language-neutral distributed computing platform.

AmbientTalk in a nutshell

Enough talking, let's delve into some AmbientTalk code by means of a simple example to see whether we can spark your interest in AmbientTalk. The demo program we're about to explore is an extremely simple instant messenger program. Each instant messenger runs on e.g. a PDA, laptop or cell phone. When peers join the ad hoc network, the instant messenger should issue a warning to the user that a new buddy is available for chatting. The user can then send simple text messages to that buddy. When peers leave the ad hoc network, the user should also be notified. Off we go.

Designing the Instant Messenger

Here is the skeleton code for the instant messenger:

def createInstantMessenger(username) {
  def TextMessage := ...; // object encapsulating a text message
  def buddyList := ...; // maps buddy names to remote IM objects

  // the interface to the IM for local objects
  def localInterface := object: {
    def sendTextMessage(to, string) { ... };
  };
  // the interface to the IM for remote objects
  def remoteInterface := object: {
    def getName(replyTo) { ... };
    def receiveTextMessage(textMessage, replyTo) { ... };
  };
  
  // engage in peer-to-peer service discovery
  deftype InstantMessenger;
  export: remoteInterface as: InstantMessenger;
  whenever: InstantMessenger discovered: { |messenger|
    ...
  };
  localInterface; // return value of this function
};

An instant messenger object is created by invoking a function called createInstantMessenger. A username should be passed to identify the user. The function defines two nested objects named localInterface and remoteInterface. As their names indicate, the localInterface defines all of the methods that can be invoked by local objects referring to the instant messenger (e.g. by a GUI object that provides the user interface for the messenger). That's why the localInterface object is also the return value of the createInstantMessenger function. The remoteInterface provides the methods that may be invoked by remote objects (i.e. other instant messengers). In a sense, the remote interface defines the protocol that the messengers will use to exchange simple text messages.

Within the lexical scope of the createInstantMessenger function, we also define a type tag named InstantMessenger which will be used to identify remote objects in the network as peer instant messengers. Type tags are simply symbolic marks that can be attached to objects to classify them. They replace the role of classes in class-based OO languages to classify objects. The advantage over classes is that an object can be classified under multiple type tags (unlike classes, type tags do not say anything about the implementation of an object). A few lines down, you'll notice that the nested remoteInterface object is exported as an InstantMessenger. This means that the object is published on the network as an instant messenger peer. The next line of code shows AmbientTalk's built-in discovery mechanism at work: the instant messenger asks the AmbientTalk interpreter to be notified whenever an object exported as an InstantMessenger becomes available in the network. Yes, service discovery in ad hoc networks can be as simple as this.

The buddyList variable will map the names of connected buddies to remote references to the remoteInterface objects of other instant messengers. The TextMessage object is a simple object that encapsulates a text string and the username of the sender.

Implementing the Instant Messenger

Let's delve into the method bodies and see how the instant messengers engage in distributed communication:

def createInstantMessenger(username) {
  def TextMessage := isolate: {
    def from := nil;
    def content := nil;
    def init(f,c) {
        from := f;
        content := c;
    };
    def printMessage() {
      system.println(from + ": " + content)
    };
  };
  def buddyList := jlobby.java.util.HashMap.new();
  ...
};

The TextMessage variable is bound to a prototype object that defines two instance variables (from to store the sender and content to store the text message itself), it defines a constructor (always called init in AmbientTalk) and one method named printMessage to display the message. Why is TextMessage defined as an isolate: rather than an object:? An isolate in AmbientTalk is an object that can be passed by copy in remote message sends, at the expense of losing its surrounding lexical scope. See the tutorial on actors for more details.

Also interesting is the initialisation code of the buddyList variable. The buddyList is actually a symbiotic Java object instantiated from the class java.util.HashMap. That's one less data structure that we need to implement ourselves!

Now let us delve into the details of the local interface object:

  def localInterface := object: {
    def sendTextMessage(to, string) {
      def buddy := buddyList.get(to);
        if: (nil == buddy) then: {
          system.println("Unknown buddy: " + to);
        } else: {
          def msg := TextMessage.new(username, string);
          buddy<-receiveTextMessage(msg, object: {
            def uponReceipt() {
              msg.printMessage();
            }
          });
        };
    };
  };

The sendTextMessage method is the only useful behaviour to be invoked by local clients of the instant messenger. It takes the name of a buddy, looks up the remote reference to the remoteInterface object of that buddy's instant messenger in the buddyList and, if found, sends the remote messanger a new TextMessage with the given string as content. Notice that the instant messenger only displays the message in its own chat window after it has received an acknowledgement from the remote messenger that the message was successfully received, by means of a small callback object. Note the ← message sending operator, which denotes asynchronous message passing between objects.

A small note for the language adepts: the if:then:else: construct used in the above method is not a built-in control structure. Rather, if:then:else: is a simple keyworded message send (as in Smalltalk), and the arguments to the then: and else: keywords are so-called blocks (for Smalltalk/Self-ers) or lambdas (for the Schemers). The {code} syntax is equivalent to Smalltalk's [code] and Scheme's (lambda () code) syntax. if:then:else: is simply defined at top-level as:
def if: cond then: cons else: alt {
  cond.ifTrue: cons ifFalse: alt
};

This code should look familiar to Smalltalk/Self programmers. As you may have guessed, the boolean values true and false are singleton objects that respond to methods like ifTrue:ifFalse:.

Most curly braces in AmbientTalk denote first-class blocks. These never denote hard-wired syntax as in C or Java. The only exception are the braces used to delimit a method definition.

Let us look in more detail at the remoteInterface of an instant messenger:

  def remoteInterface := object: {
    def getName(replyTo) { replyTo<-receive(username) };
    def receiveTextMessage(textMessage, replyTo) {
      textMessage.printMessage();
      replyTo<-uponReceipt();
    };
  };

The protocol of the instant messenger is very straightforward: when two messengers discover one another, they ask one another's user names, by means of the getName method. Note the use of the callback object to notify the sender asynchronously of the local username. The second method, receiveTextMessage allows the instant messenger to accept a text message from a remote user. It will print the message to the screen and send an acknowledgement uponReceipt message to the sender. This triggers the callback in the sendTextMessage method defined previously.

Finally, here is the code for managing the discovery of instant messengers:

  deftype InstantMessenger;
  export: remoteInterface as: InstantMessenger;
  whenever: InstantMessenger discovered: { |messenger|
    messenger<-getName(object: {
      def receive(name) {
        if: (nil == buddyList.get(name)) then: {
          buddyList.put(name, messenger);
          system.println("Added buddy: " + name);
          when: messenger disconnected: {
            system.println("Buddy offline: " + name);
          };
          when: messenger reconnected: {
            system.println("Buddy online: " + name);
          };
        };
      };
    });
  };

The whenever:discovered: method takes a type tag and a one-argument closure as parameters. It invokes the closure every time a remote object exported with (a subtype of) the given type tag is discovered. The first thing to do when a new messenger is discovered is to ask it for its name, by sending it the getName message. If the messenger was not previously discovered (i.e. its name is not in the buddy list), the messenger is added and the user is notified of the presence of the new buddy.

Finally, notice how AmbientTalk allows you to deal with partial failures: you can register two kinds of observers on remote references which are triggered when the remote reference becomes disconnected (because of e.g. network partitions) and when it becomes reconnected. The second argument to when:disconnected: and when:reconnected: is a zero-arity closure. In this example, all we do is notify the user that a buddy when offline or came online. AmbientTalk's default semantics is to buffer messages sent to disconnected remote references internally, and to flush such messages if the remote reference should become reconnected. This provides the nice property that we can send messages to disconnected buddies and rest assured that the messages will arrive when the buddy rejoins the ad hoc network.

Conclusion

You might wonder what is so special about the above example. After all, it is simply an application that sends simple strings across the network and displays them on the screen. But keep in mind all of the things you did not have to do:

  • no explicit thread management
  • not having to prevent data-level race conditions, not having to define locks, not having to debug the ensuing deadlocks
  • not having to manually serialize/deserialize objects
  • not having to manage low-level socket connections
  • no need to setup a name server or similar lookup service
  • no annoying configuration files
  • not one XML document in sight
  • not having to manage complex buffering to ensure messages sent to disconnected objects are not lost
  • no need to write the boring Singleton design pattern to make your classes look like objects
  • no need to generate stubs, skeletons or other kinds of proxies to manage remote objects

Of course, we are not claiming that AmbientTalk is a good replacement for distributed computing standards such as CORBA or Jini, which are much more flexible, at the cost of increased complexity. AmbientTalk is simply a lightweight alternative for doing experimental research.

Moving on

So, you read the introduction and are interested in all of the gory details of the language? In that case, you can go ahead and download AmbientTalk and read the tutorial which explains the language in much more detail. You can also watch an introductory presentation on AmbientTalk (in flash format).

at/introduction.txt · Last modified: 2008/07/15 12:19 by tvcutsem