reflect and get rid of eval!

eval has always been the dark horse of the apocalypse in JS.

But there really is only one thing that still needs to be done to make eval truly unjustifiable and finally get rid of it for good!

A new construct: ā€œreflectā€

Today the only true equivalent of Java’s Class.getClass(ā€œnameā€œ) is eval(ā€œnameā€œ).

But eval can do anything not just new instances!

My proposal is simple:

class Foo{
    constructor(bar)
    {
        console.log(bar);
    }
}

let fooName = "Foo";
let foo = reflect fooName;
if(foo){
   new foo("bar");
}
// prints: bar

There are many cases where you don’t know the class of an object beforehand the most basic of which is that of a driver and a driver factory, you code against a contract not a hardcoded class name!

Reflecting should be restricted to constructors only, providing a safe entry point without weird side usages.

What is this doing exactly?

How is this different from:

let foo = typeof myFoo !== "undefined" && myFoo;

if (foo) {
  new foo();
}

well, reflect receives a variable string not a fixed string - my bad!

Let me refine a bit what I meant!

let fooName = ā€œmyFooā€œ;
let foo = reflect fooName;

if(foo){
   new foo();
}

Edit: I changed it in the OP too! Thanks for the heads-up!

What's an example where this would be useful to have?

Main use case would be drivers and driver factories.

The simplest example I can give is mysql vs D3 on Cloudflare, we have tables in both, both work with SQL and parameters so the signatures of the functions are the same and the sqls are rather basic so they are cross compatible. A service would need to write to a speciffic DB and it knows it just by name, it requests from the factory a name by a string say DBFactory.getConnection(ā€œnameā€œ). The factory would load the config from either an ini or a json and then instantiate the appropriate driver as indicated in the config, and ā€œestablish the connectionā€œ (not a real connection in the case of cloudflare but you get the gist.)

I’m left to either a lot of boilerplate to do the mapping or with eval (in environments that allow it)

I'm also struggling to understand what exactly the proposed syntax does. Is there a local myFoo variable defined above? And the class that created myFoo is what's being returned from this reflect syntax?

The reflect simply returns the constructor reference from a variable string name.

class Foo{
    constructor(bar)
    {
        console.log(bar);
    }
}

let fooName = "Foo";
let foo = reflect fooName;
if(foo){
   new foo();
}

this would print ā€œbarā€œ;

So the idea would be that instead of doing something like this:

import { MySqlDriver } from 'mysql-driver';
import { D3Driver } from 'd3-driver';

const drivers = { MySqlDriver, D3Driver };

export function sendSqlCommand(driverName, command) {
  const myDriver = new drivers[driverName]();
  ...
}

You would do this:

import { MySqlDriver } from 'mysql-driver';
import { D3Driver } from 'd3-driver';

export function sendSqlCommand(driverName, command) {
  const Driver = reflect driverName;
  const myDriver = new Driver();
  ...
}

Which eliminates the boilderplate of creating this drivers variable?

Exactly, and also disuade coders from using eval in the process if the boilerplate doesn’t pre exist.

I can't conceive of why anyone would ever use eval in that scenario in the first place - that assumes they're using globals, which have been a bad practice for the better part of two decades, almost as long as using eval.

Indeed, but the fact that we still have eval after all the new tooling tells me there is still a considerable amount of eval’ed code in the wild.

Also a few mentions here.

1 - reflect should only have access to the lexical scope that new does (no cross module or anything else) - basically if you can’t instantiate you can’t ā€œreflectā€œ.

2 - minifiers would need to preserve the name of the class along with all methods if said class is to be externally usable. they could have something like

let foo = reflect fooName

// becomes after minification:
let a = r[b] 
// where: 
let r = {ā€œFooā€œ:x}; 
// and x is the new name of the class.

3 - if reflect fails it fails quietly as undefined - no error - or minimal error handling like TypeError or RefferenceError.

The fact that eval is still around isn't because we don't have good alternatives for these sorts of scenarios, it's because we need to preserve backwards compatibility - eval can't be removed. Also, eval isn't always evil, there are legitimate uses for it.

Anyways, my concern with the above code examples, it that I find the boilerplate to be very useful when it comes to code comprehension - it tells me, a future maintainer trying to understand the code, exactly what can and can't be used as a string name for this sendSqlCommand function, and it ensures the caller never accesses anything else from your module’s scope that you didn't intend them to access.

The moment you use reflect like that, you greatly weaken your module's encapsulation and make it so the caller could access things you didn't intend them to access. For example, say you wanted to import a third driver, not because you wanted users of sendSqlCommand() to have access to it, but for some other purpose. How would you do so without automatically granting sendSqlCommand access to this third driver?

Code editors would also have a bad day with this. They like to gray out variables that are unused, but would be completely unable to do so the moment you use the reflect syntax - they have no way of telling if an unused variable is actually used via reflection or not.

Reflect should only have access to the inherited scope, so if you can instantiate you can reflect, if you can’t do one you can’t do the other. How would it happen, could you show me some example code?

Since JS has no actual classloader that can insulate contexts every insulation is just coincidental till someone does something stupid, but I don’t really see how reflect can aid in that.

I don’t understand why eval can’t be removed, I mean I used to use it 20-25+ years ago too (fun times), but nowadays it makes no sense. If you really need eval you either need webgl (webcl? if it’s still alive) or just modern js in general from anons, closures, promisses, proxies, reflection to modern apis etc… or reflect :slight_smile:

