Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use arrow syntax for anonymous functions #6945

Merged
merged 8 commits into from
Aug 21, 2024

Conversation

cometkim
Copy link
Member

No description provided.

Copy link
Collaborator

@cristianoc cristianoc left a comment

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

@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
Collaborator

@cometkim would you get feedback in discord?

@zth
Copy link
Collaborator

zth commented Aug 11, 2024

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
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
Copy link
Member Author

cometkim commented Aug 11, 2024

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
Copy link
Member

cknitt commented Aug 11, 2024

I like this, too! 😄

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

@cometkim
Copy link
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
Member Author

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

@DZakh
Copy link
Contributor

DZakh commented Aug 12, 2024

@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
Copy link
Contributor

DZakh commented Aug 12, 2024

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
Copy link
Member

cknitt commented Aug 16, 2024

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
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
Copy link
Contributor

DZakh commented Aug 16, 2024

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

@cometkim
Copy link
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
Copy link
Contributor

DZakh commented Aug 16, 2024

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
Copy link
Contributor

DZakh commented Aug 16, 2024

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
Copy link
Member Author

cometkim commented Aug 20, 2024

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
Copy link
Member

cknitt commented Aug 20, 2024

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
Copy link
Collaborator

zth commented Aug 20, 2024

@cknitt sure!

@cometkim
Copy link
Member Author

@cknitt check it out!

@cometkim

This comment was marked as resolved.

@cometkim
Copy link
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
Collaborator

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

lib/es6/caml_format.js Outdated Show resolved Hide resolved
@cometkim
Copy link
Member Author

cometkim commented Aug 21, 2024

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
                })
              )
            )
          )
        )
      )
    )
  );
}

@DZakh
Copy link
Contributor

DZakh commented Aug 21, 2024

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

@cknitt
Copy link
Member

cknitt commented Aug 21, 2024

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.

Copy link
Member

@cknitt cknitt left a comment

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
Copy link
Contributor

DZakh commented Aug 21, 2024

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
Copy link
Member

cknitt commented Aug 21, 2024

Maybe I am biased because I always have

  arrowParens: "avoid",

in my prettier config.

@cknitt
Copy link
Member

cknitt commented Aug 21, 2024

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

@cometkim
Copy link
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
Copy link
Member Author

cometkim commented Aug 21, 2024

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 😅

Copy link
Collaborator

@cristianoc cristianoc left a comment

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
20 checks passed
@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