tc39 / proposal-source-phase-imports

Proposal to enable importing modules at the source phase

Home Page:https://tc39.es/proposal-source-phase-imports/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Bikeshedding the `source` keyword

lucacasonato opened this issue · comments

During the 97th TC39 meeting a discussion about bikeshedding the source keyword came up.

@syg has brought up that some folks were confused by the source keyword, expecting it to return unparsed, uncompiled source code.

This keyword represents the second phase of the five stage loading process:
image

It represents early exiting from the module loader right after the module has been fetched, parsed, (and compiled)

Some alternative keywords that have been suggested:

  • instantiable
  • handle
  • parsed
  • compiled

You can vote here: #54

Please keep discussion in this issue.

This issue will be collecting feedback on alternative names until 21 Jul 2023 at 00:00 UTC. If no consensus on an alternative name is reached by then, the proposal goes to Stage 3 with the name source.

I can live with parsed or instantiable, but given the general negative feedback to instantiable, parsed seems promising.

My stance:

  • instantiable: too long and unwieldy. It's not a widely used word and likely to be mistyped frequently, especially by non native speakers.
  • handle: too generic. A handle could be an asset reflection, a handle on an instantiated module, or anything really.
  • parsed: easy to type and remember 👍 - but I'm not confident this will be a much better name for folks outside of compiler circles however, because they don't care about how modules are fetched and parsed. Usually the result of parsing is an AST: will users be confused that this does not return an AST (Reflect.parse anyone?)
  • compiled: also easy to type and remember 👍 - more correct, because at least in the Wasm case you are parsing + compiling to get a WebAssembly.Module. Probably this is exposing too much info about internals though, and will be confusing outside compiler circles. This at least clarifies that the result is not an AST, but a compiled program.

TBH, not super convinced by any of these so far.

What about having one comment per option, that people can 👍 / 👎 ?

I don't think this is bike-shedding; misnomers have real world dev-time cost and as a teacher I watch people trip over confusing JavaScript edgecases every day. It doesn't have to be perfect, but theres no going back and JavaScript isn't as simple as when you and I learned it. Good enough is good enough, but these current options are pretty bad.

Just as a sanity check we should ask ourselves if there is a "not" of a modifying keyword

  • const implies there's a non-const (mutable)
  • async implies the default is synchronous
  • import parsed ... is the default import NOT parsed? no? Is there some other kind of import that isn't parsed? No? What???
  • import source. Is there some kind of import-but-without-the-source? Does the default import not import the source? What does that even mean?
  • import instantiable is marginally better (implying the default is non-instantiable), but we mostly agree, as a keyword, it is too unwieldy

Just as a viewpoint/context, we should sketch names for each stage; because each name should answer "how am I different from the other stages?"

import.resolve // verb (only resolving, not fetching) and its already named
import bytes // fetched, but nothing more
import string // decoded, but not more
import ast // I would assume parsed but nothing more
import detached // compiled but no attached context
import unlinked // attached 
import unevaled // linked

Something like import detached passes the negation test (e.g. "so normal imports are attached?" -> yes/obviously)

So, please consider the options below:

