Function composition syntax

Mike Samuel mikesamuel at gmail.com
Thu Sep 29 03:12:42 UTC 2016


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.

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.


>> 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.



>>> > 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 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?


More information about the es-discuss mailing list