The rock-solid foundation for Eve’s big vision

2,023 reads

Welcome to Part V of my VI-part series about Eve, an exciting and fascinating new programming language.

There’s a lot more to be done and a lot more to figure out, but fortunately we have a sound underlying foundation which we get to leverage to make sure things don’t get too far off the rails. It’s nice knowing that as long as we can resolve our semantics back to X, that we can maintain the properties of X. It’s a very tough thing to accomplish, but the results seem to have turned out so far :)

— Chris Granger, one of the founders of Eve.

In this post, I’ll try to unpack Chris’s statement by listing various areas of programming where I think Eve can fundamentally improve things, thanks to the properties of its core building blocks.

Declarative programming

Databases typically have declarative query language semantics. In a SQL SELECT query, you say what data you want to search for, not how the query planner should go about looking for it.

But application-layer languages typically don’t have declarative semantics. As Datomic remarks in its querying docs:

[D]eclarative programming is very powerful, and it is a shame that it has been relegated to the database servers and not available to application programmers.

Languages always offer some support for declarative constructs, like getting a filtered view of an array:

But usually it’s not baked into the fundamental building blocks of the language; e.g. you can’t get a filtered view of your variables:

With Eve, you get to read your application-layer state declaratively, because you get to use the same search operation that works for your database layer at every layer of your stack.

Validating data

Database data

Think about what your application’s database looks like right now. It probably has various data-validation issues sprinkled through it that you aren’t aware of, right?

At the time that your program inserted the offending data, either it had a bug, or else it was running a previous version of the code with a different idea about what’s valid. Then when you fixed the bug or updated your program logic, you didn’t have an easy way to clean out every little inconsistency that might have remained. So you said, “screw it, I’ll just fix any bug reports if and when I get them”.

Application state

There’s an analogous problem at the application layer: how confident are you that your state is always correct? Even if you have plenty of asserts, it’s hard for them to police your whole program.

Eve’s solution

With Eve, you just write a little block of code and you get a global data-validation rule, like this:

The key is that this block doesn’t have to be invoked by another block of code; it gets invoked by the invalid data it’s watching for! That’s why Eve has a better foundation for data validation than traditional databases and programming languages.

Types and schemas

Types and schemas are really just constraints that pieces of data are expected to obey. That means we can build type systems and schemas in Eve by writing data-validation blocks like the one above. Well, you and I probably won’t do it that way, but that’s the foundation on which a variety of higher-level libraries will be built.

Dependency tracking

Say your program has a variable x , and at various points it also needs to know the value of x rounded to the nearest hundred:

Well, you probably don’t want to give nearestHundred its own variable because you don’t mean it to be a separate degree of freedom in your program state; you mean it to be a fixed dependency of x . So instead, you might write it as a function:

That’s fine for a simple Math.round , but how about if getNearestHundred were a CPU-heavy computation? Then you’d want to compute it the fewest possible number of times — i.e. compute it once, then cache the result until x changes. If your language or framework helps you do that automatically, that’s called dependency tracking.

Dependency tracking is similar to memoization, but we can’t memoize getNearestHundred because it’s not a pure function — it depends on x but it doesn’t take x as an argument.

There are basically two ways to deal with nearestHundred -like situations:

Become functional programming purists and never let functions access any program state except for their arguments. So getNearestHundred() becomes getNearestHundred(x) , and so on everywhere in our code. This is what purely-functional languages like Elm make you do. Use a dependency-tracking framework. This is what the JavaScript library MobX is best known for, although it’s also been done by Meteor, Knockout, Derivable and others.

(Redux is an awkward middle ground between #1 and #2: you break chunks of state away from your UI views and quarantine them in a functional-programming-only zone.)

In Part I, I pointed out that a bind block in Eve is similar to a computed expression in MobX. Intuitively, both are similar to a spreadsheet formula:

@computed function B1() {return (Math.round(A1) / 100) * 100}

With Eve, you get to enjoy language-level dependency tracking, using nearestHundred anywhere in your code the same way you’d use cell B1 anywhere in a spreadsheet.

Runtime debugging

Watch expressions

How often do you bother to use watch expressions for runtime debugging? It’s usually not worth the effort because state is so tightly coupled to scope chains.

In Eve, you also don’t need watch expressions, because a search section already is a watch expression. If you want to use a search block for debugging, all you have to do is add a commit or bind section to write to the @view database.

Hot reloading

Hot reloading is typically a luxury that has to be painstakingly hacked into most languages and frameworks. But Eve shipped it on day zero, thanks to the language’s hot-reloading-friendly building blocks.

In the editor, each block of code can be activated or deactivated with a checkmark, and everything magically adjusts.

Serializable state

The entire state of a running Eve program is easily serializable into a file. Your users can attach a “state file” to their bug report and you can reproduce their exact state:

Smarter UI inspector tools

A web browser’s inspector tool does an admirable job of showing you the state of the UI, but it doesn’t understand how your code is related to that state. Eve understands why the UI looks the way it does.

The demo video already shows impressive GUI features for jumping to relevant blocks of code when inspecting a UI.

…and more

In addition to Eve’s improvements on existing debugging tools, we can expect the Eve community to conceive of better debugging tools than we’ve ever had. In fact, I dare to imagine that Eve will make console prints obsolete.

By the way, you know what debugging feature Eve doesn’t have? Stack traces.

“Focusing on the machine and ignoring the human factors of software engineering have led us down a difficult road.” — witheve.com

Unit testing

In traditional unit testing, it’s often hard to fake an entire scope-chain-based state. Also, it’s sometimes hard to isolate a modular unit to test. With Eve, you can always test individual blocks of code and get a clear picture of their data flow.

Performance optimization

If you’ve ever used a CPU profiler to figure out how to optimize a loop somewhere, then you know it’s a pain to understand which level of the call stack is the real culprit. Say function A calls function B, which calls function C. If A is using a lot of CPU, that doesn’t necessarily mean that A is badly written; it could also be B or C.

Eve doesn’t have a call stack; blocks of code can’t directly invoke other blocks of code. So if something is slow, you can easily isolate which block of code is responsible.

And if you’ve ever used a memory profiler to improve memory leaks… well, traditional memory leaks are impossible in Eve! There’s no way for obsolete state to get trapped in a gnarly chain of closures, so no memory profiler needed.

Ask yourself, how many memory leaks have you ever dealt with? And how many times have you ever had a bug involving inserting a ton of records into a database? Ok great, because the only way your Eve program can hog tons of memory is if you’re purposely committing tons of records into a local database.

Distributed data and computation

It’s hard to manage the chaos of things happening in parallel, whether it’s data or computation. The website for Bloom, the original Dedalus-based language, explains the problem:

Much of the pain in traditional distributed programming comes from this mismatch: programmers are expected to bridge from an ordered programming model into a disordered reality that executes their code.

Eve is an “orderless language”. Eve’s only ordering guarantee is synchronization of blocks. At each timestep, all code blocks (each with one or more search / bind / commit sections) read in that timestep’s snapshot of the world, and they all write to the next timestep’s snapshot of the world. Kind of like Smalltalk and protein programming.

Eve has the potential to make distributed data and computation more accessible:

Writing simple Eve code to manage complex data transactions at scale, the kind that require locking multiple distant shards.

Using Eve to spread a computationally-intensive task across a multi-core CPU or a massive server farm in the cloud, by writing the same code you’d write for a single core machine.

The Eve team calls this vision the “world-scale computer”.

Next post:

VI. Why Eve will be perfect for realtime apps

Tags