profile
viewpoint
Francisco Ryan Tolmasky I tolmasky San Francisco, CA http://alertdebugging.com

subethaedit/SubEthaEdit 1095

General purpose plain text editor for macOS. Widely known for its live collaboration feature.

tolmasky/language 412

A fast PEG parser written in JavaScript with first class errors

eatnumber1/goal 322

g()()()()('al') → "gooooal"

Me1000/CappuTweetie 34

Tweetie Written in Cappuccino

tolmasky/MapKit 8

A Google Maps API abstraction layer written for Cappuccino

tolmasky/alertdebugging.com 2

My personal blog

tolmasky/narwhal 2

A JavaScript standard library, package manager, and more.

tolmasky/narwhal-jsc 2

Narwhal support for JavaScriptCore (incomplete)

issue openedcodemirror/CodeMirror

Unclear what the relationship between defineMode, defineMIME, modeInfo, and mimeModes is.

There seems to be a variety of overlapping, but independent, places where mode information is stored. For example, calling defineMode() and defineMIME seems to correctly update the CodeMirror.mimeModes object, but not CodeMirror.modeInfo, which means that CodeMirror.findModeByMIME fails to find modes added solely through defineMIME. It's not clear to me what the utility of calling defineMIME is at all, as the only source of truth seems to be the contents of the meta.js file.

Anyways, I am in a situation where I am wanting to add modes to new mime types dynamically, and the only thing that seems to actually have the desired effect is to manually mutate the modeInfo object directly. If this is the expected way to do things, then that is totally fine, just seems like defineMIME is somewhat of a red herring in its current incarnation.

created time in 3 days

push eventrunkit-open-source/templated-regular-expression

Francisco Ryan Tolmasky I

commit sha 31ce66a20630246c11ab3d99af53b59f93bc1adc

Use @climb/from-entires to support node 10. Bump version up to 1.0.0-alpha.6. Reviewed by @tolmasky.

view details

push time in 23 days

MemberEvent

issue commenttc39/proposal-record-tuple

Order dependence for equality

@littledan - yes this was the conceit I put upfront: if we are willing to allow the rare cases of records that:

  1. Share the same symbols (and corresponding value)
  2. But some of those symbols have the same name 3.. AND those symbols were inserted in a different order

to not be equal, then that resolves this issue. There is no "fundamental" issue or inconsistency here, just a performance degradation for certain edge cases, since it's up to us to define what contributes to equality. This also solves the issue of iterating the symbols in a consistent way even without the comparison issue.

The reasoning here is that it's very likely that records of the same "shape" are coming from the same factory function, so it would be a rare occurrence in an immutable record feature to be comparing records of two completely different creation sources that have almost identical shapes except for the fact that two functions that created them chose to order their identically named symbols differently.

And again, this failure state is not even necessarily that bad in my opinion. For example, in the case of memoization, a !== b can of course still result if f(a) === f(b), without caching. We're basically falling into the new String("a") !== new String("b") case with this, but again, triggered by a very unlikely set of events (which arguably people are already guarding against due to the old v8 advice of always putting your keys in the same order to trigger "performance").

Furthermore, I think this to a large degree is "essentially" correct in that it demonstrates the bounds of equality: Symbol() !== Symbol() due to nominal equality in javascript. In essence, we are are trying to compute the hash(record), but the only tool we have to work with with identically named symbols is the === operator (and !== of course). Since we can't represent two identically named Symbols in a format that would distinguish them without creating further "hidden data", it stands to reason that we can no longer compare two objects that parent them -- except for the only tool we have which is direct comparison, which we can employ for determining insertion order.

bakkot

comment created time in 3 months

issue commenttc39/proposal-record-tuple

Order dependence for equality

Just a slight correction here (although either could work), the tie breaker isn't creation order but rather insertion order (similar to how in objects the order is entirely based on insertion order). The reasoning for this is that it avoids needing a global counter at all, and in theory provides no leak whatsoever with regard to symbol creation times.

bakkot

comment created time in 3 months

issue commenttc39/proposal-record-tuple

Order dependence for equality

Perhaps I didn't describe the algorithm clearly enough -- the insertion order is the tie breaker. You don't need a third tie breaker since they can't be inserted at the same time (unless I am missing something). So, the comparison is:

