The World Wide Web has evolved gradually from a document delivery platform to an architecture for distributed programming. This largely unplanned evolution is apparent in the set of interconnected languages and protocols that any Web application must manage. This paper presents Ur/Web, a domain-specific, statically typed functional programming language with a much simpler model for programming modern Web applications. Ur/Web’s model is unified, where programs in a single programming language are compiled to other “Web standards” languages as needed; supports novel kinds of encapsulation of Web-specific state; and exposes simple concurrency, where programmers can reason about distributed, multithreaded applications via a mix of transactions and cooperative preemption. We give a tutorial introduction to the main features of Ur/Web.
1. Introduction
The World Wide Web is a very popular platform today for programming certain kinds of distributed applications with graphical user interfaces (GUIs). Today’s complex ecosystem of “Web standards” was not planned monolithically. Rather, it evolved gradually, from the starting point of the Web as a delivery system for static documents. The result is not surprising: there are many pain points in implementing rich functionality on top of the particular languages that browsers and servers speak. At a minimum, today’s rich applications must generate HTML, for document structure; CSS, for document formatting; JavaScript, a scripting language for client-side interactivity; and messages of HTTP, a protocol for sending all of the above and more, to and from browsers. Most recent, popular applications also rely on languages like JSON for serializing complex datatypes for network communication, and on languages or APIs like SQL for storing persistent, structured data on servers. Code fragments in these different languages are often embedded within each other in complex ways, and the popular Web development tools provide little help in catching inconsistencies.
These complaints are not new, nor are language-based solutions. The Links project4, 8 pioneered the “tierless programming” approach, combining all the pieces of dynamic Web applications within one statically typed functional programming language. Similar benefits are attained in more recent mainstream designs, such as Google’s Web Toolkita and Closureb systems, for adding compilation on top of Web-standard languages; and Microsoft’s LINQ,12 for type-safe querying (to SQL databases and more) within general-purpose languages.
Such established systems provide substantial benefits to Web programmers, but there is more we could ask for. This article focuses on a language design that advances the state of the art by addressing two key desiderata. First, we bring encapsulation to rich Web applications, supporting program modules that treat key pieces of Web applications as private state. Second, we expose a simple concurrency model to programmers, while supporting the kinds of nontrivial communication between clients and servers that today’s applications take advantage of. Most Web programmers seem unaware of either property as something that might be worth asking for, so part of our mission here is to evangelize for them.
We present the Ur/Web programming language, an extension of the Ur language,5 a statically typed functional language inspired by dependent type theory. Open-source implementations of Ur/Web have been available since 2006, and several production Web applications use the language, including at least one profitable commercial site.
Ur/Web reduces the nest of Web standards to a simple programming model, coming close to retaining just the essence of the Web as an application platform, from the standpoints of security and performance. We have a single distributed system, with one server under the programmer’s control and many clients that are not. The server and clients communicate only through various strongly typed communication channels, and every such interaction occurs as part of a transaction that appears to execute atomically, with no interference from other actions taking place at the same time. The server has access to persistent state in an SQL database, which is also accessed only through channels with strong types specific to the data schema. Clients maintain their GUIs though a novel variant of functional-reactive programming.
The next section expands on these points with a tutorial introduction to Ur/Web. We highlight the impact on the language design of our goals to support encapsulation and simple concurrency. Afterward, we compare with other research projects and widely used frameworks.
The open-source implementation of Ur/Web is available at:
http://www.impredicative.com/ur/.
2. A Tutorial Introduction to UR/WEB
We will introduce the key features of Ur/Web through a series of refinements of one example, a multiuser chat application. Visitors to the site choose from a selection of chat rooms, each of which maintains a log of messages. Any visitor to a chat room may append any line of text to the log, and there should be some way for other users to stay up-to-date on log additions. We start with a simple implementation, in the style of 20th century Web applications, before it became common to do significant client-side scripting. We evolve toward a version with instant updating upon all message additions, where a chat room runs within a single HTML page updated incrementally by client-side code. Along the way, we highlight our running themes of encapsulation and simple concurrency.
The examples from this section are written to be understandable to readers with different levels of familiarity with statically typed functional languages. Though the code should be understandable to all at a high level, some remarks (safe to skip) may require more familiarity.
Mainstream modern Web applications manipulate code in many different languages and protocols. Ur/Web hides most of them within a unified programming model, but we decided to expose two languages explicitly: HTML, for describing the structure of Web pages as trees, and SQL, for accessing a persistent relational database on the server. In contrast to mainstream practice, Ur/Web represents code fragments in these languages as first-class, strongly typed values.
Figure 1 gives our first chat-room implementation, relying on embedding of HTML and SQL code. While, in general, Ur/Web programs contain code that runs on both server and clients, all code from this figure runs on the server, where we are able to enforce that it is run exactly as written in the source code.
The first two lines show declarations of SQL tables, which can be thought of as mutable global variables of type “multiset of records.” Table room
‘s records contain integer IDs and string titles, while table message
‘s records contain integer room IDs, timestamps, and string messages. The former table represents the set of available chat rooms, while the latter represents the set of all (timestamped) messages sent to all rooms.
We direct the reader’s attention now to the declaration of the main
function, near the end of Figure 1. Here we see Ur/Web’s syntax extensions for embedded SQL and HTML code. Such notation is desugared into calls to constructors of abstract syntax tree types. The main
definition demonstrates two notations for “antiquoting,” or inserting Ur code within a quoted code fragment. The notation {e
} asks to evaluate expression e
to produce a subfragment to be inserted at that point, and notation {[e
]} adds a further stage of formatting e
as a literal of the embedded language (using type classes17 as in Haskell’s show
). Note that we are not exposing syntax trees to the programmer as strings, so neither antiquoting form presents any danger of code injection attacks, where we accidentally interpret user input as code.
What exactly does the main
definition do? First, we run an SQL query to list all chat rooms. In our tutorial examples, we will call a variety of functions from Ur/Web’s standard library, especially various higher-order functions for using SQL query results. Such functions are higher-order in the sense that they take other functions as arguments, and we often write those function arguments anonymously using the syntax fn x => e
, which defines a function that, when called, returns the value of expression e
where parameter variable x
is replaced with the actual argument value. We adopt a typographic convention for documenting each library function briefly, starting with queryX1
, used in main
:
queryX1
Run an SQL query that returns columns from a single table (leading to the1
in the identifier), calling an argument function on every result row. Since just a single table is involved, the input to the argument function is a record with one field per column returned by the query. The argument function should return XML fragments (leading to theX
in the identifier), and all such fragments are concatenated together, in order, to form the result ofqueryX1.
Ur/Web follows functional languages like Haskell in enforcing purity, where expressions may not cause side effects. We allow imperative effects on an “opt-in” basis, with types delineating boundaries between effectful and pure code, following Haskell’s technique of monadic IO.14 For instance, the main
function here inhabits a distinguished monad for input-output. Thus, we use the <-
notation to run an effectful computation and bind its result to a variable, and we call the return
function to lift pure values into trivial computations. Readers unfamiliar with the Haskell style may generally read <-
as simple variable assignment and return
in its usual meaning from C-like languages.
The remaining interesting aspect of main
is in its use of an HTML <a>
tag to generate a hyperlink. Instead of denoting a link via a URL as in standard HTML, we use a link
attribute that accepts a suspended Ur/Web remote function call. In this case, we call chat
, which is defined earlier. The Ur/Web implementation handles proper marshalling of arguments in suspended calls.
Now let us examine the implementation of the chat
function, providing a page for viewing the current message log of a chat room. First, there is a nested definition of a function say
, which will be called to append a message to the log.
dml
Run a piece of SQL code for its side effect of mutating the database. The function name refers to SQL’s data manipulation language.
This particular invocation of dml
inserts a new row into the message
table with the current timestamp, after which we trigger the logic of the main chat
page to generate output. Note that say
, like all remotely callable functions, appears to execute atomically, so the programmer need not worry about interleavings between concurrent operations by different clients.
The main body of chat
runs appropriate queries to retrieve the room name and the full, sorted message log.
oneRowE1
Run an SQL query that should return just one result row containing just a single column (justifying the 1) that is computed using an arbitrary SQL expression (justifying theE
). That one result value becomes the result of theoneRowE1
call.
We antiquote the query results into the returned page in an unsurprising way. The only new feature involves HTML forms. In general, we tag each input widget with a record field name, and then the submit button of the form includes, in its action
attribute, an Ur/Web function that should be called upon submission, on a record built by combining the values of all the input widgets. We will not say any more about HTML forms, which to some extent represent a legacy aspect of HTML that has been superseded by client-side scripting. Old-style forms need to use a rigid language (HTML) for describing how to combine the values of different widgets into one request to send to the server, while these days it is more common to use a Turing-complete language (JavaScript) for the same task.
Compiling an application to run on the real Web platform requires exposing remotely callable functions (like main
, chat
, and say
) via URLs. Ur/Web automatically generates pleasing URL schemes by serializing the function-call expressions that appear in places like link
attributes. For instance, the link to chat
in the declaration of main
is compiled to a URL/chat/NN
, where NN
is a textual representation of the room ID.
Adding more encapsulation. The application in Figure 1 is rather monolithic. The database state is exposed without restrictions to all parts of the application. We would not tolerate such a lack of encapsulation in a large traditional application. Chunks of functionality should be modularized, for example, into classes implementing data structures. The database tables here are effectively data structures, so why not try to encapsulate them as well?
The answer is that, as far as we are aware, no prior language designs allow it! As we wrote above, the general model is that the SQL database is a preexisting resource, and any part of the application may create an interface to any part of the database. We analogize such a scheme to an object-oriented language where all class fields are public; it forecloses on some very useful styles of modular reasoning. It is important that modules be able to create their own private database tables, without requiring any changes to other source code, application configuration files, etc., for the same reason that we do not want client code of a dictionary class to change, when the dictionary switches to being implemented with hash tables instead of search trees.
Figure 2 shows a refactoring of our application code, to present the chat-room table as a mutable abstract data type. We use Ur/Web’s module system, which is in the ML tradition.11 We have modules that implement signatures, which may impose information hiding by not exposing some members or by making some types abstract. Figure 2 defines a module Room
encapsulating all database access.
The signature of Room
appears bracketed between keywords sig
and end
. We expose an abstract type id
of chat-room identifiers. Ur/Web code in other program modules will not be able to take advantage of the fact that id
is really int
, and thus cannot fabricate new IDs out of thin air. The signature exposes two methods: rooms
, to list the IDs and titles of all chat rooms; and chat
, exactly the remotely callable function we wrote before, but typed in terms of the abstract type id
. Each method’s type uses the transaction
monad, which is like Haskell’s IO monad, but with support for executing all side effects atomically in a remote call.
The implementation of Room
is mostly just a copying-and-pasting of the bulk of the code from Figure 1. We only need to add a simple implementation of the rooms
method.
queryL1
Return as a list (justifying theL
) the results of a query that only returns columns of one table (justifying the 1).
List.mapX
Apply an XML-producing function to each element of a list, then concatenate together the resulting XML fragments to compute the result ofmapX
.
The code for main
changes so that it calls methods of Room
, instead of inlining database access.
This sort of separation of a data model is often implemented as part of the “model-view-controller” pattern. To our knowledge, that pattern had not previously been combined with guaranteed encapsulation of the associated database tables. It also seems to be novel to apply ML-style type abstraction to database results, as in our use of an id
type here. We hope this example has helped convey one basic take-away message: giving first-class status to key pieces of Web applications makes it easy to apply standard language-based encapsulation mechanisms.
2.2. Client-side GUI scripting
We will develop two more variations of the chat-room application. Our modifications will be confined to the implementation of the Room
module. Its signature is already sufficient to enable our experiments, and we will keep the same main
function code for the rest of the tutorial.
Our first extension takes advantage of client-side scripting to make applications more responsive, without the need to load a completely fresh page after every user action. Mainstream Web applications are scripted with JavaScript, but, as in Links and similar languages, Ur/Web scripting is done in the language itself, which is compiled to JavaScript as needed.
Reactive GUIs. Ur/Web GUI programming follows the functional-reactive style. That is, to a large extent, the visible page is described as a transformation over streams (sequences of values over time). User inputs like keypresses are modeled as primitive streams, which together should be transformed into the stream of page contents that the user sees. Languages like Flapjax13 and Elm9 adopt the stream metaphor literally. Ur/Web follows a less pure style, where we retain the event callbacks of imperative programming. These callbacks modify data sources, which are a special kind of mutable reference cells. The only primitive streams are effectively the sequences of values that data sources take on, where new entries are pushed into the streams mostly via imperative code in callbacks. Both flavors of the functional-reactive style provide superior modularity compared to the standard Web model, which involves imperative mutation of the document tree, treated as a public global variable.
As a basic orientation to the concepts of sources and streams in Ur/Web, here is their type signature. We rely on two abstract parameterized types: source α is a data source that can store values of type α, and signal α is a time-varying value that, at any particular time, has some value of type α. As types and values occupy different namespaces, we often reuse an identifier (e.g., source) to stand for both a type and the run-time operation for allocating one of its instances, much as happens with classes and their constructors in Java. We write ∀α. T for a type that is polymorphic in some arbitrary type parameter α, much as we would write a method prototype like <a>T f(...)
in Java to bind a
for use in T
.
That is, data sources have operations for allocating them and reading or writing their values. All such operations live inside the transaction monad for imperative side effects. Signals, or time-varying values, happen to form a monad with appropriate operations (e.g., support return and the <- operator), as indicated by the presence of a first-class dictionary s_m witnessing their monadhood. One more key operation with signals is producing them from sources, via the value-level signal function, which turns a source into a stream that documents changes to the source’s contents.
Figure 3 demonstrates these constructs in a module implementing a GUI widget for append-only logs. The module signature declares an abstract type t
of logs. We have methods create
, to allocate a new log; append
, to add a new string to the end of a log; and render
, to produce the HTML representing a log. The type of render
may be deceptive in its simplicity; Ur/Web HTML values (as in the xbody
type of HTML that fits in a document body) actually are all implicitly parameterized in the dataflow style, and they are equipped to rerender themselves after changes to the data sources they depend on.
The workhorse data structure of logs is an algebraic datatype log
. Algebraic datatypes are the classic tool for structured data in statically typed functional programming, and they behave similarly to tagged unions in some other languages (or Scala’s case classes). The definition of log
gives an exhaustive list of the constructors for logs (Nil
and Cons
) with the types of arguments that they take, and later we use case
expressions to deconstruct such values, giving different code to evaluate depending on which constructor built a value. Our definition of log
looks almost like a standard definition of lists of strings. The difference is that the tail of a nonempty log
has type source log
, rather than just log
. We effectively have a type of lists that supports imperative replacement of list tails, but not replacement of heads.
The type t
of logs is a record of two fields. Field Head
is a modifiable reference to the current log state, and Tail
is a “pointer to a pointer,” telling us which source cell we should overwrite next to append to the list. Methods create
and append
involve a bit of intricacy to update these fields properly, but we will not dwell on the details. We only mention that the point of this complexity is to avoid rerendering the whole log each time an entry is appended; instead, only a constant amount of work is done per append, to modify the document tree at the end of the log. That sort of pattern is difficult to implement with pure functional-reactive programming.
The most interesting method definition is for render
. Our prior examples only showed building HTML fragments with no dependencies on data sources. We create dependencies via a pseudotag called <dyn>
. The signal
attribute of this tag accepts a signal, a time-varying value telling us what content should be displayed at this point on the page at different times. Crucially, the signal
monad rules out imperative side effects, instead capturing pure dataflow programming. Since it is a monad, we have access to the usual monad operations <-
and return
, in addition to the signal
function for lifting sources into signals.
Let us first examine the definition of render_aux
, a recursive helper function for render
. The type of render_aux
is log -> xbody
, displaying a log as HTML. Empty logs are displayed as empty HTML fragments, and nonempty logs are rendered with their textual heads followed by recursive renderings of their tails. (Following functional-programming tradition, we write “head” and “tail” respectively for the first element of a list and the remainder of the list minus that element.) However, the recursive call to render_aux
is not direct. Instead it appears inside a <dyn>
pseudotag. We indicate that this subpage depends on the current value of the tail (a data source), giving the computation to translate from the tail’s value to HTML. Now it is easy to define render
, mostly just duplicating the last part of render_aux
.
An important property of this module definition is that a rendered
log automatically updates in the browser after every call to append
, even though we have not coded any explicit coupling between these methods. The Ur/Web runtime system takes care of the details, once we express GUIs via parameterized dataflow.
Client code may use logs without knowing implementation details. In the standard model for programming browser GUIs with JavaScript, mistakes in one code module may wreck subtrees that other modules believe they control. For instance, subtrees are often looked up by string ID, creating the possibility for two different libraries to choose the same ID unwittingly for different subtrees. With the Ur/Web model, the author of module Log
may think of it as owning particular subtrees of the HTML document as private state. Standard module encapsulation protects the underlying data sources from direct modification by other modules, and rendered logs only have dataflow dependencies on the values of those sources.
Remote procedure calls. The GUI widget for displaying the chat log is only one half of the story if we are to write an application that runs within a single page. We also need a way for this application to contact the server, to trigger state modifications and receive updated information. Ur/Web’s first solution to that problem is remote procedure calls (RPCs), allowing client code to run particular function calls as if they were executing on the server, with access to shared database state. Client code only needs to wrap such calls to remotely callable functions within the rpc
keyword, and the Ur/Web implementation takes care of all network communication and marshalling. Every RPC appears to execute atomically, just as for other kinds of remote calls.
Figure 4 reimplements the Room
module to take advantage of RPCs and the Log
widget.
List.foldl
As in ML, step through a list, applying a function f to each element, so that, given an initial accumulator a and a list [x1, …, xn], the result is f(xn, …, f(x1, a) …).
List.app
Apply an effectful function to every element of a list, in order.
The code actually contains few new complexities. Our basic strategy is for each client to maintain the timestamp of the most recent chat message it has received. The textbox for user input is associated with a freshly allocated source string
, via the <ctextbox>
pseudotag (“c” is for “client-side scripting”). Whenever the user modifies the text shown in this box, the associated source is automatically mutated to contain the latest text. When the user clicks a button to send a message, we run the callback code in the button’s onclick
attribute, on the client, whereas the code for this example outside of on*
attributes runs on the server. This code makes an RPC, telling the server both the new message text and the last timestamp that the client knows about. The server sends back a list of all chat messages newer than that timestamp, and client code iterates over that list, adding each message to the log; and then updates the last-seen timestamp accordingly, taking advantage of the fact that the RPC result list will never be empty, as it always contains at least the message that this client just sent. An onload
event handler in the body
tag initialized the log in the first place, appending each entry returned by an initial database query.
Note how seamless the use of the Log
module is. We allocate a new log, drop its rendering into the right part of the page, and periodically append to it. Pure functional-reactive programming would require some acrobatics to interleave the event streams generated as input to the log system, from the two syntactically distinct calls to Log.append
.
2.3. Message passing from server to client
Web browsers make it natural for clients to contact servers via HTTP requests, but the other communication direction may also be useful. One example is our chat application, where only the server knows when a client has posted a new message, and we would like the server to notify all other clients in the same chat room. Ur/Web presents an abstraction where servers are able to send typed messages directly to clients, and the Ur/Web compiler and runtime system implement this abstraction once and for all on top of standard protocols and APIs.
The messaging abstraction is influenced by concurrent programming languages like Erlang1 and Concurrent ML.15 Communication happens over unidirectional channels. Every channel has an associated client and a type. The server may send any value of that type to the channel, which conceptually adds the message to a queue on the client. Clients asynchronously receive messages from channels for which they have handles, conceptually dequeuing from local queues, blocking when queues are empty. Any remote call may trigger any number of sends to any number of channels. All sends in a single remote call appear to take place atomically.
The API for channels is straightforward:
Figure 5 gives another reimplementation of Room
, this time using channels to keep clients synchronized at all times, modulo small amounts of lag. We retain the same room
and message
tables as before, but we also add a new table subscriber
, tracking which clients are listening for notifications about which rooms. (Thanks to Ur/Web’s approach to encapsulation of database tables, we need not change any other source or configuration files just because we add a new private table.) Every row of subscriber
has a room ID Room
and a channel Chan
that is able to receive strings.
Now the chat
method begins by allocating a fresh channel with the channel
operation, which we immediately insert into subscriber
. Compared to Figure 4, we drop the client-side timestamp tracking. Instead, the server will use channels to notify all clients in a room, each time a new message is posted there. In particular, see the tweaked definition of say
.
queryI1
Run an SQL query returning columns from just a single table (justifying the 1), applying a function to each result in order, solely for its imperative side effects (justifying theI
).
We loop over all channels associated with the current room, sending the new message to each one.
There is one last change from Figure 4. The onload
attribute of our <body>
tag still contains code to run immediately after the page is loaded. This time, before we initialize the Log
structure, we also create a new thread with the spawn
primitive. That thread loops forever, blocking to receive messages from the freshly created channel and add them to the log.
Threads follow a simple cooperative semantics, where the programming model says that, at any moment in time, at most one thread is running across all clients of the application. Execution only switches to another thread when the current one terminates or executes a blocking operation, among which we have RPCs and channel recv
. Of course, the Ur/Web implementation will run many threads at once, with an arbitrary number on the server and one JavaScript thread per client, but the implementation ensures that no behaviors occur that could not also be simulated with the simpler one-thread-at-a-time model.
This simple approach has pleasant consequences for program modularity. The example of Figure 5 only shows a single program module taking advantage of channels. It is possible for channels to be used freely throughout a program, and the Ur/Web implementation takes care of routing messages to clients, while maintaining the simple thread semantics.
Figure 5 contains no explicit deallocation of clients that have stopped participating. The Ur/Web implementation detects client departure using a heartbeating mechanism. When a client departs, the runtime system atomically deletes from the database all references to that client’s channels, providing a kind of modularity similar to what garbage collection provides for heap-allocated objects.
3. Related Work
The space of Web-development tools is a busy one, both in the research world and in the mainstream, so we will not have space to take a nearly complete tour of it; we provide a longer comparison elsewhere.7
A variety of research programming languages and libraries were under development in the early 2000s, in that interesting transitional period where the Web had become mainstream but the highly interactive “AJAX” style of Gmail and so on had not yet become common. The PLT Scheme Web Server10 provides completely first-class support for URLs as first-class functions (more specifically, continuations), making it very easy to construct many useful abstractions. The Links8 language pioneered strong static checking of multiple-tier Web applications, where the code for all tiers is collected in a single language. Hop16 is another unified Web programming language, but is dynamically typed and based on Scheme. Ocsigen2, 3 is an OCaml-based platform for building dynamic Web sites in a unified language, with static typing that rules out many potential programming mistakes. The Opa languagec is another statically typed unified language for database-backed Web applications.
In broad strokes, how does Ur/Web compare to these others from the languages research community? We believe the key comparison points relate to those features of Ur/Web that we have stressed throughout this article: encapsulation and concurrency. No other languages allow enforced encapsulation of database state inside of modules, and the languages listed above all support client-side GUIs only through mutation of a global document tree, with no enforced encapsulation of client-side state pieces. Their semantics for concurrency are substantially more complicated than Ur/Web’s transactions.
Several other languages and frameworks support functional-reactive programming for client-side Web GUIs, including Flapjax,13 which is available in one flavor as a JavaScript library; and Elm,9 a new programming language. These libraries implement the original, “pure” version of functional-reactive programming, where key parts of programs are written as pure functions that transform input streams into streams of visible GUI content. Such a style is elegant in many cases, but it does not seem compatible with the modularity patterns we demonstrated in Section 2.2, where it is natural to spread input sources to a single stream across different parts of a program. Ur/Web supports that kind of modularity by adopting a hybrid model, with imperative event callbacks that trigger recomputation of pure code.
As far as we are aware, Ur/Web was the first Web programming tool to support impure functional-reactive programming, but the idea of reactive GUI programming in JavaScript is now mainstream, and too many frameworks exist to detail here.
One popular JavaScript framework is Meteor,d distinguished by its support for a particular reactive programming style. It integrates well with mainstream Web development tools and libraries, which is a nontrivial advantage for most programmers. Its standard database support is for MongoDB, with no transactional abstraction or other way of taming simultaneous complex state updates. Like Opa, Meteor allows modules to encapsulate named database elements, but an exception is thrown if two modules have chosen the same string name for their elements; module authors must coordinate on how to divide a global namespace. Meteor supports automatic publishing of server-side database changes into client-side caches, and then from those caches into rendered pages. In addition to automatic updating of pages based on state changes, Meteor provides a standard DOM-based API for walking document structure and making changes imperatively, though it is not very idiomatic. Meteor’s machinery for reactive page updating involves a more complex API than Ur/Web’s. Its central concept is of imperative functions that need to be rerun when any of their dependencies change, whereas Ur/Web describes reactive computations in terms of pure code within the signal monad, such that it is easy to rerun only part of a computation, when not all of its dependencies have changed. Forcing purity on these computations helps avoid the confusing consequences of genuine side effects being repeated on each change to dependencies. The five lines of code near the start of Section 2.2, together with the <dyn>
pseudotag, give the complete interface for reactive programming in Ur/Web, in contrast with tens of pages of documentation (of dynamically typed functions) for Meteor.
Other popular JavaScript frameworks include Angular.js,e Backbone,f Ractive,g and React.h A commonality among these libraries seems to be heavyweight approaches to the basic structure of reactive GUIs, with built-in mandatory concepts of models, views, controllers, templates, components, etc. In contrast, Ur/Web has its 5-line API of sources and signals. These mainstream JavaScript frameworks tend to force elements of reactive state to be enumerated explicitly as fields of some distinguished object, instead of allowing data sources to be allocated dynamically throughout the modules of a program and kept as private state of those modules.
4. Conclusion
We have presented the design of Ur/Web, a programming language for Web applications, focusing on a few language-design ideas that apply broadly to a class of distributed applications. Our main mission is to promote two desiderata that programmers should be asking for in their Web frameworks, but which seem almost absent from mainstream discussion: stronger and domain-specific modes of encapsulation and simple concurrency models based on transactions.
Ur/Web is used in production today for a number of applications, including at least onei with thousands of paying customers. We list more examples elsewhere.7 We have also written elsewhere about Ur/Web’s whole-program optimizing compiler,6 which has placed favorably in a third-party Web-framework benchmarking initiative,j for instance achieving the second-best throughput (out of about 150 participating frameworks) of about 300,000 requests per second on the test closest to a full Web app.
Acknowledgments
This work has been supported in part by National Science Foundation grant CCF-1217501. The author thanks Christian J. Bell, Benjamin Delaware, Xavier Leroy, Clément Pit–Claudel, Benjamin Sherman, and Peng Wang for their feedback on drafts.
Figures
Figure 1. A simple chat-room application.
Figure 2. A modular factorization of Figure 1.
Figure 3. Append-only log module for GUIs.
Figure 4. A client-code-heavy chat-room application.
Figure 5. Final chat-room application.
Figure. Watch the author discuss his work in this exclusive Communications video. http://cacm.acm.org/videos/ur-web
Join the Discussion (0)
Become a Member or Sign In to Post a Comment