Proposal: Symbol Linked to `typeof`

# Randy Buchholz (5 years ago)

(Hi all, new to group)

Some of the Well Known Symbols are linked to standard functions - Symbol.toStringTag - A string value used for the default description of an object. Used by Object.prototype.toString().

When I have a Class instance (say, const flow = new Flow(), the debugger shows flow = Flow {. But when I do console.log( typeof flow) the output is object.

I assume changing basic behavior typeof would be breaking, but extending it through a symbol would be useful.

Symbol.typeofTag of just Symbol.typeof

Invoking typeof on an object with this symbol would return "user-typed" information.

(Posting Question) What is the preferred format/approach for highlighting code in posts?

# Isiah Meadows (5 years ago)

Let's restrict custom typeof values to just symbols - objects are unnecessarily weighty, strings would break a lot of code, and other primitives are generally useless.

A property sounds compelling, and I feel like I saw it suggested a while back (forget where) something similar by a TC39 person.

# ViliusCreator (5 years ago)

I could definitely see usage of this when using custom Constructors. But this could confuse current code of JS:

function A(B, C) {
    if(typeof B === 'string') // ...
}
A({[Symbol.typeof]: 'string'})

This would make old code updated to use not only typeof B === 'string', but also B instanceof String to secure. Well, I don't see why someone would do this for fun, but I think that someone would still use that.

I would definitely would like this feature, but there are some downsides.

# J Decker (5 years ago)

On Sat, Jan 12, 2019 at 8:19 AM Randy Buchholz <work at randybuchholz.com>

wrote:

(Hi all, new to group)

Some of the Well Known Symbols are linked to standard functions – Symbol.toStringTag - A string value used for the default description of an object. Used by Object.prototype.toString().

When I have a Class instance (say, const flow = new Flow(), the debugger shows flow = Flow {.

But when I do console.log( typeof flow) the output is object.

I assume changing basic behavior typeof would be breaking, but extending it through a symbol would be useful.

Symbol.typeofTag of just Symbol.typeof

Invoking typeof on an object with this symbol would return “user-typed” information.

can't you just use the constructor name when needing more detail?

var a = new Date(); a.constructor.name "Date"

# Jordan Harband (5 years ago)

.constructor is unreliable, and can be easily forged - of course, Symbol.typeof would create the same unreliability with one of the few truly reliable operators in the language.

# Randy Buchholz (5 years ago)

My thought was Symbol.typeof could improve both reliability and flexibility. It could be abused, but no more than Symbol.toStringTag. My use case is putting classes into “namespaces”. With [Symbol.typeof] = “Collections.Lists.LinkedList”, I can get the qualified type of the object. I can also use this in my IDE to keep my classes organized if I write tools to help me. Since imports use paths, if I move a file refactoring can be painful. With an IDE extension, I can trigger an event when I move a file to prompt “Update all imports?”. It can parse my project and update all imports to point to the right place.

From: es-discuss <es-discuss-bounces at mozilla.org> On Behalf Of Jordan Harband

Sent: Tuesday, January 15, 2019 3:36 PM To: J Decker <d3ck0r at gmail.com>

Cc: es-discuss at mozilla.org Subject: Re: Proposal: Symbol Linked to typeof

.constructor is unreliable, and can be easily forged - of course, Symbol.typeof would create the same unreliability with one of the few truly reliable operators in the language.

On Tue, Jan 15, 2019 at 12:38 PM J Decker <d3ck0r at gmail.com<mailto:d3ck0r at gmail.com>> wrote:

On Sat, Jan 12, 2019 at 8:19 AM Randy Buchholz <work at randybuchholz.com<mailto:work at randybuchholz.com>> wrote: (Hi all, new to group)

Some of the Well Known Symbols are linked to standard functions – Symbol.toStringTag - A string value used for the default description of an object. Used by Object.prototype.toString().

When I have a Class instance (say, const flow = new Flow(), the debugger shows flow = Flow {. But when I do console.log( typeof flow) the output is object.

I assume changing basic behavior typeof would be breaking, but extending it through a symbol would be useful.

Symbol.typeofTag of just Symbol.typeof

Invoking typeof on an object with this symbol would return “user-typed” information.

can't you just use the constructor name when needing more detail?

var a = new Date(); a.constructor.namea.constructor.name "Date"

(Posting Question) What is the preferred format/approach for highlighting code in posts?

# Jordan Harband (5 years ago)

Symbol.toStringTag can be used for this purpose now (and it already unfortunately destroyed the reliability of Object.prototype.toString.call).

# Randy Buchholz (5 years ago)

Right. Misappropriating a symbol corrupts its intent. As ES moves in a more towards a more "class-ish" feel, and people start writing more classes, a type/namespace system isn't far behind. Implementing some symbols to support this helps drive some standard approaches and discourages symbol misappropriation.

# Augusto Moura (5 years ago)

I don't think string namespaced names are the right feature here

Namespaces are intended mainly to avoid conflicts with names when sharing a global scope (like when using classpath in Java, or libs in C++) Javascript already solves this problems with modules, you can always guard your code with instanceof and the right instance of your class, you don't need "qualified names"

// flow.js
export class Flow {
}
// main.js
import { Flow } from './flow.js'; // we don't need namespaces, a "file
path" already resolves conflicts for us

const doSomething = (obj)  => {
  if (obj instanceof Flow) {
    // ...
  }
}

You can also customize the instanceof operation with Symbol.hasInstance

class Flow {
  // Normally you would check the minimum contract required to any guard work
  // Probably bad idea to override in most of the cases
  static [Symbol.hasInstance](obj) {
    return '_flow' in obj;
  }
}

// Note that the control is inverse to your proposal the Class
determines if a value is instance (this why it's static)
// Without altering `Flow[Symbol.hasInstance]` just expected objects
will pass the check in `doSomething`
doSomething({}); // doesn't work
doSomething({ __proto__: Flow.prototype }); // doesn't work
doSomething({ _flow: undefined }); // actually works

~string qualified names are so 20th century~

# Jordan Harband (5 years ago)
# Randy Buchholz (5 years ago)

@Augusto Moura, I like the approach using hasInstance.

It may be just me, but having your software architecture hardcoded to your filesystem (even with logical roots) feels shaky. (and so 1970’s 😊). Namespaces serve the purpose you stated at compile/runtime, but also serve as a form of human readable GUID for classes at design time, and allow creating a logical hierarchy independent of storage. This could be helpful for things like “object servers”/DI approaches. If I expose a service to deliver ES Classes, I don’t want users to know or depend on how I store them. ES is awesomely dynamic in every way except the hardcoded paths. Really, underlying the proposal is wanting to uniquely identify my classes with a token. As much for design-time as run-time. Currently, I expose a static GUID in all of my classes.

# Augusto Moura (5 years ago)

The module system is not filesystem based, but URL based, that's a lot of differences between the two, URLs are designed to be unique and be storage/protocol agnostic in most networks. Others languages are following similar paths, Go for example even allows you to import entire github projects directly in source code, Deno (a experimental Node-like platform, made by the Node former creator) follows the same direction. In a distributed world, URLs work really good as truly Global Unique Identifiers

In my opinion there's no better way to uniquely identifying than comparing it with the actual reference. But if you want a qualified name so badly, the only way I know is writing them manually, maybe you could use some external tool to automate this step, something like jscodeshift or a babel plugin. You could use decorators for runtime injection metadata (probably a parameterized namespace name)


const rootNamespace = 'com.foo'

const qualifiedName = (...namespaces) => (clazz) => {
  const namespacesJoined = namespaces.join('.');
  clazz.qualifiedName = rootNamespace + (namespacesJoined ? '.' + namespacesJoined : '') + '.' + clazz.name;
};

@qualifiedName('domain', 'package')
class Foo {
}

console.assert(Foo.qualifiedName === 'com.foo.domain.package.Foo')
# Randy Buchholz (5 years ago)

Your Absolutely right! My mistake.