Skip to content

use arrow syntax for anonymous functions - #6945

Merged
cometkim merged 8 commits into
rescript-lang:masterfrom
cometkim:emit-arrow
Aug 21, 2024
Merged

cometkim merged 8 commits into
rescript-lang:masterfrom
cometkim:emit-arrow

Conversation

@cometkim

Copy link
Copy Markdown
Member

No description provided.

@cristianoc cristianoc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the motivation for this? Shorter code?

@cometkim

Copy link
Copy Markdown
Member Author

Yes. It improves the readability of the output, especially where pipe syntax is used.

There is no difference in the runtime performance between anonymous functions that already allow implicit captures.

However, it might be a breaking change because it implicitly changes the behavior of this, arguments, and the function name of the inner closure. Idiomatic ReScript code should not rely on these.

Comment thread jscomp/build_tests/react_ppx/src/gpr_3987_test.bs.js
@cometkim cometkim changed the title emit arrow syntax for non top level functions use arrow syntax for anonymous functions Aug 11, 2024
@cristianoc

Copy link
Copy Markdown
Collaborator

@cometkim would you get feedback in discord?

@zth

zth commented Aug 11, 2024

Copy link
Copy Markdown
Member

I like this! Just a few thoughts.

Yes. It improves the readability of the output, especially where pipe syntax is used.

Could you add one or two examples of this to the description?

However, it might be a breaking change because it implicitly changes the behavior of this, arguments, and the function name of the inner closure. Idiomatic ReScript code should not rely on these.

While not idiomatic, this type of thing is sometimes necessary/useful. Would it be possible to somehow opt into a regular function like we're emitting now when necessary?

@cometkim

Copy link
Copy Markdown
Member Author

Would it be possible to somehow opt into a regular function like we're emitting now when necessary?

If is_method=true, it will still emit a regular function and users can use this and arguments. We can explicitly use E.method_ where it is semantically acceptable.

@cometkim

cometkim commented Aug 11, 2024

Copy link
Copy Markdown
Member Author

Could you add one or two examples of this to the description?

There is no big difference. It's just like:

image

It is also possible to abbreviate blocks with only have single return.

@cknitt

cknitt commented Aug 11, 2024

Copy link
Copy Markdown
Member

I like this, too! 😄

In the case of a single argument, could/should we emit x => ... instead of (x) => ...?

@cometkim

Copy link
Copy Markdown
Member Author

In the case of a single argument, could/should we emit x => ... instead of (x) => ...?

If we want. I already implemented it in my local branch

@cometkim

Copy link
Copy Markdown
Member Author

@DZakh Can you confirm that this doesn't harm your library perf?

@DZakh

DZakh commented Aug 12, 2024

Copy link
Copy Markdown
Member

@DZakh Can you confirm that this doesn't harm your library perf?

I rely on this in a few places, if it's possible to opt-in to the function there, then I can try.

@DZakh

DZakh commented Aug 12, 2024

Copy link
Copy Markdown
Member

Also, this change should positively impact the bundlesize of the projects. When I did benchmarks for rescript-schema, I noticed, that esbuild doesn't optimise function to arrow functions, which is noticeable in terms of size.

@cknitt

cknitt commented Aug 16, 2024

Copy link
Copy Markdown
Member

While not idiomatic, this type of thing is sometimes necessary/useful. Would it be possible to somehow opt into a regular function like we're emitting now when necessary?

How would this opt-in look like syntactically? Something like

let x = [0, 1]->Array.map(@NoArrow x => x + 1)

?

Maybe this could be an interim solution until all libraries / non-idiomatic code relying on this has been migrated to support arrow function output?

@cometkim

Copy link
Copy Markdown
Member Author

Yep, I thought the similar (mine was @method)

I also checked if it could be a named function, but it must be anonymous because that would break the behavior.

@DZakh

DZakh commented Aug 16, 2024

Copy link
Copy Markdown
Member

Maybe something like this, but for regular functions as well? https://rescript-lang.org/syntax-lookup#this-decorator

@cometkim

Copy link
Copy Markdown
Member Author

Maybe something like this, but for regular functions as well? https://rescript-lang.org/syntax-lookup#this-decorator

This PR wouldn't change regular function behavior.

I think we can support @this on anonymous functions. But I'm not sure we want to retain this pattern for the long term.

@DZakh

DZakh commented Aug 16, 2024

Copy link
Copy Markdown
Member

Maybe something like this, but for regular functions as well? https://rescript-lang.org/syntax-lookup#this-decorator

This PR wouldn't change regular function behavior.

I think we can support @this on anonymous functions. But I'm not sure we want to retain this pattern for the long term.

Missed the fact that the change is only for anonymous functions. Then there's no problem from my side. I don't see a good usecase for this in anonymous functions in ReScript besides Js bindings. And they are already supported by @this

@DZakh

DZakh commented Aug 16, 2024

Copy link
Copy Markdown
Member

Maybe something like this, but for regular functions as well? https://rescript-lang.org/syntax-lookup#this-decorator

This PR wouldn't change regular function behavior.
I think we can support @this on anonymous functions. But I'm not sure we want to retain this pattern for the long term.

Missed the fact that the change is only for anonymous functions. Then there's no problem from my side. I don't see a good usecase for this in anonymous functions in ReScript besides Js bindings. And they are already supported by @this

