Function composition syntax

Isiah Meadows isiahmeadows at gmail.com
Thu Sep 29 06:55:17 UTC 2016


Oh, and most of your concerns will be addressed in some way in the
completed strawman.

On Thu, Sep 8, 2016, 14:59 Mike Samuel <mikesamuel at gmail.com> wrote:

> On Wed, Sep 7, 2016 at 1:54 PM, Isiah Meadows <isiahmeadows at gmail.com>
> wrote:
> > Somehow, this missed the list...
> >
> >
> > ---------- Forwarded message ---------
> > From: Isiah Meadows <impinball at gmail.com>
> > Date: Wed, Sep 7, 2016, 12:03
> > Subject: Re: Function composition syntax
> > To: <mikesamuel at gmail.com>, <es-discuss at mozilla.org>
> >
> >
> > I was thinking in reverse order, but personally, I'm flexible on
> specifics
> > like that.
> >
> >
> > On Wed, Sep 7, 2016, 12:00 Isiah Meadows <isiahmeadows at gmail.com> wrote:
> >>
> >> Inline.
> >>
> >> On Wed, Sep 7, 2016, 11:05 Mike Samuel <mikesamuel at gmail.com> wrote:
> >>>
> >>> On Wed, Sep 7, 2016 at 10:10 AM, Isiah Meadows <isiahmeadows at gmail.com
> >
> >>> wrote:
> >>> > I would like to see a function composition operator make it into the
> >>> > language. Currently, there is:
> >>> >
> >>> > - Lodash: `_.flow` and `_.flowRight` (lodash/fp alias: `_.compose`
> and
> >>> > `_.composeRight`)
> >>> > - Underscore: `_.compose`
> >>> > - Ramda: `R.compose`
> >>> > - Tons of npm modules:
> >>> > https://www.npmjs.com/search?q=function+composition
> >>> > - Numerous manual implementations
> >>> >
> >>> > Function composition could be far more efficiently implemented in the
> >>> > engine, in ways not possible at the language level:
> >>> >
> >>> > 1. They can create pipelines to optimize multiple composition chains
> >>> > together.
> >>> > 2. They can avoid most of the closure allocation cost internally.
> >>> > 3. The returned functions can internally use a separate call path to
> >>> > avoid
> >>> > some of the [[Call]] boilerplate when called and when calling the
> >>> > functions
> >>> > themselves (you don't need to verify twice).
> >>> >
> >>> > Here's what I propose: a new infix operator `>=>` (operator and
> >>> > direction
> >>> > can change) for composing two functions. It would do the following:
> >>>
> >>> What is the advantage of an operator over a static `Function.compose`
> >>> or `Function.compose{Left,Right}`?
> >>
> >>
> >> See my section on why I suggested the operator instead of a function.
> One
> >> of the biggies is that the engine can statically optimize it, including
> >> inline arrow functions (no function object needs created). Another that
> I
> >> didn't list is that you are guaranteed 2 operands, so you can't
> "compose" 1
> >> function. Also, fewer parentheses are always better IMO.
>
> Thanks for answering.   Part of the source of my confusion might be
> that I didn't see a link to any proposal.
> The only link I saw in the OP was a link to an NPM list of function
> composition implementations.
>

See my previous email.


> I'm unclear on why an n-ary compose of 1 is a problem.  Isn't that
> just identity?
> And nullary compose is just the the void operator.


Good point. I did think of those cases, but I'll admit the reasoning is a
bit shaky there (and will be largely absent in the strawman).


>
> >> Oh, and there's a reason most `compose` functions accept multiple
> >> arguments: it would become way too unwieldy with all the parentheses
> >> otherwise.
>
>
>
> >>> > 1. Verify both operands are callable.
> >>>
> >>> What should happen if you try to compose something that only supports
> >>> [[Call]] with something that only supports [[Construct]]?  For
> >>> example, one might try to compose Object.freeze with a constructor to
> >>> get a producer of frozen instances.
> >>
> >>
> >> I can adjust my proposal accordingly to cover "construct first when
> called
> >> as constructor". I missed that use case, but it's easy to fix.
>
> I have no idea whether this use case is important.
> I was mostly just wondering whether there was something problematic
> about construct.
>

No, there isn't. That's also why I made the change already.


>
>
> >>> > 2. Create a callable-only function that calls its left operand with
> the
> >>> > original arguments and `this`, then calling its right operand with
> the
> >>> > result and the same `this`.
> >>>
> >>> > 3. Sets its length to the left operand.
> >>> > 4. Return the new function.
> >>>
> >>> Is the new function strict only when both operands are strict or when
> >>> either is strict?
> >>> Or should it depend on the scope in which the operator appears?
> >>>
> >>> When the left operand is non-strict, is the composition the caller of
> >>> the left operand?
> >>
> >>
> >> Composed functions would be similar to bound functions. So it shouldn't
> >> make a difference.
> >>
> >> A transpiler can transform `f >=> g` directly to `function () { return
> >> g.call(this, f.apply(this, arguments)) }` (mod type checks).
>
> One quibble.
> The problem with this is that non-strict functions replace a `this` of
> null with
> a reference to the global object, so this would prevent a strict f and
> g from distinguishing between
>
>    (f >=> g).call(null, x)
>
> and
>
>     (f >=> g).call(window, x)
>

The composed function will be a strict function. I was speaking in the case
of strict mode, but loose mode can be fixed by placing a `"use strict"`
inside the function itself.


>
> >>> > The reason I suggested an operator instead of a function:
> >>> >
> >>> > 1. Fewer parentheses is always a plus.
> >>> > 2. It allows engines to statically optimize functions in the middle
> >>> > (avoid
> >>> > an extra function allocation), like with `f >=> x =>
> console.log("x:" +
> >>> > x)`.
> >>>
> >>> This seems doable optimistically with Function.compose, though you'd
> >>> have to back out when Function.compose is assigned.
> >>
> >>
> >> Engines don't usually make optimistic assumptions like that on the first
> >> pass, much less the first 100 or so (it takes thousands for V8's
> inliner to
> >> trigger, for example). They would have to cut a very specific special
> case
> >> for this (they don't even do that for `bind` IIRC), which I don't see as
> >> likely.
>
> Do you a sense of how important this optimization is compared to
> Array.prototype.{forEach,map}?
> Why is syntax-enabled optimization a stronger argument for composition
> than factoring out call
> overhead in the body of a tight loop?
>
> Why are engines' decisions about when to optimize bad specifically for
> composition?


Composition is relatively trivial to generate at runtime, since there's
minimal need for even inline cache type checking, and it's a conceptually
simple thing even at the imperative level. Syntax would enable engines to
optimistically create the pipeline, cache the result if it's recreated,
among other things.

One other important case is if you use anonymous functions in the middle:
engines and transpilers won't have to allocate a full function instance for
it, and can instead opt to inline it, leaving just a stack frame for simple
cases like `func >=> (x => x + 1)`. That is simply not practical in
practice with a `compose` function, even though in theory, the engine
should be able to detect it.

Sharing a common closure across a single sequence is also possible for
anonymous functions, because the functions are never used outside of it.

Basically, there are things engines can do with syntax that plainly aren't
possible to do with a function, mostly with reducing memory and just doing
less to set them up. That would prove useful for applications that use
composition very heavily.

Conversely, inlining for Array prototype methods would just benefit speed.
Even after inlining, you still have the array and function closure
allocated (despite the latter being unused but retained in case of
bailout).

>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160929/40b9a528/attachment-0001.html>


More information about the es-discuss mailing list