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

Pipeline + Bind strawman #37

Open
bterlson opened this issue Jul 28, 2016 · 22 comments
Open

Pipeline + Bind strawman #37

bterlson opened this issue Jul 28, 2016 · 22 comments

Comments

@bterlson
Copy link
Member

I am considering an addition to the bind operator as proposed here to also satisfy the pipeline operator use cases. Essentially we allow a single ? in an arguments list to signify the parameter position to pass the LHS to, and if it's absent, use this. For example:

foo::bar(); // as normal
foo::bar(?); // approximately bar(foo)
foo::bar(1, ?); // approximately bar(1, foo)

Any thoughts on this?

@bergus
Copy link

bergus commented Jul 28, 2016

I'm not sure how that is supposed to work. Is bar.bind(foo)(?) still the same as foo::bar(?)?

Where else would these special arguments lists be applicable? Only after bind expressions? I'd rather avoid that. If this is really needed (I believe not, it doesn't even simplify the expression), it should use a different operator than ::.

@bterlson
Copy link
Member Author

My proposal is that this is not bound when using a placeholder. Syntactically only valid on the RHS of the bind operator.

Can you explain why allowing ? on the RHS of a bind operator is worth avoiding more than having two completely separate operators, one with the same placeholder syntax I am proposing we add and one without?

@deontologician
Copy link

I'm concerned this makes it look very perl-ish. I don't think it's bad personally, but I think it might be hard for people to swallow both :: and ? at once (and therefore lower the chances of this moving forward)

:: by itself is pretty simple to explain and use as it is now, even though it doesn't cover the pipelining case.

@bterlson
Copy link
Member Author

@deontologician I don't think there is much concern about "looking perl-ish" :-P By "hard for people to swallow" do you mean for committee members to agree to more syntax or for users to accept?

@bergus
Copy link

bergus commented Jul 28, 2016

I guess my worry is mostly about adding yet another meaning to ::. We already have two distinct ones, will a third make it better? It's already been proposed multiple times that extraction and binding get two different operators (for example object.:extractMethod and object:::bindFunction) which would make them better to distinguish and easier to explain. It was okay to me to use the same :: operator as it always means "binding object as this". Your new proposal doesn't share that common ground any more.

Also, if we introduce special arguments lists with placeholders, shouldn't we look for a more general use case, one that might be better composable with other languge features? Could such a placeholder thing simplify callbacks? Maybe an operator for partial application might solve this whole problem better?

I have not given it much thought specifically, but what about

parseInt(?, 10)      x => parseInt(x, 10)
bar(??)              function() { return bar(this) }
bar(1, ??, 2, ?, ?)  function(x, y) { return bar(1, this, 2, x, y) }

Those might be combined with ::, but also be useful somewhere else.

@deontologician
Copy link

do you mean for committee members to agree to more syntax or for users to accept?

Well users will just ignore it if it's not useful, so probably committee members. My impression from a couple of comments here is that the new syntax is already "pushing it". But that's just my impression watching from the sidelines.

#24 (comment)
#30 (comment)

@bterlson
Copy link
Member Author

bterlson commented Jul 28, 2016

@bergus The way I see it this isn't adding another form - :: always takes the LHS and passes it to the RHS. How it passes the LHS depends on the presence of the placeholders.

IMO: There is no syntactic space for both pipeline operator and bind operator. There is no syntactic space to use two operators for extraction and pipelining. There is no agreement between this vs. first-param pipelining. Unifying all of these into one syntax, assuming its rational, will drastically increase the chances of it being accepted.

Also, if we introduce special arguments lists with placeholders, shouldn't we look for a more general use case, one that might be better composable with other languge features? Could such a placeholder thing simplify callbacks? Maybe an operator for partial application might solve this whole problem better?

We should /always/ look for a more general use case. That's how I arrived at the OP proposal to begin with :) As to your questions, I don't know, what do you think?

@deontologician I see. I don't think size of syntax is the blocking issue for TC39 (and the addition of ? is minor syntactically IMO). Anyway, let's not worry about that aspect for now, I can manage the committee side of things :-P

@bergus
Copy link

bergus commented Jul 28, 2016

@bterlson

The way I see it this isn't adding another form - :: always takes the LHS and passes it to the RHS.

Meh, there is not necessarily a RHS that anything is passed into. A BindExpression doesn't need to part of a CallExpression, it can stand on its own. As currently specced, it's really a bind followed by a call. This is what your proposal would need to change, a BindExpression that is part of PlaceholderCallExpression would need to be evaluated in a completly different way.

[Regarding general use case] I don't know, what do you think?

I'd love to see a partial application operator :-) Admittedly, the one I drafted above would be weird to compose with the bind operator: foo :: (bar(??)) ()bar(foo) so that would either need some special rules (less generality :-/) or a different concept.
The problem of combining anything with the bind operator is that one would need to alter the callee, not just the call.

@azz
Copy link

azz commented Jul 28, 2016

Personally not a fan of ? as a placeholder symbol. ? implies a query, or (in TypeScript) optionality.

I think a separate proposal to re-arg/partially apply would be more sensible. Something like:

function foo(a, b, c) { if (a) return b-c }
function bar(f) { return f(3, 4) }
bar(foo@(true, @2, @1)); // --> (4-3) = 1

And use @... to signify variadic args.

@calebmer
Copy link

Another consideration (as long as we’re talking about generality) might be the ability to bind arguments. As the bind function not only takes a this argument but other arguments to be applied as well. If there’s a good way to express both argument binding and pipelining in the same syntax, that may be a better sell?

@bergus
Copy link

bergus commented Jul 29, 2016

@calebmer We haven't yet used the function::(argument) syntax. That could stand for partial application without this binding, so that you can use it together with the object::function operator to fully simulate the bind method (object::function::(arguments)function.bind(object, arguments)), but that does look weird and still doesn't help a lot with pipelining as it doesn't actually call the function without an extra () call.

@bterlson
Copy link
Member Author

bterlson commented Aug 1, 2016

Some good stuff to think about here, thanks!

OP is definitely not very general by necessity - the placeholders are in the argument list is actually part of the bind operator (it's telling the operator what to do with the LHS). In cases without an LHS, placeholders would likely be an error in this strawman.

I am attracted to the idea of a bind operator that composes well with pipelining, but I'm not sure how to do it - I'd still want some control over where my LHS is getting passed (as this or as a formal), and I haven't seen a reasonable syntax for bind yet (one that lets you bind this and any parameter).

Can anyone share prior art on curry/partial application operators?

@divmain
Copy link

divmain commented Aug 1, 2016

It strikes me that new syntax may not really be necessary for this, when a helper function could provide the same functionality:

const $ = (fn, ...args) => {
  return function () {
    args = args.map(arg => arg === $ ? this : arg);
    return fn.apply(null, args)
  };
}

Consumed as

foo::$(bar, 1, $)
// vs
foo::bar(1, ?)

It's not quite as clean as the placeholder proposed in this issue, but pretty close.

@bterlson
Copy link
Member Author

bterlson commented Aug 1, 2016

@divmain how are you giving special meaning to $ here? foo::$(bar, 1, $) will just pass the value of $ to $, and you can't tell what the caller has called a thing it passed to you (so the check on line 3 won't succeed).

@bergus
Copy link

bergus commented Aug 1, 2016

@bterlson It doesn't give a special meaning to the $ identifier, it does give a special meaning to the $ value. Basically the same approach that underscore/lodash use for _ that you can use as a placeholder in _.partial and friends. Of course it's not as clean as a language-level keyword, but works quite well and is easily customisable.

@bterlson
Copy link
Member Author

bterlson commented Aug 1, 2016

I see.

@Alxandr
Copy link

Alxandr commented Aug 2, 2016

@divmain you have a slight error in your code, at least with regards to how :: works in babel today.

const $ = function (fn, ...args) {
  args = args.map(arg => arg === $ ? this : arg);
  return fn.apply(null, args)
}

function foo(...args) {
  console.log(args);
}

const three = 3;
three::$(foo, 1, 2, $, 4);

In your case you return a function from $ that never gets invoked.

@divmain
Copy link

divmain commented Aug 2, 2016

You're right, of course @Alxandr. Thanks! Serves me right for commenting without testing. In any case, it seems that the bind operator alone, without the proposed parameter substitution syntax, could serve this purpose rather well.

@Alxandr
Copy link

Alxandr commented Aug 2, 2016

What I don't like about this though is that you have to do spread operator and array mapping to make it work. It's not horrible, but I would prefer language support.

@divmain
Copy link

divmain commented Aug 2, 2016

That's fair, although the proposed syntax change would de-sugar to something similar (with a Babel transform) until support landed natively. If you want to avoid the spread operator or anything ES6, it could be re-written as:

var $ = function (fn) {
  var args = Array.prototype.slice.call(arguments, 1).map(function (arg) {
    return arg === $ ? this : arg;
  });
  return fn.apply(null, args);
};

I can understand why new syntax is attractive. But a few questions come to mind:

  • Does it meet the threshold of "that much better" to necessitate introduction of new syntax?
  • Will it make the :: bind operator a harder sell?
  • Is the proposed syntax flexible enough to accommodate diverse use-cases, or does it mostly allow you to accomplish a handful of things?

To be clear, I'm less of an opponent to the proposed placeholder syntax, and more a strong proponent of :: in general.

@Alxandr
Copy link

Alxandr commented Aug 3, 2016

I didn't say I preferred no spread because I don't want es6, but because you're forced to allocate a new array of all the arguments, that you then have to sift through for every call, instead of at "compile time".

@bakkot
Copy link

bakkot commented Aug 20, 2016

Would this allow passing the LHS multiple times, a la x::y(?, ?)? I'd expect it to, but in that case I'd also expect to be able to pass the LHS as this as well as a parameter, which would need some further syntax.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants