Strawman proposal: new `is` operator

Isiah Meadows impinball at gmail.com
Mon Sep 1 03:23:03 PDT 2014


Here's my (more formalized) proposition. I also have added a proposed
@@isInstance property for the `isa` equivalent for @@hasInstance. I know
this was mostly put on hold initially because of my lack of proper
formulation, but here it is.


### Rationale: ###

Include an `isa` operator to determine if an object is a direct instance of
a constructor, and not simply a child constructor. It is also meant to work
with native primitives as well, which the `instanceof` operator lacks. It
helps when child constructors are not necessarily known, and a method specific
to the parent is made (there is no way currently to determine this) or when
implementing an abstract class that defines methods that must be overridden
for all subclasses. Example usage:

```js
// Example 1:

class Foo {
  constructor() {
    if (this isa Foo) throw new TypeError('Abstract class')
  }
}

class Bar extends Foo {}

let foo = new Foo(); // Error!
let bar = new Bar(); // Good

/****************************************************************************/

// Example 2:

class Foo {
  constructor() {}

  doSomething() {
    if (!(this isa Foo)) {
      throw new TypeError('Must be a Foo instance');
    }
    // do stuff
  }
}

class Bar extends Foo {}

let bar = new Bar();
bar.doSomething(); // Error!

/****************************************************************************/

// Example 3: (adapted from old code)

const assert = (obj) => {
  let isInstance = (b) => { if (!(a instanceof b)) throw new TypeError() };
  let isType = (b) => { if (!(a isa b)) throw new TypeError() };
  return {
    isInstance: isInstance,
    isType: isType,
  };
};

// ...

class WhileStatement extends Statement {
  constructor(condition, body, label = '') {
    super();
    assert(condition).isInstance(Expression);
    assert(body).isType(Array);
    body.forEach((i) => assert(i).isInstance(Expression));
    assert(label).isType(String); // native string
    this.condition = condition;
    this.body = body;
    this.label = label;
  }
}

// ...

class Foo extends String {
  constructor(val) {
    super(val);
  }
}

new WhileStatement(expr, [...elements], 'label') // Good
new WhileStatement(expr, [...elements])          // Good

new WhileStatement(stmt, [...elements], 'label') // Error!
new WhileStatement(expr, {}, 'label')            // Error!
new WhileStatement(expr, typedArray, 'label')    // Error! (before iterating)
new WhileStatement(expr, funcArray, 'label')     // Error!

/* Currently cannot make this throw */
new WhileStatement(expr, [...elements], new Foo('label')) // Error! (desired)


/****************************************************************************/

// Example 4: (Example 3 using Node builtins)
class WhileStatement extends Statement {
  constructor(condition, body, label = '') {
    super();
    assert(condition instanceof Expression);
    assert(body isa Array);
    body.forEach((i) => assert(i instanceof Expression));
    assert(label isa String); // native string
    this.condition = condition;
    this.body = body;
    this.label = label;
  }
}

// ...

class Foo extends String {
  constructor(val) {
    super(val);
  }
}

new WhileStatement(expr, [...elements], 'label') // Good
new WhileStatement(expr, [...elements])          // Good

new WhileStatement(stmt, [...elements], 'label') // Error!
new WhileStatement(expr, {}, 'label')            // Error!
new WhileStatement(expr, typedArray, 'label')    // Error! (before iterating)
new WhileStatement(expr, funcArray, 'label')     // Error!

/* Currently cannot make this throw */
new WhileStatement(expr, [...elements], new Foo('label')) // Error! (desired)
```

This already exists in some form in several languages, such as Ruby. It would
be great for unit testing, library API type checking (e.g.
`options isa Object`, `options isa Map`, `code isa String`), and it may lay
some solid groundwork for
[this proposition](http://wiki.ecmascript.org/doku.php?id=strawman:types) to
more easily get off the ground (internal implementation will become relatively
easy).


### Well-known Symbols: ###

Add this row to the table.

<!--
Apologies to those of you reading this text-only:

Please pardon my HTML...it's the only way I could get this to display right
otherwise. I apologize...I've been using a text editor to type all this
crap, with no more than automatic indention, brace detection and syntax
highlighting. :(

I have made it a point to keep each line to a maximum of 79 columns so it can
still be easily read in emacs/etc. in a popup terminal.
-->


<table>
<thead>
<th>Specification Name</th>
<th>[[Description]]</th>
<th>Value and Purpose</th>
</thead>
<tbody>
<tr>
<td style="text-align:center">...</td>
<td style="text-align:center">...</td>
<td style="text-align:center">...</td>
</tr>
<tr>
<td>@@isInstance</td>
<td>"Symbol.isInstance"</td>
<td style="width:40%">
A method that determines if a constructor object recognizes an object or
primitive as of the same type as the constructor. Called by the semantics
of the `isa` operator.
</td>
</tr>
<tr>
<td style="text-align:center">...</td>
<td style="text-align:center">...</td>
<td style="text-align:center">...</td>
</tr>
</table>


### Syntax: ###

> *RelationalExpression*:

> > *RelationalExpression*<sub>[?In, ?Yield]</sub> `isa`
    *ShiftExpression* <sub>[?Yield]</sub>


### Static Semantics: IsFunctionDefinition ###

> *RelationalExpression*:

> > *RelationalExpression* `isa` *ShiftExpression*

> 1. Return **false**.


### Static Semantics: IsValidSimpleTarget ###

> *RelationalExpression*:

> > *RelationalExpression* `isa` *ShiftExpression*

> 1. Return **false**.


### Runtime Semantics: Evaluation ###

> *RelationalExpression* `isa` *ShiftExpression*

1. Let *lref* be the result of evaluating *RelationalExpression*.

2. Let *lval* be GetValue(*lref*).

3. ReturnIfAbrupt(*lval*)

4. Let *rref* be the result of evaluating *ShiftExpression*.

5. Let *rval* be GetValue(*rref*).

6. ReturnIfAbrupt(*rval*).

7. Return IsaOperator(*lval*, *rval*).


### Abstract Operation: IsaOperator(O, C) ###

1. If Type(*C*) is not Object, throw a **TypeError** exception.

2. Let *isaHandler* be GetMethod(@@isInstance)

3. ReturnIfAbrupt(*isaHandler*)

4. If *isaHandler* is not `undefined`, then

  1. Let *result* be the result of calling the [[Call]] internal method of
     *isaHandler* with *C* passed as *thisArgument* and a new List containing
     *O* as its *argumentsList*.

  2. Return ToBoolean(*result*).

5. If *O* is a primitive value, then

  1. If IsPrimitiveConstructor(*C*, *O*), return **true**.

  2. Return **false**.

6. If IsCallable(*C*) is **false**, throw a **TypeError** exception.

7. Return IsInstance(*C*, *O*).


### Abstract Operation: IsInstance(C, O) ###

1. If IsCallable(*C*) is **false**, return **false**.

2. If *C* has a [[BoundTargetFunction]] internal slot, then

  1. Let *BC* be the value of *C*'s [[BoundTargetFunction]] internal slot.

  2. Return IsaOperator(*O*, *BC*).

3. If Type(*O*) is not Object, return **false**.

4. Let *P* be Get(*C*, `"prototype"`).

5. ReturnIfAbrupt(*P*).

6. If Type(*P*) is not Object, throw a **TypeError** exception.

7. Set *O* to the result of calling the [[GetPrototypeOf]] internal method of
   *O* with no arguments.

8. ReturnIfAbrupt(*O*).

9. If *O* is `null`, return **false**.

10. If SameValue(*O*) is **true**, return **true**.

11. Return **false**.


### Abstract Operation: IsPrimitiveConstructor(C, O) ###

1. If *C* is not the same Object value as one of the following intrinsic
   constructors, return **false**:

  - %Boolean%

  - %Number%

  - %String%

  - %Symbol%

2. If Type(*O*) and *C* are the same Object value, return **true**.

3. Return **false**.


More information about the es-discuss mailing list