Emit arrow functions
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 https://github.com/squint-cljs/squint/issues/498
Describe alternatives you've considered
- metadata
- new fn-style macro
Wild idea, I guess we could support this:
(require '[squint.core :refer [=>]])
(=> [x] (inc x))
clj-kondo could be easily configured using lint-as
Another more slightly verbose idea without inventing new syntax:
(fn ^:=> [x] (inc x))
On Sat, Mar 30, 2024 at 10:53 PM Martin Klepsch @.***> wrote:
To upvote this issue, give it a thumbs up. See this list https://github.com/clj-kondo/clj-kondo/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc 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 https://github.com/squint-cljs/squint/issues/498
Describe alternatives you've considered
- metadata
- new fn-style macro
— Reply to this email directly, view it on GitHub https://github.com/squint-cljs/squint/issues/499, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACFSBXYD7253PTONQO4CZDY24X6TAVCNFSM6AAAAABFP3K5ICVHI2DSMVQWIX3LMV43ASLTON2WKOZSGIYTMNRXG4YTENY . You are receiving this because you are subscribed to this thread.Message ID: @.***>
-- https://www.michielborkent.nl https://www.eetvoorjeleven.nu
^:=> is the Unicorn smiley, they're friends with the Turbofish ::<> from Rust :grin:
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.
=> 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"
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?
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
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 forfn=>then we might as well just go for(=> [x] ...)
Awesome! I support that.
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.
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?
That also crossed my mind and worth exploring. Technically it's possible, but could there be any breakage doing this?
There are a few more restrictions on () => {}:
- Cannot be used a methods
- No binding of
arguments - Cannot be used as constructors
- Cannot be used as generators
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.
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.
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 :)
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.