-
Notifications
You must be signed in to change notification settings - Fork 30
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
::object.method vs object::function #18
Comments
I was thinking of forcing Meaning the bind context is always on the left and the binded method is always on the right. This would still work getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x)); but then this looks weird class Comp extends React.Component {
methodA() { /* code requires correct context */
function render() {
return <div onClick={ this::this.methodA }>
}
} edit: clarify second example |
It may be less confusing if you take out the whitespace like you did using the
I agree with this. Looking only at examples from this spec, there's no easy rule to say what the calling context is. When I first saw examples I was trying to figure out: Does it bind to the left, or to the right? Continuing my linguistic themed arguments, regularity in language makes it easier to learn, to read, and to understand, and I'm sure most people who try to learn a natural language will agree: the exceptions and irregularities are difficult to internalize. So, when I heard there was a proposal for a bind operator, I thought: sweet, no more |
I see what you are saying. I may change my mind with I like your point about native languages. My native language is ES so I always push for things that make sense from the point of view of a native ES coder. Are there use cases for preceding for instance: Promise.resolve(123).then(console::log); Is clearer than Promise.resolve(123).then(::console.log); |
I do prefer
If |
I don't like killing I was considering |
If I agree with @lukescott, As for the virtual methods/ |
@BerkeleyTrue, ok, your import {doSomethingThisWay} from "./helpers";
class Bar extends Foo {
doSomething() {
this::doSomethingThisWay();
}
} So perhaps I really don't feel comfortable using What about |
@lukescott which operator for which semantics? |
@lukescott exactly my thoughts too! I agree that it should be two separate operators, but wasn't @zenparsing, I would assume |
@zenparsing "method" meaning function on the object, and "function" meaning not on the object. So:
I'm still on the fence about If you were to see this without being told, which one does what? foo->doSomething()
foo:>doSomething()
foo::doSomething() I think in all three cases you would think doSomething is somehow apart of foo. Perhaps something like With this example: import { map, takeWhile, forEach } from "iterlib";
getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x)); Is there any reason that can't be: getPlayers()
.map(x => x.character())
.takeWhile(x => x.strength > 100)
.forEach(x => console.log(x)); If this is an object, why can't |
Just had an idea.. If the goal of import { map, takeWhile, forEach } from "iterlib";
getPlayers()
.(map, x => x.character())
.(takeWhile, x => x.strength > 100)
.(forEach, x => console.log(x)); So the syntax would be Then you just have this for the other example: Promise.resolve(123).then(console->log); The I think that satisfies all the use-cases mentioned in the proposal, while reducing the complexity (I hope!). |
@lukescott, I sort of like that, with the exception of using the comma. import { map, takeWhile, forEach } from "iterlib";
// Current ES6 way of doing it
forEach(x => console.log(x),
takeWhile(x => x.strength > 100,
map(x => x.character(),
getPlayers()
)
)
);
// Explicitly binding
forEach.call(
takeWhile.call(
map.call(
getPlayers(),
x => x.character()
),
x => x.strength > 100
),
x => console.log(X)
)
// Current proposal
getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));
/* idea from @lukescott
I like using the `.()` but using a comma in the list seems strange, it reminds me of the `(0, obj)(args)` idiom
*/
getPlayers()
.(map, x => x.character())
.(takeWhile, x => x.strength > 100)
.(forEach, x => console.log(x));
// Using different separators, they all look strange.
getPlayers()
@(map, x => x.character()) // Too much like decorator
#(takeWhile, x => x.strength > 100) // Might be confused with prototype operator
:>(forEach, x => console.log(x)); // Isn't this operator in use somewhere else?
/* Replacing the first comma with a colon seems pythonic, but seems easier to read for me probably because I use python at $WORK */
getPlayers()
.(map: x => x.character())
.(takeWhile: x => x.strength > 100)
.(forEach: x => console.log(x));
// How does it look with 0 and multiple args? Still readable, though a little bit strange for zero args.
getPlayers()
.(map: x => x.character())
.(takeWhile: x => x.strength > 100)
.(doSomething:)
.(twoArgs: 1, 2)
.(forEach: x => console.log(x));
// Using a completely new operator
getPlayers()
@>map(x => x.character())
@>takeWhile(x => x.strength > 100)
@>forEach(x => console.log(x));
// Another operator, that looks like an arrow. although, it really is very close to -> depending on the font.
getPlayers()
~>map(x => x.character()) ~>takeWhile(x => x.strength > 100)
~>forEach(x => console.log(x)); Of all these, I really like |
@Naddiseo The issue with import {foo} from "./lib"
var fooBindObj = object::foo; // doesn't matter if its ::, :>, @>, ~>, <insert new operator here> The problem This syntax forces you to call the method, so the assignment part is no longer a problem: var result = object.(foo); The comma could be fine because // Chaining
getPlayers()
.(map, x => x.character())
.(takeWhile, x => x.strength > 100)
.(forEach, x => console.log(x));
// could be thought of as
getPlayers()
.boundCall(map, x => x.character())
.boundCall(takeWhile, x => x.strength > 100)
.boundCall(forEach, x => console.log(x)); But if a colon was used, you could simply omit the object.(doThis: "foo")
object.(doThat) The fat arrow syntax also has abbreviated syntax - The |
@lukescott I see your point. So, really, this current proposal should be split into two separate ones: one that enables the fluid style interfaces for generic functions; and one that is a syntactic replacement for Regarding the comma: when you say "more like a call," what are you conceptualizing as being called? I am assuming you see some hidden "boundCall" function as being called. For me, I see that first item/name as the thing to be called, so the comma after it seems misplaced as I read that item as the first in a list, but my brain wants to interpret that first item as the function. An alternate syntax for binding to generics could be: getPlayers()
.map:(x => x.character())
.takeWhile:(x => x.strength > 100)
.forEach:(x => console.log(x)); Which doesn't look quite so strange, but you again run into the problem that |
@Naddiseo yeah, Object.prototype.boundCall = function(fn, ...args) {
return fn.call(this, args);
}; Although it isn't really a function, so it would desugar the same way: // from
getPlayers()
.(map, x => x.character())
.(takeWhile, x => x.strength > 100)
.(forEach, x => console.log(x));
// what babel does now
var _context;
(_context = (_context = (_context = getPlayers(), map).call(_context, function (x) {
return x.character();
}), takeWhile).call(_context, function (x) {
return x.strength > 100;
}), forEach).call(_context, function (x) {
return console.log(x);
});
I think we can both agree that Regarding class Foo {
handleClick() => {
// ...
}
}
var foo = {
handleClick() => {
// ...
}
} A nice bonus to that is memorization in #17 wouldn't actually be needed. |
Something occurred to me: if we replace Regarding The was ES currently binds methods to classes is one of the things I think was poorly thought through in ES6. Methods on classes should by default be bound to them; if you didn't want them bound, then use a plain function. Instead of |
Re: method binding: I would agree it would be beneficial for methods to be bound by default, but isn't it too late for that? ES6 is done. Also if they were bound by default, wouldn't that cause a 2x performance decrease in the ES5 emulated versions? Even if it were possible to change, it would be far better to leave it as is and later allow something like this: class Foo {
boundMethod() => {
// ...
}
notBoundMethod() -> {
// ...
}
} With a linter you can ensure either Re: Re: I still think Re: The above, and any features added: Overall new features should be intuitive enough that the meaning is obvious without looking at a manual. In order to do that existing syntax needs to be leveraged, whether it's part of the language being augmented, a sister language with similar syntax, or other languages widely adopted by those using the original language (like CoffeeScript, within reason). IMO, inventing new syntax, or re-purposing existing operators for other purposes, should be avoided when possible. ES6 has done this rather well. |
Re: method binding: given how ES is design by committee and tries to preserve backwards compatibility, yes it's too late. And, yes, it does look like the ES5 emulations would suffer quite a bit performance wise. Re: Re:
100% this. Unfortunately, I think it's also the biggest argument against the |
@Naddiseo
Somewhat off-topic, but avoiding the colon would leave the door open for (optionally) named parameters: function sayHello(name String) {
console.log(name, "says hello!");
}
sayHello("John");
// vs
function sayHello(fromName: name String) {
console.log(name, "says hello!");
}
sayHello("John"); // cause a linting error
sayHello(fromName: "John"); // works class GameObject {
// abbreviated so name and variable are the same
move(x: Number, y: Number) {
this._x = x;
this._y = y;
}
}
// ...
gobj.move(x: 20, y: 30) (Assuming Go-like types, see https://github.com/lukescott/es-type-hinting) |
@lukescott, yes, I know it as the scope resolution operator, I meant using
Okay, you've convinced me, that's a good point. (Is there a spec floating around for named parameters?) It might also look like it's missing a type annotation (although they're not allowed in that context)
Actually, I really like that. |
@Naddiseo There isn't a spec yet that I know of. I could start one :) The closest thing is Swift (which came from Objective-C). They do it like this: // : is used for type hinting
func sayHello(fromName name: String) {} I really like Go types, which look like: // without named parameter
func sayHello(name string) {}
// built-in types in Go are lowercased - custom types are capitalized Combining the two concepts: // with named parameter
function sayHello(fromName: name String) {}
sayHello(fromName:"John")
// shorthand named parameter:
function sayHello(name: String) {}
sayHello(name:"John") I quickly put together https://github.com/lukescott/es-type-hinting because of preferring a simple space over a colon for type hinting. It really plays better with other potential features. If there is any interest I could use some help with it. |
Hi everyone. Thanks for keeping this discussion going! I've been listening in, although I've been busy with other spec-y things recently. Leaving syntax proposals aside for the moment, the assertion brought up in this thread is that using ::a.b.c; // The second dot has special significance in this context
x::a.b.c; // But not in this context Another issue with prefix assert(::a.b.c === ::a.b.c); // Not actually the case Which will lead to the following bug: element.addEventListener("click", ::obj.method, false);
element.removeEventListener("click", ::obj.method, false); // Not the same handler!! Also, in other languages (e.g. Python, Ruby) extracted bound methods are equal in this sense. So I think we definitely have some semantic and principle-of-least-surprise issues with the prefix form. The question is, can we resolve those issues by splitting the syntax? I don't think so. Even if you used obj->x !== obj->x; Let's say we fixed that problem by returning a memoized frozen bound function (which has problems of it's own, by the way). Then we're left with a different problem: having to justify the introduction of two new operators into the language. There's a pretty heavy burden of proof for adding syntax, and I don't think we've passed the bar. As far as the other syntax proposals go (e.g. Basically, I think we have some more work to do on the method extraction side of things. One possibility would be to make this into a more minimal proposal where we drop either the prefix or the infix form for the time being. |
@zenparsing, syntax aside, my vote would be to drop the prefix form until the memoization can be sorted out, and focus on the infix form. I would favour splitting this proposal into two, and renaming the infix |
@Naddiseo memorization wouldn't be necessary with the I don't think "prefix" form should be dropped. Instead, perhaps @zenparsing could split these into two new repos? es-method-binding (prefix), es-function-chaining (infix). The prefix form problem can be solved with expanding method definitions, such |
Sorry, that's what I was meant to convey. The memoization is specifically for the prefix form.
That was how I was reading it.
👍 |
No one seems to mention Java here... In Java 8, Method References uses the Arrays.sort(rosterAsArray, Person::compareByAge); desugars into: Arrays.sort(rosterAsArray, (a, b) -> Person.compareByAge(a, b)); I feel that My opinion: it may be a good idea to use I also have some proposals about function chaining here:
|
I find the Elixir: [1, [2], 3] |> List.flatten |> Enum.map(fn x -> x * 2 end) # [2, 4, 6] LiveScript: [1 2 3] |> map (* 2) |> filter (> 3) |> fold1 (+) # 10 Suggestion for ES: getPlayers()
|> map(x => x.character())
|> takeWhile(x => x.strength > 100)
|> forEach(x => console.log(x)); This solves the |
So var objFn = object |> fn They would just get the result of fn, but could look differently. I'm not sure that a function should be called without a set of () - somewhere. That's already well established in the language. |
@dtinth Thanks for the pointer to Java 8's usage of @maxnordlund A couple of questions regarding your suggestion of
a |> b(c);
// 1. b.call(a, c);
// 2. b.call(undefined, a, c); |
@zenparsing while I take inspiration from Elixir and LiveScript, here we're talking about @lukescott and @zenparsing I don't differentiate between Again, this is a suggestion just for The This might be viable for binding method references when giving them to third party API:s, and open up the possibility for partial applied parameters using parenthesis. E.g. |
@maxnordlund The Java version of the Using
Going out on a limb, if Either way, having the bind operator work with Allowing arguments could also be useful:
|
@ssube The issue with class Foo {
// use => to cause handleEvent to always be bound to this
handleEvent(e) => {
...
}
bindEvent(trigger) {
trigger.addEventListener(this.handleEvent);
}
} This is similar to issue #16, but extends the existing fat-arrow syntax to class methods. |
Yeah, it might not be obvious that class Foo {
_bar() {
console.log("Real implementation of bar, always bound to a Foo", this instanceof Foo)
}
get bar() {
if (!this.hasOwnProperty("_bar") {
Object.defineProperty(this, "_bar", { value: this._bar.bind(this) })
}
return this._bar // Will always point to the same `_bar` for a given instance of Foo
}
} By (ab)using getters and The same trick could be inlined into the method itself, or something to that effect, if the extra |
@maxnordlund Issue #16 already has a solution for var Foo = (function() {
var Foo = function() {
this.handleEvent = this.handleEvent.bind(this);
};
Foo.prototype.handleEvent = function() {
// this will always be instance of Foo
};
return Foo;
})(); Unless I misunderstood what you were trying to do. |
@ssube We are currently disallowing super references in the prefix form. let fn = ::super.foo; // Currently a runtime error But I think we can relax this restriction. Otherwise, the semantics you are proposing for an infix |
@lukescott I understand that Java and JavaScript are totally different thing, and I like to use the analogy “ham and hamster” to explain that 😉. Nevertheless, I still think it's always a good idea to look at prior work of what has been done in other languages (especially similar ones). In my opinion, what Java and ECMAScript is accomplishing through the As for the second case, you have a good point that |
@lukescott That global memoization is pretty ugly. It sounds like you're suggesting that
@zenparsing What was the logic behind disallowing that? I'm not sure binding |
@ssube Regarding disallowing super references, I think it was just an oversight on my part. Thanks for your interest! C++ uses |
@zenparsing C++ and Java behave very closely for While Java allows instance methods with When evaluating
I imagine that could be ambiguous in a few situations and might run afoul of JS' scoping, but IMHO it's the closest to how Java and C# handle this sort of thing. Apologies if this overloading has already been suggested. |
@ssube a hierarchy was actually part of the original proposal 2 years ago https://esdiscuss.org/topic/scoped-binding-of-a-method-to-an-object and https://esdiscuss.org/topic/protocol-library-as-alternative-to-refinements-russell-leggett |
I agree that object.method.bind(object)
fn.call(object)
There's also a proposal to change these to chain the first argument instead of fn.call(null, object) In this case, I believe the ones with a
|
@justinbmeyer At a high level, the options are:
For 2, my feeling is that the only options that have a chance are the following: // Method Extraction (kinda similar to Java syntax)
obj::method;
// Pipelining Option 1 (Similar to other languages but UGLY)
obj |> fn();
// Pipelining Option 2 (Looks nicer but goes against precedent)
obj->fn(); I would love to break pipelining out into it's own operator, but I'm not thrilled about the syntactic options. |
Thanks @zenparsing. I think they should be split. I'll go with
Would be quite understandable to beginners who have seen // A mapping method generator
// @param {String} name A property name
// @return {function()} A function that maps each item in `this` to its property `name`
var mapping = function(name){
return function(){
return this.[map](function(item){
return item[name]
})
}
};
getPlayers().[ mapping("name") ]() |
Do the following allows for sub-expressions that evaluate to the method to be called? obj->fn();
obj |> fn(); I don't think so, at least not un-ambiguously. What should the following become? obj -> fn()()
// option 1
fn.call(obj)()
// option 2
fn().call(obj) I assume option 1. But this prevents using functions that could return functions like in my previous example. |
@justinbmeyer Forgot to mention: if they are going to be split, then I think the "pipelining" operator needs to inject the left-hand side into the first argument position, instead of the Reasons being:
|
I do like Either way I definitely think the two should be split. |
They could, depending on how you specify the operator precedence. If obj |> X.y();
// Maybe??
X.y(obj); But it's pretty ugly. Other than that, you could allow parens: obj |> (something.arbitrary)();
// or:
obj->(something.arbitrary)();
// To:
(something.arbitrary)(obj); |
@lukescott I think the following:
would still be useful. Instead of: import {function} from "functions";
import {object} from "objects";
var methodOnObject = method.bind(object) one could do: import {function} from "functions";
import {object} from "objects";
var methodOnObject = object.[function] |
Some difficulties that might come up with these options...
So I think dot-anything may be problematic because dot is so tightly associated with normal property lookup. And square brackets as well. So we're trying to mix two things that "mean" property access to make something that isn't actually property access ; )
This may be problematic because it looks so much like a paren expression with a comma expression inside. |
@justinbmeyer I suppose |
Maybe this could be applied to the other use-case as well:
Although that might bit odd :) I think I might actually like I know the |
I like the arrow syntax best for fs.readFile 'file.txt', 'utf8', (err, data) ->
... |
Great ideas in here, but keeping the syntax as-is for now. |
How about You want a shorter version of |
The two different uses of
::
is a bit confusing. For example someone could write:The difference between
::object.method
andobject::function
seems a little too "magical".Instead of
::object.method
how aboutobject->method
instead. It would more naturally complement the fat arrow syntax. Then::
is only used forobject::function
.The text was updated successfully, but these errors were encountered: