WebSocket futures

The webSocket library for use in a web-server is asynchronous and here I will "touch the surface" of programming asynchronous using futures.

A webSocket service has a single onAttach_async predicate

interface webSocketService open core, pfc\asynchronous\

predicates onAttach_async : (httpRequest Request, webSocket WebSocket) -> future{unit}. % @short This predicate is invoked when a client created a webSocket connection to this service. % The service is registered in a requestQueue_webSocket. % @end

end interface webSocketService

You create a request queue from your service:

requestQueue_webSocket::new(threadpool, demoWebSocketService::new)

Which you attach to an urlGroup like you do with the other kinds of services.

The overall scenario is the following:


 * Front end code creates a webSocket connection to your service
 * If successful your onAttach_async predicate will be called with the request that made the connection and with the created "server side end" of the web socket.
 * The request can be used to provide additional information about the connection (who, why, what about, …)
 * The front end and your service can then receive and send messages on the web socket, until one of the ends closes the web socket.

Front-end code can look like this:

The front-end code have callbacks attached to deal with the four events open, message, error and close. I will not go into details about the code: I am certain that any front-end programmer will use some kind of library software on top of the web sockets anyway.

Notice however that a web socket URL looks like this:

ws:// /

In the webServiceDemo example the last part of the URL is used to distinguish between different webSocket services/behaviors:

clauses onAttach_async(Request, WebSocket) = onAttach2_async(filename::getLastDirectory(Request:path, _), WebSocket).

predicates onAttach2_async : (string Last, webSocket WebSocket) -> future{unit}. clauses onAttach2_async("echo", WebSocket) = echoLoop_async(WebSocket) :- !.

onAttach2_async("async", WebSocket) = async_async(WebSocket) :- !.

onAttach2_async("jsonAsync", WebSocket) = jsonAsync_async(WebSocket) :- !.

onAttach2_async("ball", WebSocket) = ball_async(WebSocket) :- !.

onAttach2_async(Service, WebSocket) = WebSocket:writeMessage_async(webSocket::close(210, string::format("Unknown service %", Service))).

You have of course noticed that the predicates ends in _async and that they are functions which returns a future{unit}.

This is because they are asynchronous predicates based on our promise/future library.

Let us first look at some (illegal) pseudo code (inspired by the asynchronous features of C# and JavaScript (ES2017)):

class predicates echoLoop_async : (webSocket WebSocket) asynchronous. clauses echoLoop_async(WebSocket) :- Message = await WebSocket:readMessage_async, if webSocket::close(_, _) = Message then else await WebSocket:writeMessage_async(Message), echoLoop_async(WebSocket) end if.

If you delete the words asynchronous and await the code looks like a completely normal (synchronous) loop. The await indicates that we await the completion of an asynchronous computation before we continue. However we do not actually wait in that point, instead we suspend the computation until the asynchronous computation completes and then we resume the computation again from the await point.

await resembles a vpi::processEvents</vp> in the sense that it is an explicit point where other computations can take place.

The actual running of this code is however quite different, and has more resemblance to our nondeterm</vp> predicates. When we call the asynchronous predicate the execution will continue normally until we meet an await</vp>; at that point it will return to the caller. Later when the await</vp>'ed operation completes we will jump back into the code (just like we jump back into nondeterm</vp> code on failure) and continue the execution, until we meet another await</vp> or finish. But nondeterm</vp> predicates we only return from an asynchronous predicate the first time, the other jump-back-into the code will never return to the original call point again.

I.e. to the caller an asynchronous predicate completes/returns when the first await</vp> is met. The other parts of the code are run when the relevant asynchronous computations complete.

Presumably the caller of an asynchronous predicate is interested in the final result of the computation and have little interest in the "random" state after the first await</vp>. Therefore the caller actually receives a future{…}</vp> object that represents the result of the entire asynchronous computation.

Unfortunately, asynchronous</vp> and await</vp> are not implemented in our language, so you will have to "translate" the code yourself. Hence you will need to know more about future</vp>'s.

A future</vp> represents the result of an asynchronous computation that may not complete until some point in the future. As such it has three major states:


 * it will be pending until the computation is completed (or cancelled)
 * it will be completed once the computation has finished. The completion can happen with:
 * a success indicating that the computation went well
 * or an error indicating that something went wrong (an exception was raised and not caught)
 * it can also be cancelled meaning that the computation was cancelled before it completed

There are only two possible state transitions:


 * A pending future can complete changing to the completed state
 * A pending future can cancel changing to the cancelled state

There are many ways to use future's. Here we will focus on how to "translate" asynchronous predicates into ordinary code using the thenDo</vp> operation on futures</vp>.

<vp>thenDo</vp> is used to handle the complete state transition as a kind of event listening (it will also automatically handle the cancel state transition). It is however more complex than normal event listening, because the result of the event handling is itself an asynchronous operation.

An asynchronous function that returns a value of type <vp>Type</vp> translates into a function that returns <vp>future{Type}</vp>.

If the predicate does not return anything then it is treated as if is a function that returns <vp>core::unit</vp>, and thus translates into a function that returns <vp>future{unit}</vp>. <vp>core::unit</vp> is a domain that only contains the value <vp>core::unit</vp>.

So

class predicates echoLoop_async : (webSocket WebSocket) asynchronous.

translates into

class predicates echoLoop_async : (webSocket WebSocket) -> future{unit}.

Likewise the readMessage_async function in:

Message = await WebSocket:readMessage_async,

Is actually function that returns <vp>future{message Message}</vp>

predicates readMessage_async : -> future{message Message}.

The <vp>await</vp> will translate into a <vp>thenDo</vp> call on the returned <vp>future</vp>; it is called with a continuation that receives the <vp>Message</vp> we await and returns a <vp>future</vp> <vp>F1</vp> that represents the remaining operation.

clauses echoLoop_async(WebSocket) = WebSocket:readMessage_async:thenDo(           { (Message) = F1 :- <<                if webSocket::close(_, _) = Message then                else                    await WebSocket:writeMessage_async(Message),                    echoLoop_async(WebSocket)                end if >>            }).

In the then-part of the if-statement we must return a <vp>future{unit}</vp> that represent the completion of the completion of the entire operation. In that point the entire operation is completed so we create a <vp>future{unit}</vp> that is already completed. This is done with <vp>future::newUnit</vp>:

clauses echoLoop_async(WebSocket) = WebSocket:readMessage_async:thenDo(           { (Message) = F1 :-                if webSocket::close(_, _) = Message then                    F1 = future::newUnit                else <<                    await WebSocket:writeMessage_async(Message),                    echoLoop_async(WebSocket) >>                end if            }).

In the else-part we have a (<vp>future{unit}</vp>) <vp>await</vp> on <vp>writeMessage_async</vp>. This translates into a <vp>thenDo</vp>-call with a continuation that receives the (<vp>core::unit</vp>) result of the <vp>writeMessage_async</vp> call and must return a <vp>future</vp> <vp>F2</vp> that represents the remaining operation:

clauses echoLoop_async(WebSocket) = WebSocket:readMessage_async:thenDo(           { (Message) = F1 :-                if webSocket::close(_, _) = Message then                    F1 = future::newUnit                else                    F1 =                        WebSocket:writeMessage_async(Message):thenDo( { = F2 :- <<                               echoLoop_async(WebSocket) >>                           })                end if            }).

The <vp>future</vp> that represent the remaining <vp>echoLoop_async</vp> call is simply the <vp>future</vp> that this call returns:

clauses echoLoop_async(WebSocket) = WebSocket:readMessage_async:thenDo(           { (Message) = F1 :-                if webSocket::close(_, _) = Message then                    F1 = future::newUnit                else                    F1 =                        WebSocket:writeMessage_async(Message):thenDo( { = F2 :- F2 = echoLoop_async(WebSocket) })               end if            }).

As you can see each <vp>await</vp> creates a nesting <vp>thenDo</vp>. So things that are <vp>N</vp> await's into to the future are <vp>thenDo</vp>-ed <vp>N</vp> levels deeper.

Future variables like <vp>F1</vp> and <vp>F2</vp> are only used in one nesting level and therefore they can have the same name:

clauses echoLoop_async(WebSocket) = WebSocket:readMessage_async:thenDo(           { (Message) = F :-                if webSocket::close(_, _) = Message then                    F = future::newUnit                else                    F =                        WebSocket:writeMessage_async(Message):thenDo( { = F :- F = echoLoop_async(WebSocket) })               end if            }).

Furthermore, they can often be eliminated by shifting to expressions instead of statements:

clauses echoLoop_async(WebSocket) = WebSocket:readMessage_async:thenDo(           { (Message) =                 if webSocket::close(_, _) = Message then                    future::newUnit                else                    WebSocket:writeMessage_async(Message):thenDo( { =                            echoLoop_async(WebSocket) })               end if            }).

More about futures (and promises)


 * http://docs.scala-lang.org/overviews/core/futures.html
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Chaining
Notice that in several JavaScript texts often use chaining instead of deeper nesting (e.g. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises).

Chaining code looks like this (very often formatted like here):

clauses chaining_async = something1_async :thenDo({ (R1) = something2_async(R1) }) :thenDo({ (R2) = something3_async(R2) }) :thenDo({ (R3) = something4_async(R3) }).

It looks nice because you simply have the operations after each other (embedded in some thenDo-noise, but any way).

Such code does work and it looks relatively nice, so why all the nesting?

Well if you compare to a similar nesting structure:

clauses nesting_async = something1_async:thenDo(           { (R1) =                something2_async(R1):thenDo( { (R2) = something3_async(R1, R2):thenDo(                           { (R3) =                                something4_async(R1, R2, R3)                            }) })           }).

You will notice the important difference that the nested version have access to earlier results.

In nesting code it is also quite simple to deal with branching code:

clauses nesting_async = something1_async:thenDo(           { (R1) =                if test(R1) then                    somethingTrue_async(R1)                else                    somethingFalse1_async(R1):thenDo( { (R2) = somethingFalse2_async(R1, R2) })               end if            }).

Chaining will only handle "sequential" actions.

So chaining is actually rather limited, and you might as well get used the nesting method.

(The asynchronous programming constructions in C# and JavaScript corresponds to nesting code, not chaining code).