Skip to content

Splitting Variable/Extractor matcher hook? #303

Closed
@tabatkins

Description

@tabatkins

Right now, we're using a single Symbol.customMatcher method to handle both Variable Patterns Foo and Extractor Patterns Foo(...). The former can just return true/false, but the latter needs returns either an iterable or false. (And true/iterable both do the obvious thing when used in the opposite context.)

Matthew brought up in the champion meeting today that he was somewhat concerned about the perf impact of this in variable pattern cases - String just checks if your value is a string (or a String), while String(...) will run the pattern on the (primitive, unwrapped if necessary) string, so the String[Symbol.customMatcher] method has to be defined to essentially be:

String[Symbol.customMatcher] = function(subject) {
	if( typeof subject == "string" ) return [subject];
	if( subject instanceof String ) return [String(subject)];
	return false;
}

And incurring the cost of a temp array for the likely much more common String variable matcher didn't seem very great. We resolved in the meeting to make sure that we spec things such that, when used in the pattern, the built-ins' return values are unobservable, so the temp array can be elided entirely by codegen. (You'd only see it if you invoked the method directly.)

But this doesn't help authors, who have to write the less efficient code all the time. Maybe we can solve this more directly, by splitting the hooks into two:

  • Symbol.customMatcher, which is invoked by the variable pattern syntax, and needs to return true or false.
  • Symbol.customExtractor, which is invoked by the extractor pattern syntax, and needs to return an iterable or false.

We check for both hooks on an object, in both cases, but check for the appropriate one first. That is, Foo will invoke customMatcher if it exists; if it doesn't, but customExtractor does, it invokes that (and treats an iterable as a true result); if neither exists, it's just equality-checked, as normal for variables. Similarly, Foo(...) will invoke customExtractor if it exists; if it doesn't, but customMatcher does, it invokes that (and treats true as an empty iterable); if neither exists it's a TypeError.

So that would mean that String would be instead defined as:

String[Symbol.customMatcher] = function(subject) {
	return typeof subject == "string" || subject instanceof String;
}
String[Symbol.customExtractor] = function(subject) {
	if( typeof subject == "string" ) return [subject];
	if( subject instanceof String ) return [String(subject)];
	return false;
}

This lets us avoid having to be clever in codegen for built-ins and also lets authors receive the same benefits - Option.Some can just return a bool. In more complex cases where returning the values for an extractor might be expensive, you can avoid quite a bit of work when not necessary.


For the "auto-create a custom matcher if you didn't provide one in the class block" behavior, I presume we'd only auto-generate the customMatcher one; we can't assume anything meaningful for extractors by default. But if you only provide a customExtractor, should we still auto-define the customMatcher, or just defer to the author's customExtractor? I'm leaning toward no - if the author defines either, we don't generate anything, because they know what they're doing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions