trustedtomato / proposal-partial-expression

Function partial application, operator partial application and all that fun stuff.
10 stars 1 forks source link

Babel plugin #9

Open kosich opened 3 years ago

kosich commented 3 years ago

Hey, great work on the proposal!

I created a babel plugin with simpler rules ("simpler", not "better"!), exploring no prefix partial expressions:

let bang = _ + '!';
// equals to
let bang = x => x + '!';

You can play with it in the babel playground.

And here's the plugin repository where I described some rules and how it integrates with the pipeline operator.

Maybe it can help pushing the proposal further.

trustedtomato commented 3 years ago

Neeeat! I'm not quite sure how it handles brackets though. I think [1, 2, 3].map(_ + 1) should become [1, 2, 3].map((_temp) => _temp + 1) and not (_temp) => ([1, 2, 3].map(_temp + 1)).

I think one thing you could do is to bubble up the _ to the nearest (not-function-calling) grouping, so that [1, 2, 3].map((_ + 1)) could become [1, 2, 3].map((_temp) => _temp + 1). It definitely feels a bit quirky at first, but at second glance, it's pretty robust. It would even work neatly with pipelines: const addOne = (_ |> (1 + _)) would become const addOne = (_temp) => 1 + _temp (note that the mentioned code currently results in an error).

What do you think?

kosich commented 3 years ago

Heya, glad you liked it!

Yeah, I think constructs like [1, 2, 3].map(_ + 1) should definitely be supported!

Though, with a nearest non-calling group bubbling there might be a problem, as [1, 2, 3].map( 2 * (20 + _) ) wont bubble high enough. One possible solution is to use explicit marking, where we want _ to bubble to (as you originally proposed).

Without changing the babel's syntax parsing (which I think is more troublesome), we could try the {} marking (as discussed in #5 and if we won't find more issues with it).

Also, currently the plugin handles the pipes |> as a special case. With explicit markings, we might or might not want continue doing that. E.g.:

// special case, implicit grouping
let a = 42 |> _ + 1; // ≈ 42 + 1;
typeof a; // 'number';

// no special case, explicitly placing bubble point:
let a = 42 |> { _ + 1 }; // ≈ 42 + 1;
typeof a; // 'number';

// no special case, bubbling to the top of the expression
let a = 42 |> _ + 1; // ≈ x => (x + 1)(42);
typeof a; // 'function';
a('param'); // runtime error

// no special case, no implicit bubbling
let a = 42 |> _ + 1; // parsing error

.

note that the mentioned code currently results in an error

Great catch! I'll have to check that out, thx! I was a bit lazy to add specs, but if we get serious, we should add proper tests.

.

Please, share your thoughts.

trustedtomato commented 3 years ago

I think not having special cases is a better starting position. I think if something can be added later and it makes sense to add it later, it should be added later. So I believe that having no bubbling and implicit grouping would be the best at first.

kosich commented 3 years ago

I was wrong, let a = { _ + 1 } is surely an invalid JS construct that fails at parse time (idk why I thought it would work). So if we want a marker, seems like we'll need to alter babel's parser.

kosich commented 3 years ago

Since {} turned me down, I needed some other valid syntax to mark partial expression's root. So, I had to apply a bit of magic. Bit magic, to be exact. I decided to try overriding bitwise not ~ unary operator:

let a = ~ _ + 1;

similar to your proposed ^ syntax, isn't it?

And after a while, I think it works:

a = ~ _;
assert(a(42) === 42);

a = ~ _ + 1;
assert(a(41) === 42);

a = ~ _(40) + 1;
assert(a(inc) === 42);

a = [1,2,3].map(~ _ + 1).join();
assert(a === '2,3,4');

a = [1,2,3].filter(~ _ % 2).map(~ _ + 1).join()
assert(a === '2,4');

~ mark is now required everywhere, except for pipe RHS:

// in pipe LHS, the ~ marker is required
a = ~ _ |> inc
assert(a(42) === 43)

// without a marker babel will throw
a = _ |> inc // INVALID: _ is unbound

// RHS can have explicit or implicit grouping
a = 40 |> ~ _ + 1 |> _ + 1
assert(a === 42)

Here is the babel playground with new syntax.

Please, note that I haven't covered or tested multiple placeholders, nested ~ groups, async/await, and ~ w/o a placeholder. Which might be ok for current stage and is probably better to implement with parser override.