-
Notifications
You must be signed in to change notification settings - Fork 5
Description
There are similar concepts in other languages, often called a "Symbol" or "Keyword".
Related Implementations
JavaScript
JavaScript has Symbol. It's distinct from the reference implementation in the following ways:
- Each call to
Symbol('foo')creates a new symbol (similar to Python'sobject()). Symbolhas two static methods:Symbol.for(key)always returns the same Symbol from a global registry (global registry is not public)Symbol.keyFor(symbol)always returns the registry key string ('foo') for a given symbol.
- There is no module-scoped registry. Module scoping is implicit as variable declaration.
- A call to
Symbol.for('foo')in two different modules produces identical values.
- A call to
JS' Symbols achieve module scope by simply declaring them once. They can be exported and imported elsewhere to effectively achieve module scopes without explicit namespaces.
// constants.js
export const MISSING = Symbol('missing')
// another module
import { MISSING } from 'constants';Two or more modules can obtain identical symbols without depending on each other by using Symbol.for(key) with the same key, which provides access to the global registry.
// red.js
export const MISSING = Symbol.for('missing');
// blue.js
import { MISSING } from 'red';
MISSING === Symbol.for('missing')Surprisingly, Symbol('foo') !== Symbol('foo'), since each call to Symbol creates a new instance. This guarantees the global uniqueness of symbols and guards against accidental name collisions, but it is surprising. Thankfully, Symbol.for('foo') === Symbol.for('foo').
Clojure
Clojure has two concepts related to the Sentinel proposal: keyword and symbol. Conventionally in Clojure, keywords are used for sentinel values, though symbols can be used almost identically. Keywords have a literal syntax that makes them especially convenient. For a keyword "foo", the literal is :foo. Keywords can also be namespaced explicitly,:hello.world/foo, or implicitly in the current namespace, ::foo. There is also a functional notation: (keyword name) or (keyword ns name).
They differ from the reference implementation in that:
- Each call to
keywordwith the same namespace and name returns the same (interned) object. - There is no module-scoped registry. Module scoping is implicit as variable declaration.
- A call to
(keyword "foo")in two different modules produces identical values.
- A call to
Conclusion
Comparatively, the JS and Clojure implementations differ from the proposal in that neither has a module-scoped registry. Instead, they rely on (a) a global registry and (b) the scope of variables for module-scoped behavior.
Namespaces are a great idea and we don't do well enough by them in Python. (You can't switch namespaces in the Python REPL, for example.) But, I think we already have a module-scoped registry in Python that everyone's familiar with. It's called a module.
So, how about the following?
Sentinel(name)always produces an identical value, analogous to the typical behavior ofstr(name).UniqueSentinel(name)always produces a new unique value, similar toobject()but with a more readable repr. (Alternative option:Sentinel.unique(name).)- For module-scoped (namespaced) values, import a UniqueSentinel from a module:
from my_module import MISSING - Optionally, provide get access to a global registry of UniqueSentinel, such as
UniqueSentinel.get(module, name)- e.g.,
UniqueSentinel.get("mypackage.mymodule", "MISSING")
- e.g.,
I think the trivial nature of this version Sentinel means that it's perhaps a very good candidate for the typing module. Without UniqueSentinel.get, I think UniqueSentinel could also belong in typing. This seems to cover every use case I'm aware of.
I don't know that the global registry implied by UniqueSentinel.get is really necessary. If it were, that might be motivation to put both in a distinct module. Having a distinct Sentinel and UniqueSentinel should obviate the need for a registry to re-use sentinels without importing.
As an aside, I personally prefer the name Symbol, but I recognize it's an overloaded term and might imply a lot more features than are being proposed.