openlang

Is an NFC key a sensible default for name lookup?

Okay, a user pastes their name and it ends in e followed by U+0301. Later they type the same name and the keyboard gives U+00E9. To them it's one name, canonically equivalent too, and still two different strings.

My plan is to keep the submitted text for display and derive an NFC key for lookup. Two representations, and each operation has to know which one it reads.

NFC won't decide whether two matching names belong to different accounts. That's a product call. Is this a sensible default anyway?

后续讨论 6

顺着彼此的依据读下去
  1. 固定链接

    Agreed on the split. Lookup should accept only the normalized key, with the submitted text carried next to it.

    The empty field is where I'd be careful. If the form lets someone leave the name out, null should mean they supplied none. It shouldn't double as normalization not having run yet. Two callers show the difference. The saved-name view wants the spelling as submitted. The search caller wants the derived key. If two accounts can end up with the same key, search returns several matches or asks for another identifier.

  2. 固定链接

    Type the name. Submit. normalize("NFC") runs right there and both versions leave together. So no stored name ever sits in a not-normalized-yet state, and hollis's null keeps a single meaning.

    If lookup is async, the list of matches it eventually produces is its own result. It isn't a new meaning for the name. And once that list can hold more than one account, the search caller stops being an ownership check and turns into a picker. That's the point where an account id pays for itself.

  3. 固定链接

    In Swift, "e\u{301}" == "\u{E9}" is true, which I find kind of fun. String equality is canonical equivalence, and hashing follows it, so a [String: Account] already treats your two spellings as one key. Which sounds like it solves your problem, and partly does.

    But the stored string is still whichever one came in, and the moment that key goes to a database or another language it compares by code units again. I'd still derive the NFC key explicitly and store it next to the original.

  4. 固定链接

    Wrong call on the lookup side above. "Lookup should accept only the normalized key" makes every caller responsible for running normalize("NFC") before it searches, and one of them will forget. Better that lookup takes the text as typed and derives the key itself, the same as the submit path.

    const byKey = new Map<string, string>();
    
    function lookup(typed: string): string | undefined {
      return byKey.get(typed.normalize("NFC"));
    }
    
    byKey.set("\u00E9mile".normalize("NFC"), "acct-1");
    console.log(lookup("e\u0301mile"));

    The decomposed spelling finds the account stored under the composed one. The key never reaches a caller's hands, so there's no half-normalized value for anyone to pass in.

  5. 固定链接

    Bridging changes that on the Swift side. NSString equality is literal, so "e\u{301}" and "\u{E9}" bridged into an NSMutableDictionary make two entries, and precomposedStringWithCanonicalMapping is the NFC step there.

  6. 固定链接

    Worth remembering that normalize only arrived with ES2015, so a table built by older code can hold keys that were never normalized (the CoffeeScript files I still read compared whatever the form sent, code unit for code unit, and nobody thought twice about it). hollis's lookup normalizes both sides, which is right, but only for keys it set itself. Old keys need one rebuild pass first.

    const old = new Map([["émile", "acct-1"]]); // keyed before normalize
    const key = (s) => s.normalize("NFC");
    console.log(old.get(key("émile"))); // undefined
    const rebuilt = new Map([...old].map(([k, v]) => [key(k), v]));
    console.log(rebuilt.get(key("émile"))); // acct-1

    After that pass the decomposed spelling finds the old account too.

同一个问题的其他角度

  1. Mode line columns drift as soon as a buffer name has Hangul in it

    Padded the buffer name in my mode line to a fixed width so the bits after it line up, and it worked for a long while (well, i…

    回复 1最近活动 emacs-lisp
  2. Truncating by bytes like the old C++, and Rust panics

    Porting a chunk of old C++ to Rust that cuts a std::string down to a byte limit with a plain s.substr(0, n), and I figured it…

    回复 6最近活动 rustcpp
  3. NFC lookup keys and JCS signatures

    Signing complicates kim_dh's earlier question about NFC lookup keys, which separates the submitted name from an NFC lookup ke…

    回复 4最近活动 typescript