Dev Diary (February 2017)
Corey Montella - 08 Mar 2017
(Eve is a new programming language, and this is our development blog. If you’re new to Eve, start here)
In February, we continued our efforts to refactor the runtime, making progress to the point where we can run the Flappy Bird example in the new codebase. We’ve also invested a lot of time commenting the new codebase, explaining how Eve works internally. For instance, here is an explanation of how joins work in Eve. When the dust settles on this refactor, we hope that the increased documentation/commenting will lead to a better understanding of how Eve works for those interested.
Platform
The runtime refactor has three goals:
- Create a better extensibility story for Eve
- Allow easier incorporation of Eve into existing projects and with existing JS libraries and frameworks
- Improve the performance of the Eve runtime
Let’s look at how the refactor is accomplishing those so far.
Watchers
Watchers are the primary interface to get data into and out of Eve. Let’s say you have an Eve program that produces records tagged #email/send
. Currently there is no way to send e-mails from within Eve, but if you had those records in Javascript, you could use an existing library. This is where watchers come in. To access records in Eve from Javascript, you would write a watcher that searches for records of interest, and then handles them in a callback. For instance:
program
.watch("Send emails", ({find, record}) => {
let email = find("email/send");
return [
record({to: email.to, from: email.from, subject: email.subject, body: email.body})
];
})
.asObjects<{to:string, from:string, subject:string, body:string}>((diff) => {
for(let email of diff.adds) {
let {to, from, subject, body} = email;
sendAnEmail(to, from, subject, body);
}
});
This watcher finds records tagged #email/send
, and makes them available as objects in Javascript. When a new record is created in Eve, this is reflected in diff.adds
, and processed in the supplied callback function. In this case, each element in diff.adds
goes through the hypothetical Javascript function sendAnEmail()
.
Watchers also accomplish the related task of getting data into Eve, with a call to this.program.inputEAVs(/* list of EAVs */)
, where the argument is just a list of EAV triples.
Some examples of these interfaces at work are in the renderer, which turns Eve records into DOM objects, and turns DOM events (like click) into records.
Function Interface
We’ve also simplified the function interface in Eve, making it easier for you to use foreign functions. This is a little more complicated in Eve than other languages, because Eve makes some assumptions about functions that Javascript does not guarantee:
- Functions in Eve are referentially transparent, meaning for a given input, the function produces the same output.
- Functions in Eve are monotonic, meaning more inputs cannot lead to fewer outputs.
For a function to fit into the Eve model, it must meet these qualifications. Therefore, to use Javascript functions in Eve, you must wrap the function and guarantee these two conditions yourself. Fortunately, as I stated above, this is much easier in the refactored runtime. Take the plus function, for instance:
makeFunction({
name: "math/+",
args: {a: "number", b: "number"},
returns: {result: "number"},
apply: (a:number, b:number) => {
return [a + b];
}
});
As the function wrapper, you supply its name, arguments, returns, and then the function definition. What about a function like random
, which would seem to violate the conditions outlined above? This is an example where the function wrapper must take greater care to ensure the function behaves properly in Eve. By memoizing the input seed using the function’s internal state
, we can ensure that the random
function in Eve is referentially transparent:
makeFunction({
name: "random/number",
args: {seed: "any"},
returns: {result: "number"},
initialState: {},
apply: function(seed:RawValue) {
let state = this.state;
let result = state[seed];
if(result === undefined) {
result = state[seed] = Math.random();
}
return [result];
}
});
This interface still needs some more work, to define functions that take a single input and produce multiple outputs. But where it stands, this interface opens the door to using Eve with existing Javascript libraries and functions.
Performance
Performance concerns were a heavy motivating factor for undergoing the current refactor. The v0.2.x runtime executed blocks in batches, meaning that if a fact changed between ticks, the affected blocks would be entirely recomputed, even if the change did not impact the final output of the block. For instance:
search
x = [#foo]
foo.value > 10
bind @browser
[#div text: foo.value]
If a #foo
is added with a value less than 10, this entire block will be recomputed, even though the output is not changed (since the added fact is filtered from the output).Obviously, this resulted in needless overhead, but with exponential time complexity, it also presented an upper-bound on how many facts Eve was able to handle. The new runtime executes incrementally, recomputing only those facts which have changed since the preceding tick. This execution model operates with linear time complexity, and so the upper limit on facts has increased drastically.
As a comparison test, we streamed updates through a block that created an HTML record with text in it (like the block above). The old runtime would time-out at around 3k updates, which took ~70 seconds. Compare this with the new runtime, which can stream 10k updates in ~400 - 500 ms. We’ll measure performance more accurately when the runtime refactor work is complete, but this preliminary result shows great potential.
The work to make the Eve runtime incremental we adapted heavily from the awesome research Frank McSherry has done on Differential Dataflow. Translating those ideas to a runtime for Eve has led us to some interesting discoveries (maybe even some fundamental contributions!) that we’ll talk about once the dust settles.
Community
Eve Examples
Last month we posted several example applications, meant to distill common Eve patterns into small, self-contained applications.
- Example 1 - Crazy Mike’s - Demonstrates how to set up a multi-page application.
- Example 2 - The Sandlot - Demonstrates how to create and resuse a custom component.
- Example 3 - Jurassic Park - Demonstrates the creation and use of a form component.
These examples (and more to come) are available in the eve-examples repository, located here. We’ll have Example 4 available on Monday, which will further make use of and extend the form component introduced in Example 3.
Contributions
We’d like to extend a thank you to everyone who contributed to Eve this month. With the codebase currently undergoing refactoring, we realize contributing can be difficult at this time, but we appreciate everyone who took the effort!
Eve Around the World
We’ve gotten word of two new meetups, one of which is happening early next month
New Orleans, March 7
Tyler Tallman give a talk on Eve as part of the New Orleans Functional Club, which happened last night. We’ll check in with them and see how it went.
San Francisco Bay Area, TBD Early April
Andrey Fedorov is planning a talk about Eve for the next 20/20 Salon meeting in the Bay Area next month. The date is tentatively set for early April, so we’ll keep you updated when a firm date becomes available. If you’d like to attend, they ask that you RSVP so they can accommodate everyone.
If you’d like to host your own meetup, let us know, and we’ll do what we can to support your event.