Object id, hash, etc?

# Michael McGlothlin (9 years ago)

Is there a reason not to provide an object id and hash value as other languages often do? I find myself defining an identity symbol on objects with a value from a simple global counter. It makes it easier to debug if I can just look at a log and see what objects and functions were active.

Likewise a hash value would simplify a lot of comparisons and make it easier to debug.

I especially find the ID useful when working with bound functions as they can be difficult to tell apart. I like to change toString() to provide the ID followed by the code (flattened to one line).

Thanks, Michael McGlothlin Sent from my iPhone

# Gary Guo (9 years ago)

Changing toString() is obviously not possible since the semantics are fixed and many existing code depends them (ex. Object.prototype.toString are used for type identifying, String.prototype.toString are used for meta-programming frameworks. I think you can extend Object.prototype to provide a hashing method if you really need them for debugging. Personally I think it is not usually needed.

# Garrett Smith (9 years ago)

On 9/8/15, Michael McGlothlin <mike.mcglothlin at gmail.com> wrote:

Is there a reason not to provide an object id and hash value as other languages often do? I find myself defining an identity symbol on objects with a value from a simple global counter. It makes it easier to debug if I can just look at a log and see what objects and functions were active.

Likewise a hash value would simplify a lot of comparisons and make it easier to debug.

I especially find the ID useful when working with bound functions as they can be difficult to tell apart. I like to change toString() to provide the ID followed by the code (flattened to one line).

NFE's are safe to use where IE8 support isn't needed*. (function aa(){}).name; // "aa"

As for using Object IDs and Object Pooling, lexically-scoped values have benefits over global ID generators (as your counter). The values can be passed in as a parameter to the Factory (useful with Private Proxy). There other benefits to this pattern regarding memory management and application design. Contact me if you'd like to learn more.

# joe (9 years ago)

Didn't send to list, something is wrong with my reply all. Sorry about that. Stupid mobile gmail. ---------- Forwarded message ---------- From: "joe" <joeedh at gmail.com>

Date: Sep 8, 2015 11:15 AM Subject: Re: Object id, hash, etc? To: "Garrett Smith" <dhtmlkitchen at gmail.com>

Cc:

I agree with this request. This is the logical complement to valueof, I think. And yes, for most ID use cases this isn't a good fit, but we're not talking about the general case, just the cases where a python style id() function is appropriate.

Joe

# Mark S. Miller (9 years ago)
# Michael McGlothlin (9 years ago)

I try to keep it pretty simple. It's not fancy but the times you want fast and dirty information like this are the same times you don't want to have to define it manually.

Symbol.identity = Symbol( 'Symbol.identity' ); const identity = Symbol( 'identity' ); var OBJECT_ID = 0; Object.defineProperty( Object.prototype, Symbol.identity, { get: () => { if ( !Object.hasOwnProperty.call( this, identity ) ) { this[ identity ] = ++OBJECT_ID; } return this[ identity ]; } } );

# Mark S. Miller (9 years ago)

On Tue, Sep 8, 2015 at 1:57 PM, Michael McGlothlin < mike.mcglothlin at gmail.com> wrote:

I try to keep it pretty simple. It's not fancy but the times you want fast and dirty information like this are the same times you don't want to have to define it manually.

Symbol.identity = Symbol( 'Symbol.identity' ); const identity = Symbol( 'identity' ); var OBJECT_ID = 0; Object.defineProperty( Object.prototype, Symbol.identity, { get: () => { if ( !Object.hasOwnProperty.call( this, identity ) ) { this[ identity ] = ++OBJECT_ID;

Does not work on frozen objects.

# Tab Atkins Jr. (9 years ago)

On Tue, Sep 8, 2015 at 2:07 PM, Mark S. Miller <erights at google.com> wrote:

On Tue, Sep 8, 2015 at 1:57 PM, Michael McGlothlin <mike.mcglothlin at gmail.com> wrote:

I try to keep it pretty simple. It's not fancy but the times you want fast and dirty information like this are the same times you don't want to have to define it manually.

Symbol.identity = Symbol( 'Symbol.identity' ); const identity = Symbol( 'identity' ); var OBJECT_ID = 0; Object.defineProperty( Object.prototype, Symbol.identity, { get: () => { if ( !Object.hasOwnProperty.call( this, identity ) ) { this[ identity ] = ++OBJECT_ID;

Does not work on frozen objects.

Of course, it's trivial to switch it to WeakMap'ing the key.

# Mark S. Miller (9 years ago)

then you get the Labeler, which this message is responding to.

# Andrea Giammarchi (9 years ago)

as side note, that's just a lazy assignment that doesn't need two symbols and a constant get invoke ...

Symbol.identity = Symbol( 'Symbol.identity' );
var OBJECT_ID = 0;
Object.defineProperty(
  Object.prototype,
  Symbol.identity,
  {
    get: function () {
      // first time this is invoked ... and no more ...
      Object.defineProperty(this, Symbol.identity, {value: ++OBJECT_ID});
      // from now own, direct property access \m/
      return this[Symbol.identity];
    }
  }
);
# Michał Wadas (9 years ago)

Following solution will work:

(function(){ const wm = new WeakMap(); Symbol.identity = Symbol( 'Symbol.identity' ); let i = 0; Object.defineProperty( Object.prototype, Symbol.identity, { get: function () { if (wm.has(this)) return wm.get(this); wm.set(this, Symbol(i++)); return wm.get(this); } } ); })()

2015-09-08 23:29 GMT+02:00 Andrea Giammarchi <andrea.giammarchi at gmail.com>:

# Andrea Giammarchi (9 years ago)

That's similar to the Labeler proposed by Mark, except it needs to search for the thing twice (wm.has + wm.get instead of just wm.get which would be just fine as check if you count from 1 instead of 0 ;-) )

Although I need to understand if you have a wm why wouldn't you just use that instead of a Symbol ... so the Labeler seems again a more appropriate pattern for this problem.

Just my thoughts

# Michael McGlothlin (9 years ago)

Using frozen objects and such seems a lot more complex to me than just using a property. Using a symbol to hide the property from cluttering seems to go along with how other things are being done. And it's my understanding that other than hiding it that using a Symbol for a name is nothing special - does it have some negative factor that should make using it less attractive?

📱 Michael McGlothlin

# Andrea Giammarchi (9 years ago)

Well, to start with, you don't hide symbols, these are all exposed through Object.getOwnPropertySymbols or Reflect.ownKeys so for your use case looks like using just a non enumerable property would do as well. And btw, even if Symbols don't show up in for/in and Object.keys, these are by default also enumerable so Object.assign will copy them around (reason I've used defineProperty with the value instead of just setting the symbol, so it's non enumerable by default).

As summary: do you want to be sure that object has a unique "lable" or "id" associated with it? Use WeakMaps, otherwise be prepared to possible clones around if some library uses Object.assign thinking that's a good way to copy properties around.

I still suggest the Labeler approach that Mark previously indicated.

# Michael McGlothlin (9 years ago)

I think using WeakMaps is a good idea. Using a Symbol will keep from cluttering the namespace with names others might want to use (id,label,etc) and seems more consistent than some external means of accessing the id. When I tried defining the value before I ran into issues with the prototype chain which is why I was using get and a second 'hidden' property. Using WeakMap seems like it'd fix the issue.

Can't say I've used Object.assign much - will have to experiment with it.

Thanks, Michael McGlothlin Sent from my iPhone

# Brendan Eich (9 years ago)

For posterity:

proposals:hashcodes, discussion:hashcodes

(from ES4 daze, a long time ago.)

# Michael McGlothlin (9 years ago)

I'm only seeing a couple comments there - was that all or am I missing something?

I think hashes as an overwritable method that returns a number is pretty standard and let's you combine member hashes with simple Bitwise ops. Could have on value types and a default on Object that would combine hash of own props that were value types or frozen.

Thanks, Michael McGlothlin Sent from my iPhone

# Brendan Eich (9 years ago)

Michael McGlothlin wrote:

I'm only seeing a couple comments there - was that all or am I missing something?

Did you read the note about security?

I think hashes as an overwritable method that returns a number is pretty standard and let's you combine member hashes with simple Bitwise ops. Could have on value types and a default on Object that would combine hash of own props that were value types or frozen.

As others noted, WeakMap sucks a lot of the O2 away from any hashcode proposal.

Map and Set do need a way for objects (not value types) to hash and compare, IMHO, but that was deferred to ES7/2016 or later.

# Michael McGlothlin (9 years ago)

One issue I did notice with WeakMaps is that because it can't use primative values as keys that they'll throw errors unless otherwise handled. Checking if they are an instanceof Object and if not using a Map instead seems to work.

I wouldn't think using primatives as WeakMap keys would be an issue.

const identities = new WeakMap(); const primatives = new Map(); let OBJECT_ID = 0;

function getObjectIdentity() { if ( this instanceof Object ) { if ( !identities.has( this ) ) { identities.set( this, ++OBJECT_ID ); } return identities.get( this ); }

if ( !primatives.has( this ) ) {
 primatives.set( this, ++OBJECT_ID );
}
return primatives.get( this );

}

# Bradley Meck (9 years ago)

I would avoid using Object identities on primitives as you can do === comparisons if IDs are serialized and have differing IDs for equivalent objects.

# Tab Atkins Jr. (9 years ago)

On Thu, Sep 10, 2015 at 11:02 AM, Michael McGlothlin <mike.mcglothlin at gmail.com> wrote:

One issue I did notice with WeakMaps is that because it can't use primative values as keys that they'll throw errors unless otherwise handled. Checking if they are an instanceof Object and if not using a Map instead seems to work.

I wouldn't think using primatives as WeakMap keys would be an issue.

Primitives have no notion of "reference" or "liveness". They can and are destroyed and recreated all the time, or shared across multiple independent non-communicating contexts. As such, they aren't a reliable key for WeakMaps, which keep their value alive "as long as the key is alive". Using a primitive string as a key can inadvertently leak the value for a long time, as the key might be interned and long-lived, far past the point when your code has dropped the intended references to it.

# Andrea Giammarchi (9 years ago)

in that way you are avoiding benefits provided by WeakMap + objects from other realms are not instanceof Object + Object.create(null) instanceof Object is false.

You probably want to use the WeakMap with anything that is typeof "object" or typeof "function" and for everything else you can use the value itself as unique identity for itself.

const identities = new WeakMap();
let OBJECT_ID = 0;
function getObjectIdentity(obj) {
  switch (typeof obj) {
    case 'object':
    case 'function':
      let id = identities.get(obj);
      if (!id) {
        identities.set(obj, (id = ++OBJECT_ID));
      }
      return id;
    default:
      return obj;
  }
}

Watch out collisions with possible integers or throw whenever the typeof is number. You could use this only in "use strict" capable environment otherwise primitives are going to be converted at runtime as objects with every single time a different identity.

# Brendan Eich (9 years ago)

Tab Atkins Jr. wrote:

Using a primitive string as a key can inadvertently leak the value for a long time, as the key might be interned and long-lived, far past the point when your code has dropped the intended references to it.

Indeed. Strings do not have observable reference semantics at all, so any WeakMap accepting a "foo" key would have to keep the entry alive forever, as "foo" could be uttered by later code. Same for 42 ;-). You might think primitives could be live or dead but only objects are like that.