Arrow functions as methods

# Irakli Gozalishvili (13 years ago)

I just wanted to bring up IMO important limitation of arrow functions. They do solve unbound this issues when used as a callback, but they also introduce unfortunate limitation. Arrow functions can not really be used as methods. For example it was great that in JS one could write a function that could be used as a method or as function:

function map(f) { return this.reduce((result, value) => result.concat([ f(value) ]), []) }

// Use as method foo.map = map; foo.map((x) => x + 1)

// Use as function map.call(bar, (x) => x + 1)

I would say still is unfortunate that such reusable functions had do be called via special .call but never the less code sharing was simple to do.

Now arrow functions as they stand today can not be used in that manner as this refers to the outer scope this for a good reason. But it still would be nice to allow reusing them as methods, in fact they could be even solve current map.call(…). What I'd like to propose is borrow successful idea from other languages and make:

foo.map((x) => x + 1)

be a sugar for

map(foo, (x) => x + 1)

If map is an arrow function

That would make (arrow) functions a lot more composable:

var map = (list, f) => list.reduce((result, value) => result.concat([ f(value) ]), [])

List.prototype.map = map

List().map((x) => x + 1)

or

map(List(), (x) => x + 1)

or

map([ 1, 2, 3 ], (x) => x + 1)

I think this would make a good synergy of OO and functional styles in JS

-- Irakli Gozalishvili Web: www.jeditoolkit.com

# Irakli Gozalishvili (13 years ago)

One more thing if some reason one want's to call arrow method without passing an owner it's still easy:

(List.prototype.map)([ 1, 2, 3 ], (x) => x + 1)

-- Irakli Gozalishvili Web: www.jeditoolkit.com

# Claus Reinke (13 years ago)

function map(f) { return this.reduce((result, value) => result.concat([ f(value) ]), []) } .. var map = (list, f) => list.reduce((result, value) => result.concat([ f(value) ]), [])

Not sure I got your motivation, but would this help?

function fn(f) { return f(this) } // provide 'this' as explicit arg

let map = it => f =>
  it.list.reduce((result, value) => result.concat([ f(value) ]), [])

let obj = { map: fn(map), list: [1,2,3] }; // wrap map as method

obj.map(x => x+1);
map( {list:[1,2,3]} )(x => x+1);

Claus

# Irakli Gozalishvili (13 years ago)

It is true that on could wrap arrow function into vintage one in order to reuse it as a method, but that is a poor mans workaround. As of motivation, all I really want is to have a synergy of different paradigms that are in the language.

What I really mean by that is, if one writes in a functional style:

var map = (list, f) => reduce(list, (result, value) => result.concat([ f(value) ]), [])

That should be reusable in OO code without any changes or wrappers:

List.prototype.map = map List().map((x) => x + 1)

Also this complements another idea that I have posted a while ago: esdiscuss/2012-June/023657

Where I proposed to make private names functions:

var method = Name() method(object, arg) // is just a sugar for objectmethod

This will enable people to write code in OO or functional style and make it usable in other style:

var map = Name(); List.prototype[map] = (list, f) => reduce(list, (result, value) => result.concat([ f(value) ]), [])

Which one will be able to us in a functional style

map(list, f)

or in OO

listmap

Also even if suggested changes to private names won't take of, it's still can be implemented as a library: Gozala/method

There for a way to author functions that also can be used as methods is relevant. Thin arrows would probably also solve this problem.

-- Irakli Gozalishvili Web: www.jeditoolkit.com

# Irakli Gozalishvili (13 years ago)

I think more general problem with arrow functions is that they can not capture object on which method call happened:

object.foo(bar)

If object.foo is an arrow function it has no way of knowing weather it was invoked as a function or a method. Neither it get's reference to an object. This makes composition between functional & OO styles components hard.

Passing owner object of the arrow method as first argument solves this problem, by making

object.foo(bar)

just a sugar for

var foo = object.foo; foo(object, bar);

-- Irakli Gozalishvili Web: www.jeditoolkit.com

# Gordon Brander (13 years ago)

On Jul 11, 2012, at 10:41 AM, Irakli Gozalishvili wrote:

If object.foo is an arrow function it has no way of knowing weather it was invoked as a function or a method. Neither it get's reference to an object. This makes composition between functional & OO styles components hard.

On Wednesday, 2012-07-11 at 10:23 , Irakli Gozalishvili wrote:

It is true that on could wrap arrow function into vintage one in order to reuse it as a method, but that is a poor mans workaround. As of motivation, all I really want is to have a synergy of different paradigms that are in the language.

What I really mean by that is, if one writes in a functional style: That should be reusable in OO code without any changes or wrappers:

I agree. In general, having compatible semantics allows you to manipulate code programmatically instead of writing wrapper code by hand. The more inter-compatible language concepts are, the less mechanical busy work you have to write.

Methods and functions are both "callable bundles of procedural logic", so having 2 incompatible semantics for them seems silly to me.

There is also precedent for this approach: Python, and I think Lua, do methods this way. It cleverly side-steps the need to have a different semantic for functions and methods.

Best,

# Brendan Eich (13 years ago)

Irakli Gozalishvili wrote:

I think more general problem with arrow functions is that they can not capture object on which method call happened:

As I tweeted, this is true of any function that ignores |this| or binds it. Nothing new with arrows except the convenience.

We've been around that block and will go again. If we want convenient unbound-this function syntax, -> stands ready. I proposed it along with => originally but it was cut by TC39 during our March meeting in order

to achieve consensus on =>.

object.foo(bar)

If object.foo is an arrow function it has no way of knowing weather it was invoked as a function or a method. Neither it get's reference to an object. This makes composition between functional & OO styles components hard.

No, I don't think so. The ability of a function to ignore any parameter, including |this|, may frustrate composition, but the contract of an API includes mandatory parameterization of any funargs.

People deal with this pretty well in practice, usually by writing small-world codebases and wrapping anything unknown that needs contract-enforcement along these lines.

Passing owner object of the arrow method as first argument solves this problem, by making

object.foo(bar)

just a sugar for

var foo = object.foo; foo(object, bar);

This completely breaks functional abstraction, of course.

f(a)

and

o.m(a)

in JS should never pass more than one actual parameter to the callee, however it was declared or expressed.

# Irakli Gozalishvili (13 years ago)

We've been around that block and will go again. If we want convenient unbound-this function syntax, -> stands ready. I proposed it along with => originally but it was cut by TC39 during our March meeting in order to achieve consensus on =>.

Yeah I'm aware of that, but problem as I see is that new fat arrow functions can not fully replace existing functions. In combination with thin arrows they would but since they have being cut ended up with a dependency on an old style function in order to wrap arrows to make them usable as methods. I consider it's a failure.

object.foo(bar)

If object.foo is an arrow function it has no way of knowing weather it was invoked as a function or a method. Neither it get's reference to an object. This makes composition between functional & OO styles components hard.

No, I don't think so. The ability of a function to ignore any parameter, including |this|, may frustrate composition, but the contract of an API includes mandatory parameterization of any funargs.

People deal with this pretty well in practice, usually by writing small-world codebases and wrapping anything unknown that needs contract-enforcement along these lines.

Well but as pointed out above you need an old style functions to make arrows usable as methods, I think such dependency is very unfortunate.

Passing owner object of the arrow method as first argument solves this problem, by making

object.foo(bar)

just a sugar for

var foo = object.foo; foo(object, bar);

This completely breaks functional abstraction, of course.

f(a)

and

o.m(a)

in JS should never pass more than one actual parameter to the callee, however it was declared or expressed.

Unfortunately I still do not understand why is that a problem, specially since Claus's wrapper can be used to achieve that. It's just I think it makes it a much better default.

# Brendan Eich (13 years ago)

Irakli Gozalishvili wrote:

We've been around that block and will go again. If we want convenient unbound-this function syntax, -> stands ready. I proposed it along with => originally but it was cut by TC39 during our March meeting in order to achieve consensus on =>.

Yeah I'm aware of that, but problem as I see is that new fat arrow functions can not fully replace existing functions.

That is not a problem, or rather: not a problem with fat arrows.

In combination with thin arrows they would but since they have being cut ended up with a dependency on an old style function in order to wrap arrows to make them usable as methods. I consider it's a failure.

What "it" is a failure?

Saying you can't get lunch too when you get a good free breakfast and therefore breakfast was a failure is just wrong. We got short function syntax for by far the most common forms: methods in object literals, and (with =>) functions that bind outer |this| or do not use |this| at all.

That's winning, it is not a failure.

People deal with this pretty well in practice, usually by writing

small-world codebases and wrapping anything unknown that needs contract-enforcement along these lines.

Well but as pointed out above you need an old style functions to make arrows usable as methods,

No, not "as methods" as commonly expressed in object literals. Those get other shorthand.

I think such dependency is very unfortunate.

You're using a particular functional pattern, it's neither that common a case nor the end of the world to use long-hand function.

Yes, -> would help. That's the thing to write, not "[=> is] a failure".

Passing owner object of the arrow method as first argument solves this problem, by making

object.foo(bar)

just a sugar for

var foo = object.foo; foo(object, bar);

This completely breaks functional abstraction, of course.

f(a)

and

o.m(a)

in JS should never pass more than one actual parameter to the callee, however it was declared or expressed.

Unfortunately I still do not understand why is that a problem,

Why what is a problem? Splitting call expression evaluation semantincs at runtime based on how the callee was expressed?

specially since Claus's wrapper can be used to achieve that.

People have to say what they mean, they do not get implicit uncurrying of |this| as a first parameter.

It's just I think it makes it a much better default.

Default for what? Only for => functions at runtime? That is,

o.m(a)

is

o.m(o, a)

if and only if o.m references a function expressed by => syntax, and

otherwise

o.m(a)

?

If I write a method that doesn't need |this| or uses outer |this|, and use an arrow, I get an extra argument at the front, but if I use a function, I don't. That is going to generate bugs.

You're focusing too much on the case where the callee uses dynamic |this| or wants to. Such a callee can't use => syntax.

The programmer must write the function according to the API contract. For dynamic |this| contracts, use a long-form function or keep up the arguments and pressure for supporting -> as well. Blaming => for not

serving this use-case is not going to help.

# Gordon Brander (13 years ago)

On Jul 11, 2012, at 2:58 PM, Brendan Eich wrote:

Irakli Gozalishvili wrote:

If we want convenient unbound-this function syntax, -> stands ready. I proposed it along with => originally but it was cut by TC39 during our March meeting in order to achieve consensus on =>.

Yeah I'm aware of that, but problem as I see is that new fat arrow functions can not fully replace existing functions.

That is not a problem, or rather: not a problem with fat arrows.

I would like to loop this conversation away from fat arrow and back to discussing a convenient way to specify flexible this. Irakli suggested:

o.foo(a, b)

as sugar for

foo(o, a, b)

...which is how Python and Lua handle this. Treating method invocation as sugar this way allows you to think of this as being a category of data on which you operate, rather than a class instance you have access to. It's kind of a functional twist on OOP that makes it easy to write functions that can be used in both OO and functional style.

But back to ES... I think the motivation here may be to a convenient syntax for treating this as "soft" so you can assign methods to other objects, or easily use methods in a functional style. This is sort of how functions work today with call and apply (only you can't pass the call function object around without losing its function context).

I noticed an alternative solution to this problem in the ES Wiki: harmony:arrow_function_syntax#deferred (see "optional leading this initializer"). As I understand it, that would give us some sort of special delimiter for specifying a soft-bound this context on a thin-arrow, e.g. in pseudo-code:

foo(o | a, b)

...or some-such. Irakli, would such a feature resolve your use-case?

Thanks,

# Brendan Eich (13 years ago)

See also

strawman:bind_operator

There's still some awkwardness: obj::obj.foo requires repeating obj. Irakli's mail suggests obj::foo somehow scoping foo to obj if possible, but this smells of 'with'.

# Brendan Eich (13 years ago)

Gordon Brander wrote:

I noticed an alternative solution to this problem in the ES Wiki:harmony:arrow_function_syntax#deferred (see "optional leading this initializer"). As I understand it, that would give us some sort of special delimiter for specifying a soft-bound this context on a thin-arrow, e.g. in pseudo-code:

foo(o | a, b)

...or some-such.

That syntax was for expressions and definitions of functions, not for calls. The idea was to enable parameter default values for |this|. It won't do what Irakli wants.

Soft-bind is not going to fly, see

esdiscuss/2012-June/023221, esdiscuss/2012-June/023202, and mainly esdiscuss/2012-May/023002.

Implicitly shifting |this| in call expressions to the front of the actual argument list is similarly painful in overhead for implementors, besides making the dynamic "two args, or only one?" hazard depending on how a callee called from a polymorphic site happened to be expressed.

What Python does is a bit different:

obj.foo(arg)

is

Class_of_obj.foo(obj, arg)

not just

foo(obj, arg)

for some identifier-expression 'foo'. But this depends on classes in Python, which are not in any way analogous to => function expressions.

# Irakli Gozalishvili (13 years ago)

Thanks Brendan for explaining the problem, I can see how that can get confusing.

What "it" is a failure?

While fat arrows solve a problem (or rather simplify) with .bind(this) they do introduce a new one. Namely they can't be used as methods. Which would not have being a problem if we had both thin & fat arrows. That basically means that whenever I have to explain someone how to use X I can't tell forget about old functions, I have to teach about one more flavor of it. That's what I see as failure.

No, not "as methods" as commonly expressed in object literals. Those get other shorthand.

They do get other shorthand, but that's makes interoperability between OO & functional only harder, you will have to choose either red or blue pill. While I really wish we could write code in either style and let users decide which style they want to use it.

People have to say what they mean, they do not get implicit uncurrying of |this| as a first parameter.

Yes I do understand this point. My suggestion clearly has issues, but I'm happy to get any other solution that would be issue free and would allow reuse of functions as methods. It could be thin arrow or explicit syntax to expressing desire to get this as parameter:

var map = (this, f) => reduce(this, (result, x) => result.concat([ f(x) ]), [])

-- Irakli Gozalishvili Web: www.jeditoolkit.com