function compare(recordInQuestion, symbol1, symbol2)
{
    const comparison = ToString(symbol1).localeCompare(ToString(symbol2));

    if (comparison !== 0)
        return comparison;
   
    const insertionOrder1 = InsertionOrder(symbol1, recordInQuestion);
    const insertionOrder2 = InsertionOrder(symbol2, recordInQuestion);

    // Can't be 0.
    return insertionOrder1 < insertionOrder2 ? -1 : 1;
}

The change I am saying to make is that insertion order matters when determining whether a === b in the case of a and b both having symbols with identical names. In other words, I am maintaining if a === b then f(a) === f(b) by expanding the times where a !== b.

A few examples:

const SymbolA = Symbol();
const SymbolB = Symbol();

#{ [SymbolA]: 1, [SymbolB]: 2 } === #{ [SymbolA]: 1, [SymbolB]: 2 } // trivially equal
#{ [SymbolA]: 1, [Symbol.iterator]: f } === #{ [Symbol.iterator]: f, [SymbolA]: 1 } // equal since we never have to rely on order for non-name-clashing symbols
#{ [SymbolA]: 1, [SymbolB]: 2 , [Symbol.iterator]: f } === #{ [Symbol.iterator]: f, [SymbolA]: 1, [SymbolB]: 2  } // same as above
#{ [SymbolA]: 1, [SymbolB]: 2, [Symbol.iterator]: f } === #{ [SymbolA]: 1 , [Symbol.iterator]: f, [SymbolB]: 2 } // again, SymbolA and SymbolB in both instances are ordered the same

#{ [SymbolB]: 1, [SymbolA]: 2 } !== #{ [SymbolA]: 1, [SymbolB]: 2 } // not equal because we have to fall back to insertion order when sorting name-clash symbols
#{ [SymbolA]: 1, [SymbolB]: 2, [Symbol.iterator]: f } !== #{ [Symbol.iterator]: f, [SymbolB]: 2, [SymbolA]: 1  } // same as above

(#{ [SymbolA]: 1, [SymbolB]: 2 } with .[SymbolA] = 1) === #{ [SymbolA]: 1, [SymbolB]: 2 } // still equal because `with` maintains sort order of original record

So, if two objects have the same symbols, each with a unique name, then the ordering of insertion doesn't matter and the two objects are always equal. If however, the objects contain multiple symbols with the same name, then the insertion order of those symbols matters for

bakkot

comment created time in 3 months

issue commenttc39/proposal-record-tuple

Order dependence for equality

What if we say insertion order matters towards equality iff two symbols have the same ToString(). That is to say, the ordering of the symbols is lexicographical first, insertion order second (with the added convenience that "mutation" methods keep the original record's insertion order, so OwnPropertySymbols(#{ [SymbolWithToStringA]: 5, [OtherSymbolWithToStringA]: 5} with .[SymbolWithToStringA] = 8) === [SymbolWithToStringA, OtherSymbolWithToStringA]). This breaks no if a === b, f(a) === f(b) by saying a !== b when a and b both have multiple symbols with the same name, but inserted in different orders, while still allowing the convenience of updating records without futzing with their property ordering, which would usually be the main reason to avoid this.

I'm not sure if we're aligned on goals, but I see the main goals as being:

  1. Maintain that if a === b, f(a) === f(b)
  2. Strive to have Object.is(a, b) as often as possible when Set(entries(a)) equals Set(entries(b))
  3. Allow records to participate in many of the features accessible through symbols, such as Symbol.iterator and Symbol.toStringTag (and util.inspect.custom symbol in node for debugging purposes).
  4. Avoid leaking information about the larger system.

I feel like the lexicographical ordering, falling back to insertion order, and maintaining "originating" object insertion order seems to satisfy the best balance of the above 3 goals. It satisfies goal 1, 3, and 4(?) completely, at the expense of not having goal 2 be true as often as if we either disallow symbols completely, or allow them with a global counter to provide "identical ordering".

bakkot

comment created time in 3 months

issue commenttc39/proposal-record-tuple

Order dependence for equality

For the sake of completeness since we are rehashing this, I believe there is another option, which is to only allow Symbols that have unique names. That is to say, similarly to how you can't have { "x": 1, "x": 2 } (the second overwrites the first), in records, if ToString(symbol1) === ToString(symbol2), then either symbol2 overwrites symbol1 or it throws an error. This would allow all the built-in symbols to always work (since IIRC they are the only ones where ToString resolves to something without quotes "Symbol(Symbol.iterator)" vs. "Symbol("my-symbol")" and would only pop up if you use to unnamed symbols or two named symbols with the same name.

bakkot

comment created time in 3 months

more