Just need to double check that there are tests for @this and they are working correctly after the change

@cometkim

cometkim commented Aug 20, 2024

Copy link
Copy Markdown
Member Author

Just need to double check that there are tests for @this and they are working correctly after the change

@DZakh @zth Methods using @this binding shouldn't be affected.

Tests are exist here: https://github.com/cometkim/rescript-compiler/blob/emit-arrow/jscomp/test/test_bs_this.js

@cknitt

cknitt commented Aug 20, 2024

Copy link
Copy Markdown
Member

In the case of a single argument, could/should we emit x => ... instead of (x) => ...?

If we want. I already implemented it in my local branch

I would want to 😄. What do you say @zth @cristianoc?

@zth

zth commented Aug 20, 2024

Copy link
Copy Markdown
Member

@cknitt sure!

@cometkim

Copy link
Copy Markdown
Member Author

@cknitt check it out!

@cometkim

This comment was marked as resolved.

@cometkim

Copy link
Copy Markdown
Member Author

Fixed it, but it's a heuristic. It would be a better implementation if it could recognize IIFE pattern from the Lambda IR.

@cometkim
cometkim requested a review from cristianoc August 20, 2024 21:54
@cometkim
cometkim requested review from cknitt and zth August 20, 2024 21:54
@cristianoc

Copy link
Copy Markdown
Collaborator

Just to double check: are there any concerns left about correctness, or have they all been answered?

Comment thread lib/es6/caml_format.js Outdated
@cometkim

cometkim commented Aug 21, 2024

Copy link
Copy Markdown
Member Author

Just to double check: are there any concerns left about correctness, or have they all been answered?

If everyone has checked the output, I think it's OK. Concerned parts like IIFE and this binding, are all resolved.

The rest is just to make the output a little more "pretty", but it doesn't have to do with correctness.

E.g.
function findAllAddresses(businesses) {
  return Belt_List.toArray(Belt_List.flatten(Belt_List.fromArray(Belt_Array.map(businesses, business => Belt_List.concat(Belt_Option.mapWithDefault(business.address, /* [] */0, a => ({
    hd: a,
    tl: /* [] */0
  })), Belt_Option.mapWithDefault(business.owner, /* [] */0, p => Belt_Option.mapWithDefault(p.address, /* [] */0, a => ({
    hd: a,
    tl: /* [] */0
  }))))))));
}
function findAllAddresses(businesses) {
  return Belt_List.toArray(
    Belt_List.flatten(
      Belt_List.fromArray(
        Belt_Array.map(
          businesses,
          business => Belt_List.concat(
            Belt_Option.mapWithDefault(
              business.address,
              /* [] */0,
              a => ({
                hd: a,
                tl: /* [] */0
              })
            ),
            Belt_Option.mapWithDefault(
              business.owner,
              /* [] */0,
              p => Belt_Option.mapWithDefault(
                p.address,
                /* [] */0,
                a => ({
                  hd: a,
                  tl: /* [] */0
                })
              )
            )
          )
        )
      )
    )
  );
}

Comment thread jscomp/test/UncurriedExternals.js
@DZakh

DZakh commented Aug 21, 2024

Copy link
Copy Markdown
Member

About not wrapping args with () I personally things it's better with paranthes and this is also how Prettier formats the code.

@cknitt

cknitt commented Aug 21, 2024

Copy link
Copy Markdown
Member

About not wrapping args with () I personally things it's better with paranthes and this is also how Prettier formats the code.

Yes, but

  • it's not the way the ReScript printer does it
  • for me it's extra noise
  • the only advantage of (x) => ... I can see is that it is easier to manually add a second argument, but that should not be a concern for the compiler output.

@cknitt cknitt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not notice any further issues in the compiler output.
Great work, thanks a lot! 👍

@DZakh

DZakh commented Aug 21, 2024

Copy link
Copy Markdown
Member

it's not the way the ReScript printer does it

Since it's JavaScript, I think it should be formatted as idiomatic JavaScript. And even though it's an extra noise, the majority of Js devs already got used to it and find it more natural to see paranthes than not.

@cknitt

cknitt commented Aug 21, 2024

Copy link
Copy Markdown
Member

Maybe I am biased because I always have

  arrowParens: "avoid",

in my prettier config.

@cknitt

cknitt commented Aug 21, 2024

Copy link
Copy Markdown
Member

Anyway, how do we come to a decision regarding the arrow parens?
As preferences seem to differ. 🙂

@cometkim

Copy link
Copy Markdown
Member Author

Maybe I am biased because I always have

  arrowParens: "avoid",

in my prettier config.

So is mine. Most of the projects I'm involved with use the style of avoiding it. It's more of a personal preference.

But I can agree with the always style. I don't think it's worth increasing the compiler complexity to avoid it.

@cometkim

cometkim commented Aug 21, 2024

Copy link
Copy Markdown
Member Author

I'll leave it as is, unless there are other objections. The complexity I mentioned is trivial.

Changing x => x to (x) => x for all outputs is also noisy to me 😅

@cristianoc cristianoc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No special opinions as long as it does not break anything, and people are generally happy.

@cometkim
cometkim merged commit a98bc28 into rescript-lang:master Aug 21, 2024
@cometkim
cometkim deleted the emit-arrow branch August 21, 2024 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants