HipHop is a
DSL
dedicated to Web orchestration. HipHop programs are
expressed as
AST which are
evaluated on demand by
a HipHop Reactive
Machine. HipHop is provided as a Hop library which
has to be included in your source files through the
library
directive, within the
Hop module
clause:
(module … (library hiphop) ; other module options …)
HipHop DSL constructors are suffixed by
the &
(ampersand) character, to distinguish
them from Hop constructs. HipHop use the same prefix,
parenthesized as Lisp and Scheme language families. For
instance, to pop-up the text "Hello, World!"
in HipHop, one can write:
(atom& (alert "Hello, World!"))
The atom&
HipHop primitive wraps Hop
code to be executed when HipHop decides so. The primitive
execution takes no time from the HipHop point of view, as
such the HipHop execution waits for the Hop code
to terminate.
HipHop ASTs are Hop values. As such, they can be computed by Hop code. Hop functions can return HipHop code, enabling the programmer to reuse code:
(define (pop-up& text) (atom& (alert text))) (seq& (pop-up& "Hello") (pop-up& "World"))
The first code block defines a Hop function
named pop-up&
returning
a HipHop atom&
instruction, parameterized
by the text
argument. We voluntary choose to
suffix our function name with an ampersand as this
function returns HipHop code.
The second block starts a sequence of two HipHop
instructions, using the function defined above. The code
is equivalent as repeating twice the
(atom& (alert …))
snippet in
a seq&
block.
HipHop instructions forms programs, and such programs are executed by reactive machines. Machines are responsible for isolating HipHop environment from real-world, Hop, environment by taking snapshots of it. Machine executions are driven by Hop programs.
Machines are Hop object that must be instantiated with
a HipHop program. Here we create a HipHop machine, bound
to the Hop variable M
running the "Hello
World" example above.
(define M (instantiate::HipHopMachine (program (pop-up& "Hello, World!")))) (hiphop-react! M)
The hiphop-react!
function starts
a reaction of the HipHop
machine, computing its new state, taking into account the
current state and the current environment. The call to
the hiphop-react!
function may be triggered
by some event occurring on the web page.
Here we attach an event handler to
the <body>
element. Each time the
element is clicked, the machine will run a reaction:
(add-event-listener! document.body "click" (lambda () (hiphop-react! M)))
Events are the primary mean to drive HipHop executions. HipHop programs react to events presence or absence, and to their actual value. Events may carry values that can be exchanged with Hop. HipHop is really about handling events, which the Web is full of.
Events have a status (present or absent) at each machine reaction. The presence can be set either within HipHop (the event is said to be emitted during the reaction) or input by Hop, before starting a new reaction.
As machines, HipHop Events are Hop object instances.
Pure (value-less) events are instances of
the HipHopEventPure
class.
(define my-event (instantiate::HipHopEventPure))
With a global machine M
, we
can input the
event my-event
each time a button
is pressed.
(<BUTTON> :onclick (lambda () (hiphop-input-and-react! M my-event)) "click me!")
HipHop knows about events. To pop-up out "Hello, World!"
each time the button is pressed, we need to write
a program that wait for my-event
to occur
before our atom&
:
(every& (now& my-event) (pop-up& "You clicked me!"))
The every&
instruction repeats its body
(pop-up& …)
each time the
condition (now& my-event)
is
true, i.e. each reaction
where my-event
is present.
The construction now&
takes an event as
argument and returns a true
is the event is present,
and false otherwise.
Those Boolean values may be combined using Hop's
Boolean operators.
(if& (and (now& high-temperature) (not (now& window-is-opened))) (atom& (go-open-the-window)))
HipHop programs are easy to read. The above snippet tells that we want to open the window if the window was not opened before, and the temperature is too high.
Events have memory. Analogous to now&
,
HipHop provided pre&
that reports if an
event was present in the previous reaction. For instance,
we want to turn off the air conditioner when a window
is opened:
(if& (and (now& window-is-opened) (not (pre& window-is-opened))) (atom& (turn-off-air-conditioner!)))
To access events' values, HipHop provides
both val&
and preval&
to
respectively access the event's value in the current
reaction or in the previous reaction.
Emitted events are outputs of the reactive machine. Event
listener can be attached to output events to trigger Hop
code execution when a reaction ends. Using output events
to start specific actions instead of
using atom&
directly (as we did above)
may be necessary in situations where we need to start
a long running computation.
Let's assume we want to start the air conditioner when both the indoor and outdoor temperatures are greater than 25°C, we can use the following program:
(define indoor-temp (instantiate::HipHopEventValued (name "in"))) (define outdoor-temp (instantiate::HipHopEventValued (name "out"))) (define start-ac (instantiate::HipHopEventPure)) (define threshold 25) (define machine (instantiate::HipHopMachine (program (if& (and (> (val& indoor-temp) threshold) (> (val& outdoor-temp) threshold)) (emit& start-ac))))) (add-event-listener! indoor-sensor "temp" (lambda (event) (hiphop-input-and-react! machine indoor-temp event.value))) (add-event-listener! outdoor-sensor "temp" (lambda (event) (hiphop-input-and-react! machine outdoor-temp event.value))) (add-event-listener! machine start-ac (lambda (e) (really-start-the-air-conditioner!)))
Line 1 to 3, we create the HipHop events we will use in
the program. Line 4 defines a Hop variable. Line 5, we
define the machine, specifying a simple program that will
emit the event start-ac
(line 10) when both
events indoor-temp
(line 8)
and outdoor-temp
(line 9) have a value
strictly greater than threshold
.
Line 12-13 and 14-15, we register two events handlers to
our temperature sensors. When either one of the two sensor
have new data, we start a reaction of the machine with the
sensor's temperature. Last, line 16-17, we register an
event listener to
execute really-start-the-air-conditioner
procedure when the start-ac
event is actually
emitted in the HipHop machine.
This section describes all HipHop statements provided by the standard library.
In the sequel, the square bracket [,]
characters groups arguments. We use the traditional regexp
quantifiers ?,*,+
to denote respectively
optional, possibly empty and non-empty argument lists.
Here is the list of HipHop expressions and statements:
stmt ::= core-stmt | derived-stmt core-stmt ::= (nothing&) | (emit& event host-expr*) | (atom& host-expr) | (pause&) | (if& host-expr stmt stmt?) | (seq& stmt+) | (loop& stmt+) | (par& stmt+) | (genpar& (binding+) stmt+) | (dyngenpar& (binding+) :when await-expr stmt+) | (suspend& [:immediate? bool]? await-expr stmt+) | (trap& identifier stmt+) | (exit& identifier) | (local& (binding+) stmt+) derived-stmt ::= (halt&) | (sustain& event host-expr*) | (cond& clause* (else stmt+)?) | (await& [:immediate? bool]? await-expr stmt*) | (abort& [:immediate? bool]? await-expr handler? stmt+) | (until& [:immediate? bool]? await-expr handler? stmt+) | (loop-each& [:immediate? bool]? await-expr stmt+) | (every& [:immediate? bool]? await-expr stmt+) host-expr ::= (now& event) | (pre& event) | (val& event hop-expr*) | (preval& event hop-expr*) | hop-expression | (host-expr+) binding ::= (identifier host-expr) clause ::= (host-expr stmt+) await-clause ::= (await-expr stmt+) handler ::= (handler& stmt) await-expr ::= host-expr | (times& host-expr host-expr)
The nothing&
statement does nothing and
terminates instantaneously. It is the HipHop no-op.
The emit&
statement emits its event with
values determined by the rest of its argument. It
terminates instantaneously.
The atom&
statement calls Hop to executes
its argument as a Hop expression; it is instantaneous,
which means that its Hop argument execution time should be
kept negligible in practice.
The pause&
statement delays execution by one
reaction. When executed, it pauses for the reaction and
terminates at the next reaction.
The if&
statement instantaneously evaluates
its test. If the result is true, it immediately starts its
second HipHop argument and behaves as it from then on;
otherwise, it immediately starts its third
argument. Notice that these arguments can be arbitrary
instantaneous or delayed HipHop statements. Termination of
the if&
statement is instantaneously
triggered by termination of the selected branch.
The seq&
statement executes its arguments in
order: the first HipHop statement starts immediately when
the sequence starts; when it terminates, be it immediately
or in a delayed way, the second argument is immediately
started, etc. For instance,
(seq& (emit& A) (emit& B))
immediately
emits A
and B
, which are seen as
simultaneous within the reaction, while
(seq& (emit& A) (pause&) (emit& B))
emits A
and B
in two
successive reactions.
The loop&
statement is a loop-forever,
equivalent to the infinite sequential repetition of its
argument statements, themselves implicitly evaluated in
sequence. For instance,
(loop& (pause&) (emit& A))
waits for the next
reaction and then keeps emitting A
at each
reaction. It could be infinitely expanded into
(seq& (pause&) (emit& A) (pause&) (emit& A) ...)
.
A loop&
can only be exited from
within using the exit&
statement below. It
can also be killed from outside by statements such
as until&
and abort&+
The par&
statement starts its arguments
concurrently and terminates at the reaction where the last
of them terminates. Therefore,
(par& (await& A) (await B))
terminates when
both A
and B
have been
received. Remember that all arms of a par&
statement see all statuses and values of all (visible)
events in exactly the same way.
The genpar&
and dyngenpar&
statements create dynamic programs. They are described
separately in the dynamicity
section.
The suspend&
statement immediately starts
its body. At all next instants where its body is alive, it
suspends the execution of its body for the reaction when
its condition is true. The suspend&
statements terminates if its body is executed and
terminates. For instance:
(suspend& (now& A) (loop& (emit& B) (pause&)))
emits B
at first instant and at all
subsequent instants where A
is absent.
The trap&
statement defines a named exit
point for its body.
The exit&
statement provokes immediate
termination of the corresponding trap&
statement, as well as immediate termination of all active
statements within the trap&
body, which do
normally receive the control at that instant.
The local&
statement declares local events
in the first argument list. Their scope is the body, which
is the implicitly seq&
sequential list of the
remaining HipHop arguments. They are not visible from
Hop. A local&
statement terminates when its
body does.
The halt&
statement pauses forever.
The sustain&
statement keeps emitting its
event at each reaction.
The cond&
statement extends if&
as usual in Scheme.
The await&
statement waits for its
expression to become true and terminates.
The abort&
statement instantaneously kills
its body when its condition becomes true, not passing the
control to its body in this reaction; this is what we call
strong abortion.
The until&
statement instantaneously kills
its body when its condition becomes true, but only at the
end of the reaction, passing the control to its body for
the last time at that reaction as for an
exited trap&
; this is what we call
weak abortion.
The loop-each&
statement immediately starts
its body, and then strongly kills it and restarts it
immediately whenever its condition becomes true.
The every&
statement is similar but starts
by waiting for the condition instead of immediately
starting its body.
HipHop introduces dynamic primitives to generate programs
depending on previous computations. Using Hop, one can
already generate programs from statically computed lists,
assuming fetch&
is HipHop code that fetch
some data using an URL:
(define program& (par& (map (lambda (url) (fetch& url)) (list "http://hop.inria.fr" "http://fr.wikipedia.org"))))
which is equivalent to the following code:
(define program& (par& (fetch& "http://hop.inria.fr") (fetch& "http://fr.wikipedia.org")))
The above idiom works fine with Hop lists, which are
known at program elaboration. However, the list may be
computed as a HipHop run-time value (i.e. an
event value. In such a situation, we need a specific
statement to dynamically create parallelism during
run-time. This is the purpose of genpar&
.
Assuming with have an urls
event generated by
Hop (for instance, a user input box), we could re-write
the fetch example as follows:
(define program& (seq& (emit& urls (list "http://hop.inria.fr" "http://fr.wikipedia.org")) (genpar& ((url (val& urls))) (fetch& url))))
The above snippet is actually equivalent to the two
previous ones, but it is important to notice that in the
first two snippets, the par&
statement
statically has two branches, while in the third snippet,
it has zero branch. The two branches will be dynamically
appended to the (hidden) par&
statement when
the genpar&
statement is executed. Once
the genpar&
branches are appended, the
statement is said to be expanded.
The expansion of a genpar&
only occurs
once. Once expanded it behaves exactly as if
a plain par&
has been used in place
of genpar&
.
We also introduced dyngenpar&
to provide
a continuously expanding genpar&
-like
behavior. For instance, we want to fetch URLs at each
reaction were new URLs are obtained (maybe from a remote
service), while the previously started fetch&
programs may not have finished yet.
(Using every&
and genpar&
is not
suitable, as every&
would kill unfinished
programs):
(define (program& urls::HipHopEvent) (dyngenpar& ((url (val& urls))) :when (now& urls) (fetch& url)))
The dyngenpar&
statement's syntax is similar
to that of genpar&
; we add a :when
attribute which tells HipHop when to compute new expansions.
Hooks provide user-defined actions that are triggered by the HipHop run-time. Hooks can be attached to any HipHop standard (library-provided) constructor.
A hook is a piece of code that can be attached through
a dedicated attribute. The name of the attribute specify
the kind of event that will trigger the hook
execution. Here is an example for
a abort&
:
(let& ((http (make-client-socket "hop.inria.fr" 80))) (abort& :when (now& cancel) :onkill (socket-close http) (use-data-from-socket& http)))
Using let&
, we create a socket
to hop.inria.fr
when the code is executed,
then run a HipHop program to get the data from the
socket. However, we enable the user to cancel the request
through the cancel
event (e.g.
emitted when the user clicks a big red button). On
abortion, the abort&
body is killed,
triggering the execution of
the (socket-close …)
code.
Here is the list of supported hooks:
:onstart
:
code to be executed when the associated statement is
executed for the first time.:onpause
:
code to be executed when the associated statement (or
its body) pauses (see pause&
).:onresume
:
code to be executed when the associated statement is
resumed (i.e. is has been
previously paused.:onterminate
:
code to be executed when the associated statement
terminates normally.:onexit
:
code to be executed when the associated statement (or
its body) exits (see exit&
).:onkill
:
code to be executed when the associated statement is
killed or aborted.:onsuspend
:
code to be executed when the associated statement is
suspended by a parent suspend&
node.Hooks implementation follows the
DOM API. HipHop
implements add-event-listener!
with the above
events on HipHop statements:
(define code (loop& (atom& :id "hello" (alert "Hello, World!")) (pause&))) (hiphop-add-event-listener! (hiphop-get-element-by-id code "hello") "start" (lambda () (alert "Goodbye, World!")))
Note: if you define
your own HipHop forms, you have to add support for hooks.
WIP: a special
form define-hiphop
(in the manner
of define-tag
in Hop) is getting introduced,
which may automatically add support for hooks in
user-defined HipHop constructs.