I'm not going to champion one, as I just want any good-enough solution and I think the 4 options above don't make the cut.

  1. import detached <thing> from "source"
  2. compile <thing> from "source" Similar to import.resolve; the verb implies it's "merely" compiled and nothing more
  3. import as detached <thing> from "source"
  4. import.compile <thing> from "source"
  5. `import binding from "source"
  6. `import partial from "source"
  7. `import skeleton from "source"
  8. Some other alternative!

Is there some kind of import-but-without-the-source? Does the default import not import the source? What does that even mean?

import source gives you a representation of the source of the imported module. The default behavior does not do that, it gives you the result of evaluating the module. The modifier here is not describing the "internal process", but it's describing the result value.

I might suggest: syntax, coming from the Racket programming language. For Racket, syntax data is typically used in macros, but in a wider sense, syntax refers to a rich representation (it includes the source location, such as a URL or file path, along with line and column information) of not-yet-evaluated data, including whole modules.

Is syntax an abstract syntax tree in racket?

@nicolo-ribaudo

the source of the imported module

I think the issue is it doesn't diffentiate from the other steps. E.g. importing as a string (the source code) also qualifies as "representation of the source of a module", and importing the AST also qualifies as "a represention of the source of a module". It's like import code or import thing those aren't false statements, they're just not distinct enough to mean anything to someone who doesn't pre-know what it's suppose to mean.

I think source also fails the synonym test:

Swapping "const" with a synonym like "frozen" or "fixed" still would make sense.

Swapping "source" with something like "origin" or "root" doesn't make sense (e.g. import origin { thing } from "somthing" ?)

To me “instantiable” seems like the only term so far that directly describes what makes it different, but I can understand why it’s unpopular. Are there other terms that could convey this without resorting to words that are misleadingly over-specific?

(@rbuckton’s “abstract” suggestion is a good example of one that’s not over-specific, though it might suffer from the opposite issue. Personally I think under-specific is better than over-specific, though, since under-specific at least doesn’t mislead.)

“abstract” suggestion

to +1 on that; googling JavaScript import abstract (or many of the other terms) will likely return relevant results in the future.

Googling JavaScript import source is going to return all kinds of source-map results and irrelevant threads.

It's worth noting here that most JavaScript code will not use import source x from './x.js'.

Rather, the code that does this will be deep in npm libraries that use WebAssembly, that code itself being JS wrapper code generated by WebAssembly tooling.

For JS virtualization users when we support it for JS (as the name intendeds), this will be used for mocking and the construction of virtual sandboxes. Eg, creating a shadow realm, then using a source import to pass the source into that realm and execute it there. In these workflows I think the term source makes complete sense to the developer (an experienced JS developer knowingly doing virtualization work). So I don't think they would be confused by it.

In the rare case where a novice developer sees this it would either be: a) browsing library code (in which case, yes I agree it would likely be confusing until they full grok how it is being used by following the code paths) or b) a Wasm library that instructs usage via the import source mechanism, in which case it would be a blind copy-and-paste.

Semantically we have put a lot of thought to the terminology in use, so there's fair good reason for the usage. So if this is just about learning perhaps the argument is that the novice user simply doesn't need to be concerned about it?

From the Module Harmony call today, we found our way to descriptor as an option that we collectively neither love nor hate, but it does avoid some problems we acknowledge with source and many of its alternatives. One potential guiding principle for naming this phase would be:

Each phase should describe itself and not its relationship to a neighboring phase.

The object in question is a handle, potentially opaque, that describes the bindings and behavior the module system will need to link and execute the module in a later-specified context. The handle does not even necessarily retail the source text, since the behavior depends only on the resulting object code. There handle may not even retain code, since a virtualization would model the behavior component as a mere function. Compilation may not even be necessary.

This framing discredits source, prelink, unlink, preinstantiated, instantiable, &c. But the verb describe suggests “descriptor”. This would look like:

import descriptor md from 'module';
await import.descriptor('module');

const m = new Module(md);
// or
const m = module {
  import foo from 'bar';
};

// Can be extracted from a module instance
const { descriptor } = m;

// May reflect bindings and behavior
descriptor.bindings;
  // could look like:
  // [{ import: 'foo', from: 'bar' }]
descriptor.behavior;
  // could look like:
  // function (environment, meta, dynamicImport) {}

// Where available and relevant, could conceivably
// expose source code (this is not proposed, but would
// avoid a language wart like source.source.
descriptor.source;
  // could look like:
  // "import foo from 'bar';\n"

// Module system still needs to be expressly entered
// through dynamic import, and only language internals
// can create namespaces, environments (the module’s internal
// namespace), and meta objects.
await import(m);

I am actively bored reading the word "descriptor". Truly, I do not hate it. (Maybe this is actually high praise?)

PSA My understanding is that we’re coming close to the deadline for the champions to make a decision of whether to run with source or an alternative. Please review your votes before the deadline lapses. It doesn’t look to me like momentum has gathered around an alternative to source and I’d like to at least give descriptor a fair shake. #54

Is syntax an abstract syntax tree in racket?

Yes, in general it is. It could be a simple string that needs to be parsed later on, but typically it contains an AST.

Thanks everyone for the feedback and discussion. We have unfortunately not found a compelling alternative that has wider support than source, as you can see in #54. As such, we'll be sticking with the import source syntax.