Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import Gist that compares with Extensions proposal #9

Closed
js-choi opened this issue Oct 2, 2021 · 4 comments
Closed

Import Gist that compares with Extensions proposal #9

js-choi opened this issue Oct 2, 2021 · 4 comments
Labels
documentation Improvements or additions to documentation

Comments

@js-choi
Copy link
Collaborator

js-choi commented Oct 2, 2021

I need to integrate this old Gist that compares this proposal with @hax’s Stage-1 Extensions proposal. There are a few problems with the Gist, too.

The biggest problem is, as per the Matrix discussion from two days ago and also discussion in #8, the document focuses too much on the security use case.

I’m going to redo this document to focus more on the general frequency of bind/call and how clunky they currently are.

@js-choi js-choi added the documentation Improvements or additions to documentation label Oct 2, 2021
@js-choi
Copy link
Collaborator Author

js-choi commented Oct 2, 2021

@acutmore left a comment on the Gist, to which I’m replying here:

// The adversary’s code.
delete Set.prototype.slice;

I think this was supposed to be delete Set.prototype.has :)

Yes, the delete Set.prototype.slice is a mistake; thanks for catching it!

There is currently no way to harden trusted code against an adversary’s mutation of Function.prototype.call or Function.prototype.bind.

This is not entirely true. It can be hardened against it just relies on being able to run code before anyone else (just like capturing any global/method before it can be modified).

// at start before other code runs:
const uncurryThis = Function.bind.bind(Function.call);
  // f => Function.call.bind(f)
  // f => (…args) => f.call(…args)

// or
const {apply} = Reflect;
const uncurryThis = f => (that, …args) => apply(f, that, args);

// then
const setHas = uncurryThis(Set.prototype.has);

// later
setHas(new Set([1]), 1); // true

It is true that guarding against Function.prototype.call mutation is possible with Function.bind.bind. This is another old mistake which I’ll need to fix.
This guard requires wrapping every global method you use in an uncurried-this wrapper function, à la @ljharb’s call-bind library, which is transitively used by a ton of websites.

So the argument that I need to make is that (1) this intrinsic call-binding is just one common use case of a very commonly used pair of functions (bind/call), and (2) despite its frequency, using bind/call is currently clunky and its ergonomics should be optimized with an operator.

I do anticipate that the biggest argument against this will be “the method-wrapping is fine; just use the pipe operator |> if you want a fluent postfix expression”. I am still trying to think of ways to respond to that reasonable objection. But first I need to modify and import this Gist.

@ljharb
Copy link
Member

ljharb commented Oct 2, 2021

to me it’s really not about the postfix part, i just need a way to syntactically change the receiver of a function - it’s just that once that exists, it’s kind of silly to force the awkward ordering of .call when we could easily have natural o.a(b) ordering.

@js-choi
Copy link
Collaborator Author

js-choi commented Oct 3, 2021

i just need a way to syntactically change the receiver of a function

@ljharb: People will probably say, “Why do you need syntax to change your function’s receiver? Why not just keep using Function.call or your uncurryThis/callBind wrapper?” It’s already kind of happened in that Matrix conversation, too.

The best argument against that anti-argument that I can think of is developer ergonomics. Using Function.call/uncurryThis/callBind is weird, clunky, and obscuring. o->fn(a) or whatever is more ergonomic than Function.call(fn, o) because it’s less wordy and because the ordering resembles o.fn(a).

If it weren’t for that ergonomic difference, then it’s like what @acutmore said: everything is already possible in userland without syntax. (Possible inherent performance problems with wrapped functions notwithstanding, cf. #8.)

@ljharb
Copy link
Member

ljharb commented Oct 3, 2021

Part of it is robustness, and part of it is ergonomics. We have indisputable evidence that the current state of things is not ergonomic; nobody uses call/bind/apply when an arrow function or unsafe call suffices.

@js-choi js-choi closed this as completed in 06055f9 Oct 6, 2021
js-choi added a commit that referenced this issue Oct 6, 2021
js-choi added a commit that referenced this issue Oct 6, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

2 participants