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.
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.
[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.
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”.
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.
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 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.
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.
There are basically two ways to deal with
getNearestHundred(x), and so on everywhere in our code. This is what purely-functional languages like Elm make you do.
(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:
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.
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
bind section to write to the
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.
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:
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.
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.
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.
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.
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
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:
The Eve team calls this vision the “world-scale computer”.