Exporting Symbols
The main issue is that modules are statically analysable and imports/exports are processed before the module executes, and the behavior of a symbol is by definition a runtime thing. Until the code executes, there would be no symbol object, and there would be no way to know what thing was being imported imported / exported.
The primary benefit of symbols is that they are unique, and cannot collide unless you have a reference to them. Module exports have full control over their names and don't have the same collision issues that objects and classes generally do. What is the benefit you are hoping to get from exposing these values on symbols instead of string keys?
There are special features we want to expose if a module defines a certain key. However, we’d like to not rely on certain names just because there’s some chance they came up with it themselves. If it was a symbol that we gave them access to, it would be impossible for this to be the case, they would have had to grab the symbol from us:
var SpecialSymbol = require(“our-symbols”).SpecialSymbol;
export { whatever as SpecialSymbol }
The difficulty I’m having right now is being torn by two competing “best practices”: on the one hand, you can’t do what I did above for the runtime reasons, on the other hand you CAN actually pull it off using the export default strategy, and in that situation it technically is safer for us to use our special symbol instead of relying on a string match.
Wouldn’t this work?
our-symbols.js:
export const SpecialSymbol = Symbol("SpecialSymbol");
their-consumer.js
import { SpecialSymbol } from "our-symbols";
export const features = {
[SpecialSymbol]: true
};
our-features.js
import { SpecialSymbol } from "our-symbols";
import { features } from "their-consumer";
if (features[SpecialSymbol]) {
// …
}
This uses an export named “features”, though in practice it’s pretty much the same as using “export default”. While the “features” identifier could theoretically be some other “features” with a different meaning, you can still detect the presence of your special symbol.
Ron
From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Francisco Tolmasky Sent: Thursday, October 15, 2015 10:47 AM To: Logan Smyth <loganfsmyth at gmail.com>
Cc: es-discuss at mozilla.org Subject: Re: Exporting Symbols
There are special features we want to expose if a module defines a certain key. However, we’d like to not rely on certain names just because there’s some chance they came up with it themselves. If it was a symbol that we gave them access to, it would be impossible for this to be the case, they would have had to grab the symbol from us:
var SpecialSymbol = require(“our-symbols”).SpecialSymbol;
export { whatever as SpecialSymbol }
The difficulty I’m having right now is being torn by two competing “best practices”: on the one hand, you can’t do what I did above for the runtime reasons, on the other hand you CAN actually pull it off using the export default strategy, and in that situation it technically is safer for us to use our special symbol instead of relying on a string match.
On Thu, Oct 15, 2015 at 8:10 AM, Logan Smyth <loganfsmyth at gmail.com<mailto:loganfsmyth at gmail.com>> wrote:
The main issue is that modules are statically analysable and imports/exports are processed before the module executes, and the behavior of a symbol is by definition a runtime thing. Until the code executes, there would be no symbol object, and there would be no way to know what thing was being imported imported / exported.
The primary benefit of symbols is that they are unique, and cannot collide unless you have a reference to them. Module exports have full control over their names and don't have the same collision issues that objects and classes generally do. What is the benefit you are hoping to get from exposing these values on symbols instead of string keys?
On Thu, Oct 15, 2015 at 12:14 AM, Francisco Tolmasky <tolmasky at gmail.com<mailto:tolmasky at gmail.com>> wrote:
Not sure if it isn’t the case, but it seems the only way to export symbols right now is:
export default { [Symbol(“something”)]: blah }
It would be nice if “symbol literals” could somehow be supported. In particular, I can imagine this sort of thing becoming common:
export { “5.4” as Symbol(“version”) }
Or, even better (but harder since its now more expression):
import VersionSymbol from “symbols" export { “5.4” as VersionSymbol }
I’m currently writing an API where there are expected methods stored in symbols, but this forces exporting one default object instead of being able to lay them out individual.
Thanks,
Francisco
-- Francisco Tolmasky www.tolmasky.comna01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwww.tolmasky.com&data=01|01|ron.buckton%40microsoft.com|2e02643e0d944134b1f708d2d588b34b|72f988bf86f141af91ab2d7cd011db47|1&sdata=2IQQvQqHc7DJeuLKj9fgCknBXv7p56Vct6%2BSNKqL2N8%3D
Unless of course you needed to export a "features" AND support this magic symbol.
Symbol has built-in API for making Symbols available across all code realms:
www.ecma-international.org/ecma-262/6.0/#sec-symbol.for, www.ecma-international.org/ecma-262/6.0/#sec-symbol.keyfor
You can create a generated key and export it, giving your consumers only the key, which they can then use to get the symbol.
// In module.js let key = Math.trunc((Math.random() * 0xffffffff)); let sym = Symbol.for(key); // use sym throughout the module code export { key as features }
// In program.js import { features } from "module";
console.log(features, Symbol.for(features));
The math.trunc part is completely irrelevant, leftover from fiddling around—sorry for the noise!
The symbol registry is a great way to map universal (not just global, but
fully cross-realm) names (strings) to unique, distinct concepts (symbols).
But as a flat string namespace has all the same kinds of issues symbols
were introduced to solve. IIUC throwing Math.random
strings into the
symbol registry is pretty much equivalent to writing (non-enumerable)
Math.random object properties on the global object, right? Doesn't this
eliminate any of the cross-realm dereferencing advantages provided by the
symbol registry?
I suspect we'll see the symbol registry used with something like xml's
namespace uris to address this -- fully qualified, canonical uris as a
means of unambiguously resolving the symbol for some fully-cross-realm
"concept", which might look something like Symbol.for(' http://example.com/v5.4')
example.com/v5.4').
This mitigates collisions naturally, and is reasonably legible -- though
I'm devs won't want to pepper these xmlns monstrosities throughout their
application code. ISTM library authors will end up providing something like
the require(“our-symbols”).SpecialSymbol
module Fransisco suggested,
where string binding names, sensible in the context of some library, are
associated with these global fully-qualified registered symbols.
This might look export let v5_4 = Symbol.for('http://example.com/v5.4') }
. The url can be whatever you want (and like xmlns uris, need not be
resolvable). There are certainly better uri schemes for this that may be
more sensible -- you can use whatever you want, so long as you do it
consistently. This essentially transform the module import path namespace
into a kind of thesaurus to map strings which make sense in the context of
some module to some universal concept -- which could offer some pretty nice
wins when writing generic library code.
On Fri, Oct 16, 2015 at 2:47 PM Dean Landolt <dean at deanlandolt.com> wrote:
The symbol registry is a great way to map universal (not just global, but fully cross-realm) names (strings) to unique, distinct concepts (symbols). But as a flat string namespace has all the same kinds of issues symbols were introduced to solve. IIUC throwing
Math.random
strings into the symbol registry is pretty much equivalent to writing (non-enumerable) Math.random object properties on the global object, right?
Global object is not cross-realm. Symbol registry is cross realm, try this
var iframe = document.createElement("iframe"); document.body.appendChild(iframe); iframe.contentWindow.Symbol.iterator === Symbol.iterator; // true, this is in the Symbol registry. iframe.contentWindow.Symbol === Symbol; // false iframe.contentWindow.Array === Array; // false
Also, the OP is actually asking to give away the Symbol key:
"There are special features we want to expose if a module defines a certain key. However, we’d like to not rely on certain names just because there’s some chance they came up with it themselves. If it was a symbol that we gave them access to, it would be impossible for this to be the case, they would have had to grab the symbol from us:
var SpecialSymbol = require(“our-symbols”).SpecialSymbol;
export { whatever as SpecialSymbol }"
My example wasn't intended to show Francisco what I think he should do, just to answer his question based on his expressed parameters. If it were me...
// module.js let sym = Symbol(); // ...use my sym for special features... export { sym as feature }
Seems sufficient? Maybe I've misunderstood the constraints.
On Fri, Oct 16, 2015 at 3:46 PM, Rick Waldron <waldron.rick at gmail.com>
wrote:
On Fri, Oct 16, 2015 at 2:47 PM Dean Landolt <dean at deanlandolt.com> wrote:
The symbol registry is a great way to map universal (not just global, but fully cross-realm) names (strings) to unique, distinct concepts (symbols). But as a flat string namespace has all the same kinds of issues symbols were introduced to solve. IIUC throwing
Math.random
strings into the symbol registry is pretty much equivalent to writing (non-enumerable) Math.random object properties on the global object, right?Global object is not cross-realm. Symbol registry is cross realm, try this
var iframe = document.createElement("iframe"); document.body.appendChild(iframe); iframe.contentWindow.Symbol.iterator === Symbol.iterator; // true, this is in the Symbol registry. iframe.contentWindow.Symbol === Symbol; // false iframe.contentWindow.Array === Array; // false
I know -- I said that in the first line of my message :)
But if you register a random value as the key of a symbol, how would a consumer resolve that key? They would have to get a reference to the module, right? How would two different consumers, in two different realms, resolve the same key? AFAIK they can't, at least not without additional coordination. AFAIK this symbol would only be useful to users within a single realm, which is why I suggested it was equivalent to tucking it on a global somewhere.
Also, the OP is actually asking to give away the Symbol key:
"There are special features we want to expose if a module defines a certain key. However, we’d like to not rely on certain names just because there’s some chance they came up with it themselves. If it was a symbol that we gave them access to, it would be impossible for this to be the case, they would have had to grab the symbol from us:
var SpecialSymbol = require(“our-symbols”).SpecialSymbol;
export { whatever as SpecialSymbol }"
My example wasn't intended to show Francisco what I think he should do, just to answer his question based on his expressed parameters.
Fair enough -- I wasn't trying to call out your answer specifically -- I was just trying to point out the benefits using the symbol registry for truly cross-realm coordination, and trying to expand a bit on what that can look like.
Francisco's examples, from what I could tell at least, can't really be made to work as is, even if you could export symbol bindings. The symbols being exported are generated within the module itself, so without associating them with some known semantics (one way or another), they're completely opaque to downstream consumers. When default-exporting an object w/ symbol keys, you can get a handle on all these symbols through reflection, but they're still inherently indistinguishable from each other, and meaningless in general. (Unless you switch on their string description -- but that's madness!)
The solution you proposed does accomplish what he seems to be after -- but I suppose I neglected to elaborate on why I believe what he's chasing is fundamentally flawed...
If it were me...
// module.js let sym = Symbol(); // ...use my sym for special features... export { sym as feature }
Seems sufficient? Maybe I've misunderstood the constraints.
IIUC he wants to export multiple symbols from a given module, without having to conjure up application-specific strings for each. But as soon as you introduce objects with opaque identity (what a philosopher might call "intensional objects") -- whether through Symbols, UUIDs, random values, whatever -- you have to come up with some mechanism to communicate their semantics. Otherwise they're completely useless, for identity purposes at least. Unless they fail at being opaque -- but that's always a bug...
For example, Symbols themselves are beautifully opaque -- but if you try to use the Symbol description string as a means to communicate semantics on a symbol instance you break this property. Don't do that. It may seem as though something like a UUIDv1, which exposes process identity and some information about the processor's wallclock, isn't fully opaque. This information can be used to partially order the creation date of UUIDs by the process which generated them, but this says absolutely nothing about what any of these UUIDs actually represent. From an identity standpoint, they're still completely opaque.
So if you export an opaque identity that you created yourself, and don't expose some mechanism for downstream consumers to know what it means, you're handing a bag of meaningless tokens which are, by definition, completely useless to them.
This problem of assigning semantics is neatly solved by the symbol registry -- and IMHO can be solved properly by using something like fully qualified uris as keys in the symbol registry. Any global namespace will do -- DNS, namecoin, any PKI infrastructure, etc., so long as it has a uri scheme you naturally get a string-prefixed subspace of the symbol registry you can do whatever the hell you want with.
To export these symbols directly from his library, Fransisco would still have to associate them with string keys of his choosing -- which seems like the core problem he was hoping to solve with symbols. I'm arguing that unless you're the only one using a symbol you create, you have to conjure up some mechanism of communicating its semantics to other users -- symbols can't solve this problem alone.
All that said, I can see the benefit to being able to export symbol bindings, which was the main point of his message -- at least registered symbols. Perhaps we just need to grow some syntax for creating registered symbols which is statically analyzable to make this feasible?
[snip]
So if you export an opaque identity that you created yourself, and don't expose some mechanism for downstream consumers to know what it means, you're handing a bag of meaningless tokens which are, by definition, completely useless to them.
I should add: if you only intend to default-export a single symbol, this is not semantically meaningless. It may be possible to infer some kind of meaning from the module path of the import. I'm not convinced this is valuable, but it seems like something which could be made to work.
My point only holds when not doing a default-export of a symbol -- e.g. using symbol keys on a default-exported object or using unregistered symbols as export bindings (if this were even possible).
May I ask what the use case for exporting symbol-named values is? I can't seem to think of any obvious one.
Not sure if it isn’t the case, but it seems the only way to export symbols right now is:
export default { [Symbol(“something”)]: blah }
It would be nice if “symbol literals” could somehow be supported. In particular, I can imagine this sort of thing becoming common:
export { “5.4” as Symbol(“version”) }
Or, even better (but harder since its now more expression):
import VersionSymbol from “symbols" export { “5.4” as VersionSymbol }
I’m currently writing an API where there are expected methods stored in symbols, but this forces exporting one default object instead of being able to lay them out individual.
Thanks,
Francisco