Closed maxnordlund closed 8 years ago
May I suggest?
foo::bar:::(a, b, ... rest) // Three colons! Not just two!
The (foo, a, b, ...rest)::bar
suggestion can accidentally be conflated with an ugly feature (bug?) with ECMAScript. Often times, the last expression in a comma-delimited set of expressions is considered the final computed value. So, for example, the following expression:
10, 20, 'foo', 'bar', 10 === 20, 3
Would yield the value 3
, because 3
happens to be the last expression, in the above expression, per section 12.15.3 of ECMA-262. The only exception seems to be when when supplying a set of parameters. But regardless, if we were to have the following expression:
(10, 20, 'foo', 'bar', 10 === 20, 3, foo)(10)
Not only would 10
, 20
, 'foo'
, 'bar'
, 10 === 20
, 3
, and foo
be evaluated, but also foo(10)
. And hence why the (foo, a, b, ...rest)::bar
suggestion can be conflated for something else, such as (foo, a, b, bar)::baz
, where JavaScript will evaluate foo
, a
, b
, and bar
, and then evaluate bar::baz
.
Of course, there's nothing stopping us from actually suggesting that (foo, a, b, ...rest)::bar
be parsed and compiled as bar.bind(foo, a, b, ...rest)
, but bear in mind that there are programmers out there that use the comma-delimited expression as a language feature, and use it heavily.
From the looks of the standard, though, the colon (:
) is only used for separating the key from the value in an object expression. Outside of an object, however, the colon isn't used for anything (or at least, not that I'm aware of), hence why, I guess, the double colon is used as the shorthand for bind
. A third colon, perhaps, is available to our imagination. And I'm suggesting that we use the triple colon for partial application of functions.
Edit: some clarification
Bump for email notification
I made some changes to my earlier comment.
Maybe I should also clarify what my suggested foo::bar:::(a, b, ... rest)
syntax is.
foo::bar:::(<parameters list>)
should compile to bar.bind(foo, <parameters list>)
.
I like your idea, I just don’t think that theres a big enough difference between :: and ::: - it’s going to hinder readability. Similar to how Swift used to use .. and … for its range operators, before turning it into >.. and .. I think.
On 26 May 2015, at 15:31, Salehen Shovon Rahman notifications@github.com wrote:
Maybe I should also clarify what my suggested foo::bar:::(a, b, ... rest) syntax is.
foo::bar:::(
) should compile to bar.bind(foo, ). — Reply to this email directly or view it on GitHub https://github.com/zenparsing/es-function-bind/issues/15#issuecomment-105544924.
I like your idea, I just don’t think that theres a big enough difference between :: and ::: - it’s going to hinder readability.
True that.
How about the following?
foo::bar::>([parameters list])
Or better yet:
foo::bar:>([parameters list])
IMO this is way out of scope and no syntax is necessary.
I'm not sure how this is out of scope. This is about adding a syntax literal for bind, and partials are part of that.
Sent from my iPhone
On 26 May 2015, at 15:45, Domenic Denicola notifications@github.com wrote:
IMO this is way out of scope and no syntax is necessary.
— Reply to this email directly or view it on GitHub.
Hi @maxnordlund , @nathggns . Thanks for the suggestion. From my point of view, the purpose of this proposal is to introduce very surgical syntactic sugar for two patterns that are currently more cumbersome than they need to be, or should be.
We don't need to completely cover Function.prototype.bind with new syntax. And I don't think we should attempt to. In my opinion, Function.prototype.bind works well enough for the more complex case of curried arguments and won't benefit greatly from sugar.
I'll post some more on this a bit later...
I think what bugs me is you have to pass the thisArg when using bind, even if you don't want to change it. So far something like obj.adder.bind(obj, 1, 2)
or something, it gets fairly tedious to have to do that often (which you might if you use fp namespaced under an object that refers to this
). That's why I would support a syntactical version of it. I also feel it's something people would expect if you can do ::console.log
.
Quick question, under the current proposal, what would ::console.log('Hello, World')
do? That would be what I would expect to do partial application.
@nathggns, that usage of bind also annoys me. My current use case is JSX templates where I'm binding callbacks to the controller, it can get pretty ugly if the object I want to bind to is pretty deep:
let vdom = <a onclick={ thing.otherthing.ctrl.onClick.bind(thing.otherthing.ctrl, someVariable) }></a>;
However, I do understand why this usage is out of scope for the "::" operator; it's not really a lazy operator like partial application is. Off the top of my head, I think having another operator, say "->" (read as "binds to") could be used: thing->method(arg1)
read as "thing binds to method with arg1"
I just see that the usage for -> and :: would be so so similar. Especially if was is a prefix and one is a suffix. It would be annoying to have to replace :: with -> every time I also want to bind an argument as well as the thisArg. I just see no reason to not support ::someObject.someMethod(a, b, c)
as a replacement for someObject.someMethod.bind(someObject, a, b, c)
, unless that would already do something else under the current proposal.
That indeed would already do something else under the current proposal. It would call ::someObject.someMethod with arguments a, b, and c. See the readme for more details.
Isn't that a bit... pointless? someObject.someMethod(a, b, c)
does the exact same thing as someObject.someMethod.bind(someObject)(a, b, c)
...
@nathggns, yes, that usage is the same, but when you currently use bind
you don't want the function to execute immediately.
So I'm confused. What's the purpose of ::someObject.someMethod(a, b, c)
being transformed to someObject.someMethod.bind(someObject)(a, b, c)
under the current proposal instead of someObject.someMethod.bind(someObject, a, b, c)
.
For generic methods I think. If someMethod
isn't actually a method of someObject, you can still bind the this argument.
::console.log(1);
console::log(1);
Array::ThisMethodIsntOnArray(1,2,3)
Transpiles->
console.log.call(console, 1);
log.call(console, 1);
ThisMethodIsntOnArray.call(Array, 1, 2, 3);
@nathggns, let me present to you _=>
the "unary boundcurry operator".
Usage:
instead of a.b.bind(a, c)
use _=>a.b(c)
According to this jsperf it's faster than using bind for both construction and calling.
Exactly, the ::console.log(1) example is a pointless conversion, it does exactly the same thing as console.log(1). Why not make ::console.log(1) mean console.log.bind(console, 1)?
Also _=> doesn't pass on extra arguments like bind would
@nathggns, I think issue #18 covers that particular ambiguity to the unary operator; and I tend to agree with that use case. ::obj.method()
vs ::obj.function()
does seem rather magical and confusing.
Re: _=>
yes, it was meant to be a bit tongue-in-cheek. It works for simple cases though, and is shorter.
There just doesn't seem to be a substantial reason for not supporting ::console.log(1)
transforming to console.log.bind(console, 1)
It's extremely confusing for ::console.log
to return a function, but calling that function with the call syntax ::console.log(1)
does not call it but instead somehow gives a new function. Other confusing things under your proposal:
::console.log(1) // nothing logged??
const bound = ::console.log;
bound(1); // something logged!??!
(::console.log)(1) // something logged!?!
foo::console.log(1) // console.log.call(foo, 1)
console::console.log(1) // console.log.call(console, 1)
::console.log(1) // console.log.bind(console, 1) ???
In other words, using call syntax ()
for anything besides calling is ... not good.
I'll try to address some of your concerns. First of all, foo::bar(a, b)::baz(...c)
currently becomes baz.apply(bar.call(a, b), c)
, and should probably stay as I talked about above. This would allow things like document.querySelectorAll("p")::Array.prototype.map((p) => p.textContent)
, and this is something worth making easy I think.
But I also find having an easy way of using curried arguments/parameters is very useful, such as let click = (target, "click", new MouseEvent("click"))::EventTarget.prototype.dispatchEvent
. For me this feels clunkier then target::dispatchEvent("click", ...)
, but as @domenic says, it might be even weirder if it didn't make a call.
I kinda like the foo::bar:::(a, b)
from @shovon but would rather let it be just a double colon, foo::bar::(a, b)
. As for those who like the comma operator, you could still use temporary variable(s). I would be ok with having ::(...)
be reserved for argument binding. This does add a new case, bar::(a, b)
, which should become bar.bind(undefined, a, b)
.
@domenic while I agree that this proposal is confusing, it's also somewhat confusing that ::
is only partially equivalent to bind
, since it doesn't support currying.
Let's not call it bind then so people stop asking for partial application.
I kinda like the foo::bar:::(a, b) from @shovon but would rather let it be just a double colon, foo::bar::(a, b). As for those who like the comma operator, you could still use temporary variable(s). I would be ok with having ::(...) be reserved for argument binding. This does add a new case, bar::(a, b), which should become bar.bind(undefined, a, b).
I like this! Any drawbacks to console::log::('stuff')
becoming console.log.bind(console, 'stuff')
? @domenic @zenparsing
I would expect ("stuff") to behave the same as "stuff" in a value context.
Oh right. It exudes the “extra parens” vibe..
From an outsiders perspective I'd find the ::
syntax incredibly helpful if it supporting currying in some way. With it, I'd have a whole variety of use cases where I'd be very happy to use it.
For people doing non-class based programming this becomes a sort of default for a lot of use cases.
let x = 0;
const add = ::(x = x + 1) // is this too crazy?
add()
const log = ::console.log('Debug: ')
I'd be ok with some modification of the way to do it to be clear though.
I think the partial application (not currying---learn your terms, people) proposal should be a separate one outside the scope of this repo.
Pre-first coffee... Not returning a function so no it's not currying, it's partial application. In effect, I think we understand what we're talking about, but thanks for the reminder.
how about making function bind syntax (foo :: arg1, arg2) +> bar
to support partially applied parameters?
ex1)
foo +> bar
= bar.bind(foo)
ex2)
() +> console.log
= console.log.bind(console)
ex3)
(arg1, arg2) +> foo.baz
= (foo :: arg1, arg2) +> foo.baz
= foo.baz.bind(foo, arg1, arg2)
Partial application of functions seems like it's really tangential to binding - I'm not sure we should continue the trend of conflating them (e.g. just because Function#bind
does it doesn't make it great)
At least, I've seen a bunch of cases where I want to partially apply a function, but don't care about binding a particular this
.
Can we perhaps come up with a syntax that is independent of bind
/::
? Maybe I'm just being a bit crazy, though :P
Well, one of the reason I would like some syntax support is precisely because of that. Currently you cannot partially apply a function without also binding it's this value, save for returning a function closure. See #18 for more discussion and syntax variants.
I personally like the pipeline from Elixir and/or LiveScript
getPlayers()
|> map(x => x.character())
|> takeWhile(x => x.strength > 100)
|> forEach(x => console.log(x));
+1 to @nevir on separating this
binding from partial application. If we could figure out some sort of syntax that would complement ::
, that'd be +1000.
::foo.bar/* Something? */(arg1, arg2)
I don't want to insert another goofy symbol to the mix, so I won't. But if we can just come up with something to do partial application in this way, it should flow nicely with how ::
currently stands (and I think is awesome, btw)
I haven't read this entire thread but is there anyway we could have :::foo.bar(1, 2) meaning foo.bar.bind(foo, 1, 2)? Notice the three ::: instead of two to signify partial application.
Sent from my iPhone
On 14 Aug 2015, at 23:16, MikeMcElroy notifications@github.com wrote:
+1 to @nevir on separating this binding from partial application. If we could figure out some sort of syntax that would complement ::, that'd be +1000.
::foo.bar/* Something? */(arg1, arg2)
I don't want to insert another goofy symbol to the mix, so I won't. But if we can just come up with something to do partial application in this way, it should flow nicely with how :: currently stands (and I think is awesome, btw)
— Reply to this email directly or view it on GitHub.
:::
can be very easily confused with ::
, IMHO.
:::
can be very easily confused with::
, IMHO.
When I initially proposed :::
, @nathggns pointed out how it's too similar to ::
. Soon after, I suggested the :>
syntax, which is far easier to distinguish.
@shovon I have no real problem with :>
, not that my vote has anything to do with it. Arguing over the symbol to use for this is not something I care to do, just so long as we get a nice easy-to-use symbol for it.
Another big +1 on @nevir's comment.
I had some syntax/implementation thoughts on this after reading Jeremy Fairbank's blog post; re-posting my comment here in case there are helpful ideas, though with a warning that I haven't re-read it to make sure the code examples are correct:
Re. the partial application syntax, what about separating it from the context binding entirely? Like the following:
// syntax based on the ES2015 spread operator: add2 = add(2, ...); debug = ::console.log('DEBUG:', ...); // or, syntax based on the ES2016 bind operator: add2 = add::(2); debug = ::console.log::('DEBUG:');
This could translate to something like the following (using ES2015 for brevity):
add2 = (add => { return function partial(...args) { add.call(this, 2, ...args); }; }(add));
Note that (1) an IIFE is used to capture the 'add' binding at the time of partial application in case it's re-bound later in the code (seems like the right thing to do), and (2) the context variable is deliberately not bound until the partial function is called, so we can do things like this [contrived example]:
// spread-like syntax: const concat4 = Array.prototype.concat(4, ...); const concat45 = concat4(5, ...); console.log([1, 2, 3]::concat45(6)); // [1, 2, 3, 4, 5, 6] // bind-like syntax: const concat4 = Array.prototype.concat::(4); const concat45 = concat4::(5); console.log([1, 2, 3]::concat45(6)); // [1, 2, 3, 4, 5, 6]
I've gotta say, I'm quite partial to the spread-like syntax... (I'll see myself out =P)
+1 @msegado's spread-like syntax. Declarative and complementary to ::
.
add2 = add(2, ...);
@msegado oh, snap! The spread operator wins!
@maxnordlund thoughts?
@MikeMcElroy @shovon Thanks!
By the way, we could hypothetically allow application of final parameters with this syntax too...
longDelay = window.setTimeout(..., 10000);
...though I'm not sure how well that fits with the language design as a whole; a normal rest parameter can only come at the end of a function's parameter list (I assume for reasons of optimizability?), and this is kind of like a function definition.
+1 for the spread operator. Though this should really be defined in a different spec.
@RangerMauve Thanks =) And I agree, it would probably make sense to remove partial application from this spec and advance it separately. @maxnordlund @nathggns, thoughts? (I'm assuming @zenparsing is already leaning toward this given his earlier comment).
Yeah, this does look rather neat. To self promote a bit, it also would complement my suggestion for a application only syntax. But as mentioned by others, both here and in #18, we should have this discussion somewhere separate.
Closing this as out-of-scope.
The proposed syntax doesn't cover the other use of
bind
, namely partially application of function parameters. I've been thinking how this could be solved, and come up with this:Either the current
call
syntax will be repurposed as extra arguments for bind:However this makes the suggested common use awkward,
The other way I found is to use parenthesis on the left hand side to indicate more parameters, which menas that the common case of only supplying a
this
value would be seen as having implicit parenthesis.This case would be more backwards compatible then the above, but might be more confusing to read. It does however clearly distinguish between bound and call parameters.
I came upon this when writing a curry decorator and ended up using
Function.prototype.bind.bind(bar, foo)(...arguments)
which babel compiles toFunction.prototype.bind.bind(bar, foo).apply(undefined, arguments)
.What do you think? Anything I missed?
Edit Change currying -> partial application per https://github.com/zenparsing/es-function-bind/issues/15#issuecomment-110892162