A Declarative Clock in Eve
Corey Montella - 21 Jul 2016
This post has been updated. See the original here.
An analog clock is an obligatory example for reactive programming languages. It’s got all the right pieces: an event stream that changes over time, and a way to present those changes visually. Here is our version:
This clock is written in the Eve developer syntax, which is proposed in our first Request for Comments (RFC). In this post, I’ll go through the code and explain how it works.
Eve code is written as a series of blocks, each of which can be thought of as operating in two phases:
- Phase 1: Match - select objects from the Eve DB by matching patterns
- Phase 2: Action - change the Eve DB by either adding, setting, removing, or merging objects.
The clock program consists of two blocks: the first generically defines a clock hand, while the second defines the drawing of the clock. Let’s focus on each block in turn.
Draw a clock hand
First, we define how a clock hand is drawn. Although an analog clock has three hands, we can write code as if we’re drawing a single hand; Eve’s set semantics take care of drawing multiple hands, so no iterators like a
for statement are necessary.
match // Select #clock-hands with angle and length attributes hand = [#clock-hand angle length] // Calculate coordinates for drawing a hand x2 = 50 + (length * sin[angle]) y2 = 50 - (length * cos[angle]) // Bind tells Eve to update objects as values change bind // Merge line coordinates into hand, tag it as a #line hand <- [#line, x1: 50, y1: 50, x2, y2]
Blocks start with the
match phase, indicated by the
match fence. Within the match, the programmer gathers all of the objects from the Eve DB needed to complete the block. Blocks only enter the
action phase if all supplied objects in the
match resolve against an object in the Eve DB.
In the first line of the match, we encounter our first object:
hand = [#clock-hand angle length]
Objects are a set of attribute:value pairs enclosed in square brackets. They ask Eve to find all the entities that fit the supplied attribute shape. The
hand object seen here is asking Eve to find every object tagged
#clock-hand that also has the attributes
length. We use the
length attributes of this object to calculate two values,
y2, which we will use as coordinates for the lines that draw the clock hands.
Now we enter the action phase of the query, indicated by the use of the
bind fence (one of two fences applicable to the
action phase). The
bind fence says that we are finished reading from the Eve DB, and now we are ready to write to it. The use of the
bind fence in particular says that as the objects in the
match change, Eve will keep their dependant objects up-to-date.
hand <- [#line, x1: 50, y1: 50, x2, y2]
Here, we use the merge operator
<-, one of four action operators. The merge operator merges one object with another. In this case, we are merging the object
[#line, x1: 50, y1: 50, x2, y2] into the
hand object. This means that every
length attributes also has a
#line tag, as well as
That’s all this block does: it selects every
[#clock-hand angle length], and tags each one as a
#line, which Eve knows how to draw using
y2. Let’s see how that’s done.
Draw a clock
In this block, we define the clock using a face drawn as a circle, and three
#clock-hands drawn as lines.
match // Select the current time [#time hours minutes seconds] // Update the SVG as the time changes bind // Add an SVG element to the root of the DOM [#svg viewBox: "0 0 100 100", width: "300px", children: // Add a clock face at (50,50) with radius 45. [#circle cx: 50, cy: 50, r: 45, fill: "#0B79CE"] // Add the hours hand [#clock-hand @hour-hand angle: 30 * hours, length: 30, stroke: "#023963"] // Add the minutes hand [#clock-hand @minute-hand angle: 6 * minutes, length: 40, stroke: "#023963"] // Add the seconds hand [#clock-hand @second-hand angle: 6 * seconds, length: 40, stroke: "#ce0b46"]]
This block (as with all blocks) follows the familiar match -> action pattern. For this block’s match, we select the current time, represented by the
#time object; and its attributes
seconds. This object is just like any other, but under the covers Eve keeps it up to date as the system clock changes.
Again we use the
bind fence, because we want Eve to update the clock drawing as the time changes. Behind the
bind fence, we add an SVG element:
[#svg viewBox: "0 0 100 100", width: "300px", children: ... ]
Since no parent is specified, the SVG element will be rendered as a child of the DOM root. Next, we add the clock elements as children of the SVG element. We add the clock face (a blue circle with a center at (50, 50) with radius 45):
[#circle cx: 50, cy: 50, r: 45, fill: "#0B79CE"]
and three hands:
[#clock-hand @hour-hand angle: 30 * hours, length: 30, stroke: "#023963"] [#clock-hand @minute-hand angle: 6 * minutes, length: 40, stroke: "#023963"] [#clock-hand @second-hand angle: 6 * seconds, length: 40, stroke: "#ce0b46"]
Now we can see how this block ties back to the first one; the objects representing the hands of the clock are tagged
#clock-hand and have
length attributes. This is exactly the pattern we were matching against in the first block! Thus, the loop is completed, and the clock hands are drawn as lines based on the current time as an angle.
Try it yourself!
You can download Eve and try the clock yourself! The easiest way is to use our docker container:
docker pull witheve/eve
You can also download the Eve source and build it yourself (Linux and OSX only for now):
git clone https://github.com/witheve/Eve.git
Instructions to build are available on the witheve/eve repository.