Live/Dynamic Bindings — Small Steps (draft)

#1

Hi everyone:

A little while ago @bmeck showed a slide which I found a little confusing at the time, but I upon reflection, makes me think of a potential way to take a stab at smaller inefficiencies towards create first-class parity with how ECMAScript Module namespace bindings work.

I am not able to find his example myself so I will rely on my memory. (sorry if I am inaccurate - please correct me)

private foo;

class UsesFoo {
  private [foo]; // I am sure I am messing this up 🤔 
}

At that moment, some of us (at least myself) were a little confused with referencing [foo] as if it were a computed field — as the semantics of various similar subtly different syntaxes that already exist today often imply that […] is a field for which the name is computed from the enclosed expression.

But elsewhere in the modules world I have a very little problem, and I tried many a hammer and they proved too blunt for job. Here we have a very opinionated system with a new superpower it is keeping to itself, and maybe @bmeck is pointing us in a direction where we can share that superpower to solve one side of this problem.

Let me try to elaborate

caution: this is not a good ocap model but is valid ES

Consider the following:

// exporter
export const C = 1;
export let x;
export {x as y}

// importer
import {x} from 'exporter';

This syntax creates a namespace object with a one-way binding against a scoped variable x in exporter and creates a one-way bound variable (also x but a different x from the original x nonetheless) in scope of importer.

So forget about the fact that ECMAScript Modules today are too closed up that the only way you can create a namespace object at runtime is by importing a source text module that spells out the exact statements shown, and suppose we want to take small steps towards creating namespaces. Replicating the bindings that take place is a hefty chore and is somewhat crude for a language to devise such complex operations and keep the end-users of the language at arms length — actually rude too.

How does this sound:

let x;
const C = 1;
module.exports = { 
  [C], // unaliased one-way constant binding
  [x], // unaliased one-way binding
  [x as y], // aliased one-way binding
}; 

Today, the very lengthy code to actually create those different bindings looks like this:

let x;
const C = 1;
module.exports = { 
  get C() { return C; },
  get x() { return x; },
  get y() { return x; },
}; 

When you optimize it using magic methods, it could look like this:

declare var exports: (ƒ: BindingFunction) => void; // scoped helper method
let x;
const C = 1;
exports(
  () => C,  // infers C from ƒ.toString()
  () => x, // infers x from ƒ.toString()
  y => x, // infers y from ƒ.toString()
);

But all this is inferior to optimize at runtime because unlike the actual runtime itself which has full access to the scope from which the bindings are created, magic code only sees getters — plain and simple — no contextual awareness of the state of the scope variables being referenced. It was a perk in this one instance that magic code needed no contextual awareness to retain the semantics needed, but that was about the only nice thing in this kind of binding design.

Why this particular solution and possibly all others are suboptimal is because they all will likely create opportunities for unexpected side-effects which can be misappropriated or else incur expensive costs for blocking them that often lead to unexpected limitations (ie caveats not reasonably communicable to those not familiar with the mojo) and in order to understand them, likely users will become qualified to stick some side-effects where you did not expect — because side-effects are easier solutions to immediate problems that are a little more complicated than time often permits for fixing production problems.

So, to summarize, if with (createBindings([module.exports, ['a', 'b', 'c']])) { … } is like an accidental and really bad import, dynamic bindings are in contrast an intentional and onpar export. And a next step would naturally be to create a good import {} from <thing> for first-class binding against a certain yet-to-be-made-clear thing.

Working Draft: https://smotaal.io/experimental/modules/bindings/