If we would have a true oop class model with classloader insulation subclasses etc and such Class.getClass(ā€œnameā€œ) would be completely natural, but as it stands reflect seems like the obvious extension to me.

Nothing that's widely used on the web can ever be removed, including eval. Whether it makes sense to still use it or not is irrelevant.

A "class loader" is also not an objectively good thing to want or have, and I think if you want one, you should build it in userland.

We all dislike eval, but JavaScript has to maintain eternal backwards compatibility. The Committee and the big engines will not make any changes to the language that suddenly break existing applications with real-life users.

There have been a few exceptions: if the Committee can reach out to the maintainers of every single application that a new feature would break, and it asks them to change their application, so that the application doesn’t break for existing users after the language change, then maybe the new feature can be added. That’s a lot of if’s.

Oftentimes this is not possible (look up ā€œSmooshgateā€ for an example). And use of eval is one of those cases. eval is still widespread in used applications. That ship has sailed; we can only try to make it gradually less powerful or common in backwards compatible ways.

All web standards, such and HTML and CSS, are like this. ā€œDon’t break the webā€. (With varying degrees. Some web standards do take into account whether a feature is used by practically nobody and then cautiously remove it. JavaScript is not one of them.)

We can sometimes formally deprecate features in web standards, and maybe eval should be one of them. but one of JavaScript’s goals is to maintain eternal backwards compatibility. Yes, this has trade-offs, but it’s a foundational stability principle that millions or billions depend on.

With that said, thank you for your enthusiasm and welcome to this forum. eval is an important topic.

1 Like

I don’t know why people are so keen to remove eval. Given the high tendency for JS language design to copy other languages, I would expect the lack of eval to be odd for JS since it’s in virtually every single interpreted language. PHP has it; Python has it; Bash has it; Ruby has it; R has it; and the list could go on. eval can be (rightfully) discouraged by style guides and security advisories, but the machinery being available is useful and natural.

import { MySqlDriver } from 'mysql-driver';
import { D3Driver } from 'd3-driver';
import { MockDriver } from './MockDriver'; // <-- I added this import

export function sendSqlCommand(driverName, command) {
  const Driver = reflect driverName;
  const myDriver = new Driver();
  ...
}

Because I added the import to this MockDriver class, now it becomes valid to call sendSqlCommand(ā€˜MockDriver’, …), as that would reference one of the local, instantiable variables in the module. Even if I didn’t intend to add ’MockDriver’ as one of the allowed strings for sendSqlCommand(), it automatically is, just because I added a new import to the module.

Unless I’m still mis-understanding how this works. (I’m not familiar with this Class.getClass(ā€œnameā€) function you keep speeking of in Java - and when I try to google it, all I come up with is instance.getClass(), which seems to be different from what you’re talking about - that just returns the class that created the instance - the equivalent in JavaScript would be instance.constructor).

True, Classloaders don’t make sense for a language that run singlethreaded and siloed like js, that is why you can also not have true isolation, basically unless you have security siloing like iframes do for instance nothing you load can be truly insulated.

In my experience if you have gotten to the point of I need eval… you already are many architectural mistakes in and yes at that point in time eval may be the only solution that doesn’t get you fired over a months long refactoring. And I agree in days past eval was not only nice to have, was necessary because we didn’t have any advanced tools, but in the land of modern js with proxies with reflection with type casting with promises on one hand and the webgl, xml/xsl and many other modern apis implemented in JS there is simply no NEED to use eval, you can still find a shortcut using it but usually evaluating your options and using ā€œnormalā€ JS is usually much readable safer and normally faster too. This applies to any mature language, eval is the silver bullet for new languages to say well if you can’t do it with what you have you can always use eval… it’s the ā€œothersā€œ section of the computing world.

Not breaking the web is such a bad policy, and if that would have ever been truly the case, why did we obsolete all the quirky stuff IE was doing? why was active-x tossed? with construct? It has nothing to do with actual programmers needs and maintainability and everything to do with big corp politics. Big corporations have their stacks built on ancient wobbly code and upgrading to modern times was always expensive! But in the days of AI? Today you could use various linters and static analysis tools to highlight the bs and then have ai propose fixes. I mean I wouldn’t want to rewrite a 250k code base I wrote over 20 years ago, sure, but I could probably rewrite it today with only 10k lines and with ai it would only take me the better part of a month and make it make sense and be 1000x more maintainable. What would be the incentive of me doing that without if everything is forever backwards compatible.

I mean, critical infrastructure already runs frozen builds on frozen deployments - those NEVER change whatever the committee will ever decide, and that’s fine! Websites on the other hand have been broken all the time! Especially in the transition from IE → chrome websites would stop working right all the time, and it was ā€œnormalā€œ.

What drives me nuts is that classes had the clear cut new start chance we were waiting for for decades, and what did we get? another halfbaked mess that we now have to ā€œforevermoreā€œ live with. At this point try explaining to juniors how the inheritance schema works in javascript without sound totally insane! And then you see coding guidelines like: never use ā€œthisā€œā€¦ Even hoisting, heck even the execution stack explanations make more sense.

So yeah, I get it, backwards compatibility is nice and all, but you have to set time limits, if a code isn’t getting updated it means nobody needs it! Because if someone needs it they would take up the maintenance and upgrade there of even if original maintainers and authors are no longer in the picture - or option b just go with another similar library, or roll your own. And if now the committee would say in 5 years eval will be completely taken out of the language and never supported again. Yes there would be initial uproar and people would panic for a while, but at the end we’d all sleep better at night.