Channels ▼
RSS

Open Source

Language of the Month: Opa


DOM Manipulation and MVC

Opa offers tight integration with XHTML for the presentation layer. This code is a simple but complete application that presents a button. Upon clicking the button, the present date is displayed or updated.

Page() =
  date_id = Dom.fresh_id()
  show_date(_) =
    Dom.transform([#{date_id} 
       <- <>{Date.to_string(Date.now())}</>])
  <button onclick={show_date}>Click for date</>
  <span id=#{date_id} />
server = one_page_server("Date", page)

The last two lines of the Page function are its result: the XHTML representation of the page. The first tag is a <button>. Its onclick attribute contains Opa code to handle the event. In this case, it's a show_date function that uses the Dom.transform function to perform a list of transformations on the page DOM. Here, it simply replaces the content of the element with date_id with an XHTML representation of the current date.

This example conforms to the MVC architecture. Opa does not enforce MVC structure, but MVC is supported. It's perfectly possible to separate different layers of Opa programs, with the database serving as a model, the controller being the logic in the code, and the view embodied by XHTML, possibly factored out of the program using Opa's templating library.

Mutability and State

It is well known that mutation-free code is much easier to reason about. It is equally well known that every non-trivial program will have mutable-state. Opa tries to find a balance here by being mostly pure, with the exception of database operations and sessions.

Opa uses sessions to capture and handle mutable state. A new session is created like this:

Session.NonBlocking.make : 'state, ('msg, Session.NonBlocking.handler('state) -> void) -> channel('msg)

This function takes two arguments: the initial state of the session and a callback function that is called every time a message is sent to the session. The callback function has two arguments: the message and a handler. The make function returns a channel that can be used to send messages to the session:

send : channel('message), 'message -> void

The handler in the callback function can be used for retrieving and updating the state:

Session.NonBlocking.get : Session.NonBlocking.handler('state) -> 'state
Session.NonBlocking.update : ('state -> 'state), Session.NonBlocking.handler('state) -> void

Session.NonBlocking.get returns the current state associated with a session. Session.NonBlocking.update updates this state.

This may look simple, but make no mistake: Sessions are a powerful concept in Opa, especially in combination with location transparency, which ensures that session communication works regardless of the location of the sender and the session.

Opa further builds on the concept of sessions with cells (which are essentially sessions that return results to a received message) and with networks (which offer infrastructure for broadcasting information to observers). Together, these notions serve as a high-level backbone for distributed communication in the Opa ecosystem.

Client-Server Separation

The biggest source of complexity in Web development is the distribution of functionality between the client and the server. (And of course, it is common for many clients and multiple servers to be involved.) Opa transparently takes care of dividing the application between the client and the server, and it takes care of all the communication between the two.

The Opa compiler decides where to put code on a case-by-case basis, marking each function as client-only, server-only, or both. The functions for implementing the user interface (manipulating the DOM) are clear candidates for the client side, of course, while functions that access the database will need to be kept on the server. The compiler also attempts to minimize communication overhead.

A call to a function residing on the "other" side is changed into a remote procedure call, with all the serialization, deserialization, and communication handled transparently. The fact that this is done automatically saves developers lots of trouble and makes client-server communication more secure. This automatic process works well most of the time, but sometimes performance considerations require tweaking with the @client, @server, and @both directives. Similarly, safety precautions may require use of visibility directives such as @private and @publish.

Showcase

The canonical example of a Web application is chat. Let's see how a simple chat program can be coded in Opa.

type message = { author : string ; text : string }

@publish room = Network.cloud("room") : Network.network(message)

user_update(x : message) =
  line = <div class="line">
            <div class="user">{x.author}:</>
            <div class="message">{x.text}</>
         </>
  do Dom.transform([#conversation +<- line ])
  Dom.scroll_to_bottom(#conversation)

broadcast(author) =
  do Network.broadcast({~author text=Dom.get_value(#entry)}, room)
   Dom.clear_value(#entry)

start() =
   author = Random.string(8)
   <div id=#header><div id=#logo></></>
   <div id=#conversation onready={_ ->
      Network.add_callback(user_update, room)}></>
   <div id=#footer>
      <input id=#entry onnewline={_ -> broadcast(author)} />
      <div class="button" onclick={_ ->
         broadcast(author)}>Post</>
   </>

server = Server.one_page_bundle("Chat", 
   [@static_resource_directory("resources")],
      ["resources/css.css"], start)

At fewer than 25 lines of code (not including CSS), this app is concise. You can see this application in action here (open the app in two browser windows if there is no one else around). The complete source code (including resources, CSS, and comments) is available here.

...And More

Clearly, one can't cover all the features of a new language in such a short article. If you want to learn more, go to http://opalang.org, where you'll find a manual, sample apps, and a blog with tutorials and discussions. If you want to peek under the hood and see the implementation of Opa, it is open source and you can download it from GitHub.


—Adam Koprowski is a tech evangelist at MLstate.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video