Ubiquity, Oni, and Composability
Ubiquity and Composability
One case where the limitations of conventional asynchronous code bubble to the surface is in systems like Ubiquity. E.g. Ubiquity has a 'google' command and a 'translate' command. I can instruct Ubiquity to google for something:google fooand I can get Ubiquity to translate something:
translate The quick brown fox to Germanbut I cannot instruct it to give me list of translated google results:
translate (google foo) to German // doesn't workOr email me the resulting list:
email(translate (google foo) to German) // doesn't workOr email me a list of laptops on ebay below some price:
email(filter(price<500dollars, find_on_ebay(macbook)) // doesn't work(Ok, excuse the syntax for these examples; in 'real-world' Ubiquity you would probably want this to be in a more natural language style, but I hope I'm getting across the idea of composability. Or rather: lack thereof.)
Oni and Composability
So how does Oni relate to this? Oni is a browser-based "embedded structured concurrency framework". It allows you to write asynchronous code as if it was synchronous, adding back the kind-of composibility that is lost when juggling concurrent strands of execution (such as e.g. pending XMLHttpRequests) with 'conventional' sequential languages.
Oni has recently gained a small AJAX library with some bindings to the Google AJAX APIs. With this new functionality we can write some semi-useful Oni programs which nicely demonstrate the type of composability that Oni provides.
One of the new Oni functions is Google.Feeds.Load(url), which retrieves a JSON object of the RSS feed at 'url'.
With this function it is easy to write an Oni program that e.g. displays the most recent item posted to planet.mozilla.org:
Display(GetMostRecentPost("http://planet.mozilla.org/atom.xml"))
(full sample: composability1.html)
where Display, and GetMostRecentPost are implemented as follows:
var Display = SLift(function(html) {
document.getElementById('output').innerHTML += html;
});
'SLift' is an operator that 'lifts' a conventional sequential JavaScript
function into Oni.
var GetMostRecentPost = Defun(['url'],
ObjMem(Google.Feeds.Load(Get('url')),
'entries', 0, 'content'));
'GetMostPost' is a new Oni function which uses 'Google.Feeds.Load' to
load the feed, and then traverses the returned feed JSON object using
ObjMem, to finally return the html content of the current first post.
So far so good, but this is hardly exciting. Let's add another function into the mix: Google.Language.Translate(text, srclang, destlang), which returns a translation of text (assumed to be in language 'srclang') to language 'destlang'. The object returned by this function is a JSON 'translation result' object of which we only want the 'translation' member, so let's wrap it into a 'Translate' function which extracts just this member:
var Translate =
Defun(['txt', 'lang'],
ObjMem(Google.Language.Translate(Get('txt'), 'en', Get('lang')),
'translation'));
Armed with this new function, we can now do something a little more
interesting, e.g. retrieve a feed entry and translate it into German:
Display(Translate(GetMostRecentPost("http://planet.mozilla.org/atom.xml"),
'de'))
(full sample: composability2.html)
Or retrieve a post and display it in English, German, Spanish and Japanese:
Let({post:GetMostRecentPost("http://planet.mozilla.org/atom.xml")},
Par(Display(Get('post')),
Display(Translate(Get('post'), 'de')),
Display(Translate(Get('post'), 'es')),
Display(Translate(Get('post'), 'ja'))))
(full sample: composability3.html)
Here, 'Let' introduces a new variable ('post'). 'Par' executes its subexpressions in parallel, so the ordering of what's displayed on screen will depend on the order in which the translation requests return.
Here's another example: Fire off requests to get the most recent post from slashdot and the most recent post from planet mozilla and display a translated version of the first one that comes in:
Display(Translate(Alt(GetMostRecentPost("http://rss.slashdot.org/..."),
GetMostRecentPost("http://planet.mozilla.org/...")),
'de'))
(full sample: composibility4.html)
Here, 'Alt' is an Oni operator that executes its subexpressions in parallel and returns the first one that returns, at the same time cancelling the other one.
And for a final example, let's add a timeout to the previous example. If we don't get a translation of a planet or slashdot post within 1 second, cancel any pending requests and display a message instead:
Display(Alt(Translate(Alt(GetMostRecentPost("http://rss.slashdot.org/..."),
GetMostRecentPost("http://planet.mozilla.org/...")),
'de'),
Delay(1000, "Sites timed out")))
(full sample: composibility5.html)
So what's the big deal?
What these examples illustrate is composability, something that the conventional way of dealing with concurrency lacks. The 'standard' way of coding the examples above would be by using callback functions. This works well enough for simple cases, but as programs become more complex you soon enough end up in 'callback hell'. In Oni (and similar systems such as Orc or Arrowlets), we can build more complex programs out of simpler ones in a modular, structured fashion.
So what's the relationship to Ubiquity? Oni is a low-level framework layered on top of JavaScript. It is not, and does not aspire to be a human interface in the way that Ubiquity is. The relation between the two is only tangential: I believe that Ubiquity could become much more expressive if layered on top of a system like Oni.
If this got you interested, and you'd like to find out more about Oni, please visit the project homepage at http://www.croczilla.com/oni.