It’s a joint effort

I met Claudio Russo when I was working at Harlequin. He worked on the Dylan team and we worked together a little when he was implementing a CORBA ORB. He later moved to Microsoft Research Cambridge and I occasionally browse through his publications on such interesting topics as SML.NET and GADTs. Ages ago I read the paper on the Joins library which came about as a portable expression of some of the constructs of the experimental C-omega language. Recently, joins support was added into an experimental version of the Visual Basic compiler.
 
The joins idea is fairly straight forward. We make instances of join points which we communicate with via channels. The join point can be told what function to call when values are available on a subset of its channels. Communication with the join point via the channel can be synchronous or asynchronous. In the synchronous case the value of the executed function defined on the join point can be returned to the client. In the asynchronous case, no value is returned.
Let’s try a quick example.
 
#I "C:Program FilesMicrosoft ResearchJoins LibraryAssemblies";;
#r "Microsoft.Research.Joins.dll";;
open Microsoft.Research.Joins;;
 
We allocate a join point.
 
let join = Join.Create();;
 
We define a synchronous method which communicates a string to the join point, and returns an int result from any function that gets executed on the values.
 
let get= Synchronous<int>.CreateChannel<string>(join);;
 
We define another method that communicates a string. This is asynchronous, so it will not wait for the join point to execute any functions.
 
let put = Asynchronous.CreateChannel<string>(join);;
 
Let’s define a method on the join point. This waits until get and put channels have pending values, at which point it prints the two strings and returns the sum of their lengths.
 
type StringPattern = JoinPatterns.JoinPattern<int>.OpenPattern<string,string>;;
join.When(get).And(put).Do(
  StringPattern.Continuation(
   fun a ->
     fun b ->
       printfn "Arg1: %s" a;
       printfn "Arg2: %s" b;
       a.Length + b.Length));;
 
get and put are realised as delegates. The syntax for calling them in F# isn’t as consise as it C#, so we’ll need to use the Invoke. In a real example we’d encapsulate them into new functions. We can push multiple values into the put channel.
 
> put.Invoke "hello";;
val it : unit = ()
> put.Invoke "mum";;
val it : unit = ()
 
As soon as we push a value into the get channel, the function on the join point is triggered, returning a result.
 
> get.Invoke "how";;
Arg1: how
Arg2: hello
val it : int = 8
 
We didn’t lose the second value we pushed. It was buffered in the channel.
 
> get.Invoke "are";;
Arg1: are
Arg2: mum
val it : int = 6
 
Get is synchronous so it will block if a function cannot be triggered. In the following example, the thread calling the get is blocked until the background thread executes.
 
System.Threading.ThreadPool.QueueUserWorkItem(
  fun _ ->
    System.Threading.Thread.Sleep(10000);
    put.Invoke "another");;
   
get.Invoke "you";;
 
As a slightly less contrived example, we can implement a standard one element buffer. We’ll have put and get channels as well as a channel for denoting that the buffer is empty and a channel for holding on to the buffered value.
let buffer = Join.Create();;
 
The get channel will be used for returning the string value from the buffer. If no value is present it will block until a value is ready.
 
let bufferGet : Synchronous<string>.Channel = Synchronous<string>.CreateChannel(buffer);;
 
We can synchronously put values into the channel.
 
let bufferPut = Synchronous<string>.CreateChannel<string>(buffer);;
 
We need two internal channels. The first signals when the buffer is empty.
 
let bufferEmpty : Asynchronous.Channel = Asynchronous.CreateChannel(buffer);;
 
And the second channel contains the value that is held by the buffer.
 
let bufferContains = Asynchronous.CreateChannel<string>(buffer);;
 
If the buffer is holding a value in its Contains channel and someone calls Get, then we return the value and put ourselves back into the empty state.
 
type BufferFetchPattern = JoinPatterns.JoinPattern<string>.OpenPattern<string>;;
 
buffer.When(bufferGet).And(bufferContains).Do(
  BufferFetchPattern.Continuation(
   fun a ->
     bufferEmpty.Invoke();
     a
     ));;
 
If the buffer is empty and a value is added to the Put channel, then we capture it in the buffer.
 
let response = BufferFetchPattern.Continuation<string,string>(fun x -> (bufferContains.Invoke x; x));;
 
buffer.When(bufferPut).And(bufferEmpty).Do(response);;
 
The buffer is initially in the empty state.
 
bufferEmpty.Invoke ();;
> bufferPut.Invoke "1";;
val it : unit = ()
> bufferGet.Invoke ();;
val it : string = "1"
 
If we try to put into a filled buffer, then the put operation blocks.
> bufferPut.Invoke "2";;
val it : string = "2"
> bufferPut.Invoke "3";;
– Interrupt
 
> bufferGet.Invoke ();;
val it : string = "2"
 
Get will also block until a value is ready for us to fetch.
 
System.Threading.ThreadPool.QueueUserWorkItem(
  fun _ ->
    System.Threading.Thread.Sleep(10000);
    bufferPut.Invoke "another";
    ());;
 
bufferGet.Invoke ();;
 
The joins page  and the slide deck contain more details of the implementation and numerous examples such as how actors can be implemented in this model.

 
Advertisements
This entry was posted in Computers and Internet. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s