It can, but it's more dangerous.
defmacro
has three main features:
- It runs at compile time;
- It is a genuine in-place substitution; and
- Because of the whole nature of Lisp syntax, it works at an AST level, not a source-code level.
By contrast, eval
:
- Executes at runtime;
- Is not an in-place substitution; and
- Works with strings.
Why does this matter?
First, eval
has a speed penalty: because it has to evaluate its arguments every single time it's called, instead of just once at compile time, you'll have a speed hit on each run of the program.
Second, there is no compile-time validation that the eval
is actually getting passed something sane. You can have a horrible syntax error in your eval
argument (or, worse, a horrible syntax error only when called in certain ways), but it won't be caught until runtime far down the road. Because Lisp macros are compile-time, you can't have that. Your macro might not work, mind you, but it'll compile by definition to something valid.
Third, eval
has serious safety issues compared to defmacro
. Because Lisp macros work with the AST, there's a high degree of safety. No matter what argument you supply the macro at runtime, those arguments can't allow a malicious caller to "escape" the macro, or call malicious code. That's not true of eval
: if you don't think through your string escapes very, very carefully, and you allow any form of user-generated input into the eval
string, you're risking allowing malicious code to run.
Okay, but, whatever, I can hear you say. The user would have to do that to himself, and it's already his browser, so why care?
Because there are lots of ways to inject scripts in that situation. Cross-site script requests, for example, or forgetting to escape a string properly that you store in a database. There are lots of ways of getting malicious values into those macros--and the moment that happens, your users' data is toast.
Finally, even if all of that doesn't dissuade you, JavaScript's eval
in particular is simply less powerful. There are odd ways that it interacts with the enclosing variable scope and the global variable scope, for example, and there's no sane way to do the equivalent of Common Lisp's gensym
for variable hygiene.
Can you use eval
in some of the places you might use defmacro
? Sure. But they are hardly interchangeable, and I hope that the above explanation makes it clear why absolutely zero good JavaScript frameworks I know use eval
.
Instead, if you really find yourself wanting macros, go find a compiles-to-JavaScript language that has them, such as ClojureScript. That way, you'll get the benefits of Lisp macros without the drawbacks of eval
.
I'm not aware of a published implementation of this technique, but it's a fairly standard way of using promises. An interesting side effect of the composability, reusability, and extensibility of functional programming is that often you don't find things in libraries that you would expect.
In a less composable paradigm, you would need support in a library to do something like your throttle function, because you have to weave support for it throughout other functions. In functional code, programmers just write it themselves once, then they can reuse it everywhere.
After you write something like this, who do you share it with, and how? It's too small to be its own library. It's a little too specific to not be considered bloat in a promise library. Perhaps some sort of promise utilities library, but then it would be in a not very cohesive module with other functions vaguely related to promises, which makes it hard to find.
What happens to me a lot is I search for 10 minutes or so, then just write it myself because it would only take me a half hour. I put it in a "utilities" file I keep around with other odds and ends functions that are short but highly reusable, but don't really fit anywhere. Then six months later I happen upon it by chance in a community-maintained library with some weird name.
My point is, with functional programming, not being able to find an existing implementation for something that feels fairly "standard" is not a sign that other people are either doing it in a superior way, or that you're the first to come up with it. It's just a symptom of the difficulty of sharing short, reusable code in a way that's easy to discover by a search.
Best Answer
What you are looking for is a simple expression language that can be evaluated from within ECMAScript (which is just another way of saying there exists an interpreter written in ECMAScript). Thankfully, such a language and interpreter already exists: Jexl.
Jexl is a simple ECMAScript expression language that features pretty much everything you listed:
This last one is not exactly what you are asking about, because you were asking about accessing arbitrary ECMAScript identifiers, but it is arguably more safe: you can only access what is explicitly passed in. And if you really wanted to, you could pass in the context object
{ window: window }
and then your expression has access to the globalwindow
object and can do every evil thing you never imagined.It also has some features you didn't list:
listOfPeople[.name == "John"]
will return a sub-array oflistOfPeople
with only those people whosename
property is"John"
and"A;B;C"|lower|split(";") // => ["a", "b", "c"]
.It sounds like this is exactly what you are looking for.