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

Emit arrow functions #499

Closed
martinklepsch opened this issue Mar 30, 2024 · 13 comments
Closed

Emit arrow functions #499

martinklepsch opened this issue Mar 30, 2024 · 13 comments

Comments

@martinklepsch
Copy link
Contributor

To upvote this issue, give it a thumbs up. See this list for the most upvoted issues.

Is your feature request related to a problem? Please describe.

Arrow functions have different semantics than regular JS functions. For better host interop it might be worth considering a way to support them.

via #498

Describe alternatives you've considered

  • metadata
  • new fn-style macro
@martinklepsch martinklepsch mentioned this issue Mar 30, 2024
@borkdude
Copy link
Member

borkdude commented Mar 30, 2024 via email

@egasimus
Copy link

^:=> is the Unicorn smiley, they're friends with the Turbofish ::<> from Rust 😁

Didn't (=> ...)` mean something entirely different in Clojure (pipe operator? wasn't there a TC39 proposal to add that to JS?)

Or maybe some kind of per-file/per-project pragma? This complicates things...

New syntax (lambda [x] ...) or (fn=> [x] ...) to emit arrow functions is yet another option, though that way the "shorthand" property of the ()=>{} syntax is lost.

@borkdude
Copy link
Member

=> is not a clojure core macro, that is ->
if we're going for fn=> then we might as well just go for (=> [x] ...)
I don't understand what you mean with "shorthand property is lost"

@brandonstubbs
Copy link
Contributor

I just had a question. If within jsx functions were always compiled to the arrow style wouldn't this be enough? I know you mentioned the existing CLJS support, but do you typically need that within jsx?

@borkdude
Copy link
Member

Even in JSX you don't know if you will need the this argument which you will perhaps sometimes need, I don't think it's a good idea to just automatically switch. Also JSX isn't the only context where you might want to use an "arrow" function.

More info here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

@egasimus
Copy link

egasimus commented Apr 3, 2024

I don't understand what you mean with "shorthand property is lost"

That's because of the ambiguity of the word "property". I don't mean JS properties, just the properties of a thing in the general sense. My bad, could've expressed that more clearly - differences aside, ()=>{} is a shorthand for function(){}; thus, it would not make sense to add a (fn=>) form that is longer than (fn), even by a couple characters.

=> is not a clojure core macro, that is ->
if we're going for fn=> then we might as well just go for (=> [x] ...)

Awesome! I support that.

@borkdude
Copy link
Member

borkdude commented Apr 3, 2024

Shortness isn't the main point here though. (fn []) is already quite short and Clojure has an even shorter way to create functions: #(...), but all of these compiled to function in ClojureScript and squint adheres to this for compatibility reasons.

The main reason you would have an additional way in squint to produce () => {} imo is because arrow functions have different meaning in JS (see for example https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) and because some tools expect people to write them in some places. Basically what @martinklepsch wrote in the original issue post.

@lilactown
Copy link
Collaborator

I'm going to throw a wild idea out here: could fn analyze whether this-as is used and emit an arrow function if not?

@borkdude
Copy link
Member

borkdude commented Apr 3, 2024

That also crossed my mind and worth exploring. Technically it's possible, but could there be any breakage doing this?

@borkdude
Copy link
Member

borkdude commented Apr 3, 2024

There are a few more restrictions on () => {}:

So the compiler would have to check for these things as well... and there may be cases where the user wants to do one or the other depending on the closed over value of this vs the contextual behavior when writing stuff like {:a (fn [..] ..)
Seems a bit complex to account for all of this, so perhaps it's best to stick with an explicit opt-in.

@lilactown
Copy link
Collaborator

Just writing my thoughts down...

I think that the first one is the same thing we've been talking about re: this; it's not that you can't do

{a: () => "foo"}

it works just fine, but you won't get the desired behavior if you want to use this inside it.

re: arguments, this would need to be another static analysis, however does arguments even work in squint today? Spread args basically obviate the need for it, and calling external code that uses it should have no issues.

yield also can't be used with regular function, you must use function*. We can keep the same behavior as today with fns marked with ^:gen.

The only technically ambiguous case in that list would be constructors. I'm not sure how much weight to give that.

Another potentially ambiguous case is when you actually want to use the surrounding this inside of an arrow function, e.g.

function someMethod() {
  return () => { this.someOtherMethod() };
}

let o = {someMethod, someOtherMethod() { console.log("foo") }};

let f = o.someMethod();

f()

The above ought to print foo to the console, as the this inside of the arrow function is lexically bound to the same value as someMethod.

This could be expressed two different ways:

;; not ambiguous, `#(.someOtherMethod this)` can be an arrow
(fn [] (this-as this #(.someOtherMethod this)))

;; ambiguous, we do actually want an arrow but it's easily interpreted that we don't
(fn [] #(this-as this (.someOtherMethod this)))

This also presents why arrow functions are less useful in ClojureScript; the default way of accessing this is to lexically bind it at the point where we want to capture the context, and then to use that binding rather than the ambiguous ephemeral this. I.e. it's the same pattern Ye Olde JS devs (like myself) used to do

function someMethod {
  let that = this;
  return function () { that.someOtherMethod() };
}

Anywho, I think I've talked myself out of automatically deciding which syntax to use, and instead would vote for an unambiguous syntax (just like JS); I like (=> [] ,,,) or even (afn [] ,,,) if we don't want to add more sigils.

@borkdude
Copy link
Member

borkdude commented Apr 4, 2024

Re this:

var obj = {a: () => this} // this depends on context 

and

var obj = {a: function() { return this }} // always returns obj

are both possible and behave differently. The JS user can control this behavior explicitly by using an arrow function or not.
If we decide based on static analysis, the user doesn't have a choice anymore. This is discussed in https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Re arguments: doesn't yet work in squint, but should work like this:

$ plk -e '(defn foo [] (vec (js-arguments))) (js/console.log (foo 1 2 3))'
WARNING: Wrong number of args (3) passed to cljs.user/foo at line 1
[1 2 3]

...

and then indeed I read that you already talked yourself out of auto-arrowing :)

@borkdude
Copy link
Member

borkdude commented Apr 4, 2024

I think I'll do the following: first support it via metadata. If it then becomes annoying because people use this all the time, then we'll introduce a (=> [] ...) form.

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

No branches or pull requests

5 participants