Proposal: Symbol.inObject well-known symbol to customize the "in" operator's behavior

classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

Proposal: Symbol.inObject well-known symbol to customize the "in" operator's behavior

Tom Barrasso
Like Symbol.hasInstance but for the "in" operator.
This symbol would work for both native and user-defined objects.

*Example implementation* prototyping native object:

```js
String.prototype[Symbol.inObject] =
  function(searchString) {
    return this.includes(searchString)
}
```

*Example implementation* for user-defined object:

```js
function range(min, max) => ({
    [Symbol.inObject]: (prop) => {
        return (prop >= min && prop <= max)
    }
})
```

*Example usage*:

```js
("foo" in "food")    // true
(14 in range(1, 25)) // true
```

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Symbol.inObject well-known symbol to customize the "in" operator's behavior

Claude Pache


Le 9 mai 2019 à 20:52, Tom Barrasso <[hidden email]> a écrit :

Like Symbol.hasInstance but for the "in" operator.
This symbol would work for both native and user-defined objects.

*Example implementation* prototyping native object:

```js
String.prototype[Symbol.inObject] =
  function(searchString) {
    return this.includes(searchString)
}
```

*Example implementation* for user-defined object:

```js
function range(min, max) => ({
    [Symbol.inObject]: (prop) => {
        return (prop >= min && prop <= max)
    }
})
```

*Example usage*:

```js
("foo" in "food")    // true
(14 in range(1, 25)) // true
```

Those two examples seem to give to the `in` operator a meaning that it was not intended to have. The `in` operator is specifically meant to check whether a given property exists in a given object.

Also, there already exists a way to customise the behaviour of the `in` operator, namely by using a Proxy.

—Claude

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Symbol.inObject well-known symbol to customize the "in" operator's behavior

Tom Barrasso
Thanks interesting, I hadn’t realized it was possible to “trap” the in operator using Proxy. I may be wrong, but I don’t think Proxy is capable of operating on the prototype chain. Specifically, I don’t think you can change the behavior of the in operator for all Strings (which I’m sure many would prefer).

If this Symbol were seriously considered I believe it would expand the meaning of the in operator as you’re correct, this is definitely not it’s current intention.

Tom

On Thu, May 9, 2019 at 4:39 PM Claude Pache <[hidden email]> wrote:


Le 9 mai 2019 à 20:52, Tom Barrasso <[hidden email]> a écrit :

Like Symbol.hasInstance but for the "in" operator.
This symbol would work for both native and user-defined objects.

*Example implementation* prototyping native object:

```js
String.prototype[Symbol.inObject] =
  function(searchString) {
    return this.includes(searchString)
}
```

*Example implementation* for user-defined object:

```js
function range(min, max) => ({
    [Symbol.inObject]: (prop) => {
        return (prop >= min && prop <= max)
    }
})
```

*Example usage*:

```js
("foo" in "food")    // true
(14 in range(1, 25)) // true
```

Those two examples seem to give to the `in` operator a meaning that it was not intended to have. The `in` operator is specifically meant to check whether a given property exists in a given object.

Also, there already exists a way to customise the behaviour of the `in` operator, namely by using a Proxy.

—Claude

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Symbol.inObject well-known symbol to customize the "in" operator's behavior

Isiah Meadows-2
There's several ways proxies can change the result of `in`:

- Directly via `handler.has(target, key)` (returns boolean)
- Indirectly via `handler.getOwnPropertyDescriptor(target, key)` (returns existing descriptor or `undefined` if missing)
- Indirectly via `handler.getPrototypeOf(target)' (returns relevant prototype)

The proxy trap `has` is almost a direct substitute for your `Symbol.inObject` in most cases, provided you're okay with keys getting stringified.

On Thu, May 9, 2019 at 17:17 Tom Barrasso <[hidden email]> wrote:
Thanks interesting, I hadn’t realized it was possible to “trap” the in operator using Proxy. I may be wrong, but I don’t think Proxy is capable of operating on the prototype chain. Specifically, I don’t think you can change the behavior of the in operator for all Strings (which I’m sure many would prefer).

If this Symbol were seriously considered I believe it would expand the meaning of the in operator as you’re correct, this is definitely not it’s current intention.

Tom

On Thu, May 9, 2019 at 4:39 PM Claude Pache <[hidden email]> wrote:


Le 9 mai 2019 à 20:52, Tom Barrasso <[hidden email]> a écrit :

Like Symbol.hasInstance but for the "in" operator.
This symbol would work for both native and user-defined objects.

*Example implementation* prototyping native object:

```js
String.prototype[Symbol.inObject] =
  function(searchString) {
    return this.includes(searchString)
}
```

*Example implementation* for user-defined object:

```js
function range(min, max) => ({
    [Symbol.inObject]: (prop) => {
        return (prop >= min && prop <= max)
    }
})
```

*Example usage*:

```js
("foo" in "food")    // true
(14 in range(1, 25)) // true
```

Those two examples seem to give to the `in` operator a meaning that it was not intended to have. The `in` operator is specifically meant to check whether a given property exists in a given object.

Also, there already exists a way to customise the behaviour of the `in` operator, namely by using a Proxy.

—Claude
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
--

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Symbol.inObject well-known symbol to customize the "in" operator's behavior

Tom Barrasso
`handler.has(target, key)` appears to be the most direct way to accomplish this, although as mentioned in requires dealing with string parsing.
Here's a quick example I put together using `Proxy` to accomplish the `range` function.

```js
function range(min, max) {
  return new Proxy(Object.create(null), {
    has (target, key) {
      return (Number(key) >= min && Number(key) <= max)
    }
  })
}
```

However, the same cannot be achieved easily for Strings. I consider using Strings directly, but got a "Uncaught TypeError: Cannot create proxy with a non-object as target or handler."
I also considered using template tags as well, but that seemed like more of a novel hack and not advantageous compared to a simple function. This is the best I could come up with.

```js
function q(str) {
  const S = class extends String{}
  return new Proxy(new S(str), {
    has (target, searchString) {
      return str.includes(searchString)
    }
  })
}
```

A class that extends String is sufficient where a String itself is not, although this has ramifications elsewhere like being passed by reference, not value, through functions.
Based on the stringification and inability to work with native object prototype, I'm not fully convinced that `Proxy` is a complete substitute for `Symbol.inObject`.

Tom

On Thu, May 9, 2019 at 7:20 PM Isiah Meadows <[hidden email]> wrote:
There's several ways proxies can change the result of `in`:

- Directly via `handler.has(target, key)` (returns boolean)
- Indirectly via `handler.getOwnPropertyDescriptor(target, key)` (returns existing descriptor or `undefined` if missing)
- Indirectly via `handler.getPrototypeOf(target)' (returns relevant prototype)

The proxy trap `has` is almost a direct substitute for your `Symbol.inObject` in most cases, provided you're okay with keys getting stringified.

On Thu, May 9, 2019 at 17:17 Tom Barrasso <[hidden email]> wrote:
Thanks interesting, I hadn’t realized it was possible to “trap” the in operator using Proxy. I may be wrong, but I don’t think Proxy is capable of operating on the prototype chain. Specifically, I don’t think you can change the behavior of the in operator for all Strings (which I’m sure many would prefer).

If this Symbol were seriously considered I believe it would expand the meaning of the in operator as you’re correct, this is definitely not it’s current intention.

Tom

On Thu, May 9, 2019 at 4:39 PM Claude Pache <[hidden email]> wrote:


Le 9 mai 2019 à 20:52, Tom Barrasso <[hidden email]> a écrit :

Like Symbol.hasInstance but for the "in" operator.
This symbol would work for both native and user-defined objects.

*Example implementation* prototyping native object:

```js
String.prototype[Symbol.inObject] =
  function(searchString) {
    return this.includes(searchString)
}
```

*Example implementation* for user-defined object:

```js
function range(min, max) => ({
    [Symbol.inObject]: (prop) => {
        return (prop >= min && prop <= max)
    }
})
```

*Example usage*:

```js
("foo" in "food")    // true
(14 in range(1, 25)) // true
```

Those two examples seem to give to the `in` operator a meaning that it was not intended to have. The `in` operator is specifically meant to check whether a given property exists in a given object.

Also, there already exists a way to customise the behaviour of the `in` operator, namely by using a Proxy.

—Claude
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
--

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Symbol.inObject well-known symbol to customize the "in" operator's behavior

guest271314
```
"" in q("food") // true
```

Is the purpose of the code to return a boolean for string and integer input? 

What is the significance of  ```in``` operator usage to output?

On Fri, May 10, 2019 at 2:34 AM Tom Barrasso <[hidden email]> wrote:
`handler.has(target, key)` appears to be the most direct way to accomplish this, although as mentioned in requires dealing with string parsing.
Here's a quick example I put together using `Proxy` to accomplish the `range` function.

```js
function range(min, max) {
  return new Proxy(Object.create(null), {
    has (target, key) {
      return (Number(key) >= min && Number(key) <= max)
    }
  })
}
```

However, the same cannot be achieved easily for Strings. I consider using Strings directly, but got a "Uncaught TypeError: Cannot create proxy with a non-object as target or handler."
I also considered using template tags as well, but that seemed like more of a novel hack and not advantageous compared to a simple function. This is the best I could come up with.

```js
function q(str) {
  const S = class extends String{}
  return new Proxy(new S(str), {
    has (target, searchString) {
      return str.includes(searchString)
    }
  })
}
```

A class that extends String is sufficient where a String itself is not, although this has ramifications elsewhere like being passed by reference, not value, through functions.
Based on the stringification and inability to work with native object prototype, I'm not fully convinced that `Proxy` is a complete substitute for `Symbol.inObject`.

Tom

On Thu, May 9, 2019 at 7:20 PM Isiah Meadows <[hidden email]> wrote:
There's several ways proxies can change the result of `in`:

- Directly via `handler.has(target, key)` (returns boolean)
- Indirectly via `handler.getOwnPropertyDescriptor(target, key)` (returns existing descriptor or `undefined` if missing)
- Indirectly via `handler.getPrototypeOf(target)' (returns relevant prototype)

The proxy trap `has` is almost a direct substitute for your `Symbol.inObject` in most cases, provided you're okay with keys getting stringified.

On Thu, May 9, 2019 at 17:17 Tom Barrasso <[hidden email]> wrote:
Thanks interesting, I hadn’t realized it was possible to “trap” the in operator using Proxy. I may be wrong, but I don’t think Proxy is capable of operating on the prototype chain. Specifically, I don’t think you can change the behavior of the in operator for all Strings (which I’m sure many would prefer).

If this Symbol were seriously considered I believe it would expand the meaning of the in operator as you’re correct, this is definitely not it’s current intention.

Tom

On Thu, May 9, 2019 at 4:39 PM Claude Pache <[hidden email]> wrote:


Le 9 mai 2019 à 20:52, Tom Barrasso <[hidden email]> a écrit :

Like Symbol.hasInstance but for the "in" operator.
This symbol would work for both native and user-defined objects.

*Example implementation* prototyping native object:

```js
String.prototype[Symbol.inObject] =
  function(searchString) {
    return this.includes(searchString)
}
```

*Example implementation* for user-defined object:

```js
function range(min, max) => ({
    [Symbol.inObject]: (prop) => {
        return (prop >= min && prop <= max)
    }
})
```

*Example usage*:

```js
("foo" in "food")    // true
(14 in range(1, 25)) // true
```

Those two examples seem to give to the `in` operator a meaning that it was not intended to have. The `in` operator is specifically meant to check whether a given property exists in a given object.

Also, there already exists a way to customise the behaviour of the `in` operator, namely by using a Proxy.

—Claude
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
--
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Symbol.inObject well-known symbol to customize the "in" operator's behavior

Claude Pache
In reply to this post by Tom Barrasso


Le 9 mai 2019 à 23:17, Tom Barrasso <[hidden email]> a écrit :

If this Symbol were seriously considered I believe it would expand the meaning of the in operator as you’re correct, this is definitely not it’s current intention.


The `in` operator has a well-defined meaning, that by design you can’t ignore even with the fanciest proxy. (And no, there is no hope for introducing a new mechanism that allows to overcome those limitations, since they are by design.)
Consider for example the following proxy around a String object:

```js
function conflateInAndIncludes(str) {
    return new Proxy(Object(str), { 
        has(target, key) { return str.includes(key) } 
    })
}

var FrankensteinFood = conflateInAndIncludes("food");

"foo" in FrankensteinFood // true, yeah!
"bar" in FrankensteinFood // false, yeah!
"length" in FrankensteinFood // TypeError: proxy can't report a non-configurable own property '"length"' as non-existent
```


—Claude

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Symbol.inObject well-known symbol to customize the "in" operator's behavior

Tom Barrasso
There are obvious limitations given the masking/ overwriting of the default implementation, but are these limitations not also true for other symbols like `Symbol.hasInstance`? If overwritten (including in a prototype chain), it then masks the original behavior of the `instanceof` operator. Symbols are one way to do this more explicitly and without the caveats of needing to use a Proxy everywhere.

Any symbol that allows for reconfiguring/ overloading an operator risks breaking the assumptions about how that operator is supposed to behave. Perhaps I’m not understanding some way this is different/ more significant for overloading the `in` operator than say `instanceof` or the spread operator? 

Perhaps a more reasonable use case than prototyping String would be a user-defined, iterable data structure like an unrolled linked list?
Even with `Proxy` this is not possible unless we wrapped all constructor calls.

```js
class UnrolledLinkedList {
  constructor(nodeCapacity = 8) {
    // ...
  }

  [Symbol.inObject](index) {
    return (index >= 0 && index <= this.length)
 }

let list = new UnrolledLinkedList();
list.add(1);
list.add(2);
list.add(3);

if (1 in list) {
  // ...
}
```

A developer may want to mimic the behavior of `Array`, but using a `Proxy` would require wrapping all constructor calls or calls to the `in` operator.
The other alternative is to set numeric properties on the list, but doing so isn't always possible for every data structure.

Tom



On Fri, May 10, 2019 at 1:23 AM Claude Pache <[hidden email]> wrote:


Le 9 mai 2019 à 23:17, Tom Barrasso <[hidden email]> a écrit :

If this Symbol were seriously considered I believe it would expand the meaning of the in operator as you’re correct, this is definitely not it’s current intention.


The `in` operator has a well-defined meaning, that by design you can’t ignore even with the fanciest proxy. (And no, there is no hope for introducing a new mechanism that allows to overcome those limitations, since they are by design.)
Consider for example the following proxy around a String object:

```js
function conflateInAndIncludes(str) {
    return new Proxy(Object(str), { 
        has(target, key) { return str.includes(key) } 
    })
}

var FrankensteinFood = conflateInAndIncludes("food");

"foo" in FrankensteinFood // true, yeah!
"bar" in FrankensteinFood // false, yeah!
"length" in FrankensteinFood // TypeError: proxy can't report a non-configurable own property '"length"' as non-existent
```


—Claude

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Symbol.inObject well-known symbol to customize the "in" operator's behavior

guest271314
Still not gathering the significance or purpose of using ```in``` to generate a boolean output. What appears to be the requirement can be achieved by using ```Set``` or ```Map``` 

```
let m = new Map([[0, "a"],[ 1, 1], [2, {}]);
m.has(0); // true
m.get(2) // {}
```

On Fri, May 10, 2019 at 1:41 PM Tom Barrasso <[hidden email]> wrote:
There are obvious limitations given the masking/ overwriting of the default implementation, but are these limitations not also true for other symbols like `Symbol.hasInstance`? If overwritten (including in a prototype chain), it then masks the original behavior of the `instanceof` operator. Symbols are one way to do this more explicitly and without the caveats of needing to use a Proxy everywhere.

Any symbol that allows for reconfiguring/ overloading an operator risks breaking the assumptions about how that operator is supposed to behave. Perhaps I’m not understanding some way this is different/ more significant for overloading the `in` operator than say `instanceof` or the spread operator? 

Perhaps a more reasonable use case than prototyping String would be a user-defined, iterable data structure like an unrolled linked list?
Even with `Proxy` this is not possible unless we wrapped all constructor calls.

```js
class UnrolledLinkedList {
  constructor(nodeCapacity = 8) {
    // ...
  }

  [Symbol.inObject](index) {
    return (index >= 0 && index <= this.length)
 }

let list = new UnrolledLinkedList();
list.add(1);
list.add(2);
list.add(3);

if (1 in list) {
  // ...
}
```

A developer may want to mimic the behavior of `Array`, but using a `Proxy` would require wrapping all constructor calls or calls to the `in` operator.
The other alternative is to set numeric properties on the list, but doing so isn't always possible for every data structure.

Tom



On Fri, May 10, 2019 at 1:23 AM Claude Pache <[hidden email]> wrote:


Le 9 mai 2019 à 23:17, Tom Barrasso <[hidden email]> a écrit :

If this Symbol were seriously considered I believe it would expand the meaning of the in operator as you’re correct, this is definitely not it’s current intention.


The `in` operator has a well-defined meaning, that by design you can’t ignore even with the fanciest proxy. (And no, there is no hope for introducing a new mechanism that allows to overcome those limitations, since they are by design.)
Consider for example the following proxy around a String object:

```js
function conflateInAndIncludes(str) {
    return new Proxy(Object(str), { 
        has(target, key) { return str.includes(key) } 
    })
}

var FrankensteinFood = conflateInAndIncludes("food");

"foo" in FrankensteinFood // true, yeah!
"bar" in FrankensteinFood // false, yeah!
"length" in FrankensteinFood // TypeError: proxy can't report a non-configurable own property '"length"' as non-existent
```


—Claude
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Symbol.inObject well-known symbol to customize the "in" operator's behavior

Isiah Meadows-2
In reply to this post by Claude Pache
This example could be fixed by passing `{}` as the proxy target.

On Fri, May 10, 2019 at 01:23 Claude Pache <[hidden email]> wrote:


Le 9 mai 2019 à 23:17, Tom Barrasso <[hidden email]> a écrit :

If this Symbol were seriously considered I believe it would expand the meaning of the in operator as you’re correct, this is definitely not it’s current intention.


The `in` operator has a well-defined meaning, that by design you can’t ignore even with the fanciest proxy. (And no, there is no hope for introducing a new mechanism that allows to overcome those limitations, since they are by design.)
Consider for example the following proxy around a String object:

```js
function conflateInAndIncludes(str) {
    return new Proxy(Object(str), { 
        has(target, key) { return str.includes(key) } 
    })
}

var FrankensteinFood = conflateInAndIncludes("food");

"foo" in FrankensteinFood // true, yeah!
"bar" in FrankensteinFood // false, yeah!
"length" in FrankensteinFood // TypeError: proxy can't report a non-configurable own property '"length"' as non-existent
```


—Claude
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
--

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss