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

Arrow functions #8

Merged
merged 21 commits into from Nov 28, 2016

Conversation

Projects
None yet
@RealyUniqueName
Member

RealyUniqueName commented Oct 11, 2016

This proposes better syntax for anonymous functions for the sake of cleaner and easier to maintan code

Rendered version

@ncannasse

This comment has been minimized.

Member

ncannasse commented Oct 11, 2016

Let's examine what would be the impact on the parser.

For instance the following (expr : Type) is already parsed as ECheckType(expr,type)

We already have an => operator, but we don't have a way to parse "tuples" such as (a,b,c) or with optional types (a:Int,b,c:String)

So this would require to change ECheckType of expr * ttype into ETuple of (expr * ttype option) list (maybe keep ECheckType for backward compatibility with a single case).

While Map literals and comprehensions can be type inferred to distinguish them from a arrow function, this is not very elegant since you can get a whole different meaning from [x => "Hello"] depending on the type it's inferred which is weird. The same goes for [for( x in strings ) x => x.length] which can have both a meaning as creating an Array of functions or a Map<String,Int>

I guess that would require to change Map literals & comprehensions syntax as well.

@nadako

This comment has been minimized.

Member

nadako commented Oct 11, 2016

A good point about map/array literal ambiguity, but I don't really understand how you want to represent this in untyped AST? EBinop(OpArrow, ETuple([(e1, t1); (e2, t2)]), ef)? Really? I think this should be parsed into a proper AST node, either EFunction or a new EArrowFunction (similar to how we have EIf and ETernary). Otherwise it'll be a huge pain working with them in macros.

@clemos

This comment has been minimized.

clemos commented Oct 11, 2016

It's probably too many breaking changes, but I think it would make (or have made) sense to use -> for maps (like in Scala) and => for arrow functions (ES6, Scala).
But then again maybe it brings even more ambiguous syntaxes with existing -> for function types, etc.

@RealyUniqueName

This comment has been minimized.

Member

RealyUniqueName commented Oct 11, 2016

While Map literals and comprehensions can be type inferred to distinguish them from a arrow function, this is not very elegant since you can get a whole different meaning from [x => "Hello"] depending on the type it's inferred which is weird. The same goes for [for( x in strings ) x => x.length] which can have both a meaning as creating an Array of functions or a Map<String,Int>

In such cases user has to enclose expression in parenthesis, if he wants it to be interpreted as a closure instead of a map.

[(x => "hello")]; 
[for( x in strings ) (x => x.length)]
@RealyUniqueName

This comment has been minimized.

Member

RealyUniqueName commented Oct 11, 2016

I think [x => "Hello"] and [for( x in strings ) x => x.length] should always be a map regardless expected type. Otherwise it's confusing if sometimes it's a map, but sometimes it's an array of functions.

With parentheses both cases become obvious.

@RealyUniqueName

This comment has been minimized.

Member

RealyUniqueName commented Oct 11, 2016

Also we can consider using -> instead of =>

@ncannasse

This comment has been minimized.

Member

ncannasse commented Oct 11, 2016

It's pretty confusing to either have parenthesis affecting the type of the expression. And I'm not in favor of having both -> and => since Haxe is not Perl :)

@clemos

This comment has been minimized.

clemos commented Oct 11, 2016

And I'm not in favor of having both -> and => since Haxe is not Perl :)

Or is it ;) ?
https://github.com/HaxeFoundation/haxe/blob/development/src/syntax/lexer.mll#L287-L289

@RealyUniqueName

This comment has been minimized.

Member

RealyUniqueName commented Oct 11, 2016

-> is also used by Java, Ruby and Ocaml. But i still prefer => )

@RealyUniqueName

This comment has been minimized.

Member

RealyUniqueName commented Oct 11, 2016

It's pretty confusing to either have parenthesis affecting the type of the expression

We already have this problem with abstracts operator overloading:

b + (a + c)

Is not guaranteed to be the same type as

(b + a) + c

if those vars are abstracts.

@back2dos

This comment has been minimized.

Member

back2dos commented Oct 11, 2016

I think by "type of expression" Nicolas did not mean types in the sense of our type system, but rather whether parentheses make the difference between a function literal or a binary operation.

To expand on that => is a valid binary operator and abstracts may define it: http://try.haxe.org/#c3A05 - Changing this is a pretty big deal.

If we want to have something any time soon, then -> is the way to go. I fail to see how distinguishing function syntax from a binary operator by using a different symbol is Perlish. I'd say relying on type is far crazier. Just use -> instead. It's explicit. It's aligned with quite a few other languages.

@ncannasse

This comment has been minimized.

Member

ncannasse commented Oct 11, 2016

@RealyUniqueName that's not very common, and also unless you have very strange abstract types the semantics of + will most likely be preserved

@RealyUniqueName

This comment has been minimized.

Member

RealyUniqueName commented Oct 11, 2016

Perhaps using -> has more sense than i thought. This operator is already tied to functions in Haxe :)

@nadako

This comment has been minimized.

Member

nadako commented Oct 11, 2016

Yes, -> could be good solution (tho it feels a bit uncomfortable after c# and js, but I guess it's kind of personal), but speaking of function type syntax, is everyone okay with it? Because if not, that's something that could be discussed too and changing it could impact arrow choice here :)

@back2dos

This comment has been minimized.

Member

back2dos commented Oct 12, 2016

As far as function type syntax goes, my issue is rather that you can't specify parameter names.

For everything else, I don't care. It's an arrow. That's good enough. If there's any "correct" way to do it, it's to reuse mathematical notation for functions, in which case ℝ ⟶ ℝ is the type and x ⟼ 2x is the function itself. Make of that what you wish ^^

@RealyUniqueName

This comment has been minimized.

Member

RealyUniqueName commented Oct 12, 2016

speaking of function type syntax, is everyone okay with it?

It lacks arguments names. But i think with args names it may become too verbose.

Also Int->String->Float != (Int->String)->Float is a bit strange (again to parentheses affecting type question ;) )

@RealyUniqueName

This comment has been minimized.

Member

RealyUniqueName commented Oct 19, 2016

I bump this thread.

Added AST section

(summon @Simn, @andyli, @hughsando, @waneck)

@hughsando

This comment has been minimized.

Member

hughsando commented Oct 19, 2016

I'm not too concerned about the syntax, but I would question whether it should be differentiated from the equivalent function definition in the AST. ie, after parsing, should you be able to tell the difference.
And if you can, might it be a variation of the exiting enum?

@Simn

This comment has been minimized.

Member

Simn commented Oct 19, 2016

Note that currently arg => expr is parsed as EBinop(OpArrow,arg,expr) and I don't think we want to change this.

I feel like => is gonna be annoying to get right. There's a syntactic ambiguity because [a => b] could be either a map declaration or an array with an arrow function.

@RealyUniqueName

This comment has been minimized.

Member

RealyUniqueName commented Oct 19, 2016

Are you ok with -> instead of =>?

@RealyUniqueName

This comment has been minimized.

Member

RealyUniqueName commented Nov 22, 2016

@ncannasse

This comment has been minimized.

Member

ncannasse commented Nov 24, 2016

I took some time to think about it.

My original opinion on the matter haven't changed much since the last time it was discussed. For me, a nicely designed language is not when there's nothing more to add to it, but when there's nothing more to remove from it, and arrow functions are just a small syntactic sugar that is not necessary for Haxe language.

Of course, that can be said for other features as well, such as some tricky metadata that we are adding sometimes in the compiler. But these does not need to be understood/learnt by the users to work. My main concern with Array functions is still to introduce some syntax that is confusing by the majority of (new) Haxe users.

However, things have evolved in the recent years, FP has become more and more popular in particular due to the async aspect of JS and Node, which I'm happy with, being myself a long time FP user "before it was cool" (which makes me a FP hipster!). The arrow function has been adopted by more and more languages and is hopefully used by more and more users (which I still think represents a minority of each language users).

In that regard, I'll vote in favor of the current proposal, with the same restrictions that Simon stated. I hope that this does not open a door from various requests to add X or Y new cool syntactic feature recently adopted by cool new language Z, as this is definitely not the way I want Haxe to head in the next years.

One request though, that in order to prevent errors by users mistaking => for -> we add an error when unifying a function with a e1 => e2 expression, such as "The operator => is used for maps, did you mean -> for arrow function?"

@andyli

This comment has been minimized.

Member

andyli commented Nov 25, 2016

Hey, sorry being late to join this discussion. I've read through the whole proposal and the discussion, and I think the current proposal is promising.

I'm quite interested in the idea @back2dos made in his comment: requiring arrow functions to be pure. It justifies the addition of arrow function pretty well since it will mean that arrow function is not just a syntactic sugar, but actually a proper way to declare pure function, that can be checked by the compiler and potentially enable some optimization.

Similarly, JS's arrow function is not just a syntactic sugar either. The this inside a JS arrow function will always be the instance of the enclosing "class", unlike the this in classic JS functions can be re-bound to anything using func.call(o) or func.apply(o).

However, I don't know how much optimization can this guarantee of purity bring. May be @Simn can advice?
If purity is required, may be we have to design a way to specify a pure function type, as I can imagine someone would want to utilize that, for example, to write a function that returns a memoized function of an input function.

I know this require some consideration and probably will delay the addition of arrow function, which is what everyone want to use now. But I think this is an interesting idea that deserve some attention. It wouldn't be easy to add/remove this purity requirement later since it would be a breaking change.

@RealyUniqueName

This comment has been minimized.

Member

RealyUniqueName commented Nov 25, 2016

I don't think purity requirement for short lambda is a good thing. What would majority of users do when they see "Your lambda is not pure" error? They will get annoyed :)

In case of JS and this lambdas are just fixing language design problem, which Haxe does not have.

@fullofcaffeine

This comment has been minimized.

fullofcaffeine commented Nov 26, 2016

So, what's the next step? :)

@andyli

This comment has been minimized.

Member

andyli commented Nov 28, 2016

Look like there is not much interest in the purity requirement after all. I cast my +1 now so we can just move forward. :)

@clemos

This comment has been minimized.

clemos commented Nov 28, 2016

To me purity is something entirely different, and it's probably a pity to restrain arrow function usage to pure FP.

@back2dos

This comment has been minimized.

Member

back2dos commented Nov 28, 2016

I will point out once again, that this proposal gives these two reasons as motivation:

  1. Other languages have it.
  2. It saves characters.

None of these have ever been reasons to add to the language. I don't see why it should change. This proposal adds nothing to the language. The discussion mentions FP as a motivation several times. The proposal doesn't.

As for the discussion, half the posts are pure statements of opinion and don't even try to provide any testable argument. The posts that do propose an objective observation are either superficial or wrong or both. I don't have the time to pick them apart and I bet you have better things to do then to read through me doing it, so let me just pick the last one that tries to make an argument:

In case of JS and this lambdas are just fixing language design problem, which Haxe does not have.

The this rebinding in JavaScript is what's required to have OOP. Arrow function expressions cannot be used to declare methods. Now if what's actually being said here is that JavaScript's design is fundamentally flawed (which I wouldn't protest), then I wonder why we should be imitating it.

As for purity:

I don't think purity requirement for short lambda is a good thing. What would majority of users do when they see "Your lambda is not pure" error? They will get annoyed :)

People get annoyed all the time. Some people are even annoyed that Haxe has a type system to start with. There are more than enough tools out there for them to blow their foot off. There's more value in providing an alternative than in mindlessly imitating the mainstream.

Purity is a very powerful quality, that the analyzer already leverages for optimization. It also guarantees thread safety and composability. And last but not least we can trivially lift this requirement if we wind up with something useless. OTOH introducing it later on will potentially break a lot of code. At the same time no case has actually be made for impure arrow functions. All of the examples given in the proposal are pure. How about some tangible problem to illustrate the point rather than speculating about how some imaginary majority might react?


My reservation though has little to do with purity or not. This proposal has no worthy goal at all. People don't want FP, then that's fine by me. Let's have something else then. But this? What's the manual going to say on the usage of this syntax? "Use this syntax if you wish to save characters or if you need to show your colleagues that Haxe's development is not in stagnation"? What do we tell users who want to use this syntax for method declaration? Will we deprecate the current function syntax if both are equivalent? Etc. etc.

I am all for having a shorter function syntax. But I think it should fit into some bigger picture. If there is one here, it eludes me.

Why care? Why not just add it? Well, I would just point out that we can't use => because adding OpArrow seemed such a great idea a few years back (that said OpArrow makes it possible to express things in a way that didn't exist before and thus prevents a certain class of bugs). This is an opportunity for us to go back and reevaluate the wisdom of the current map literal syntax which not only became an obstacle for this addition, but also does not support empty maps (which is arguably a superficial problem but still smells like hell). No, we just continue. It is short-sighted and too high a price to pay for mere convenience.

Another case illustrating the cost of such additions is the shorter but otherwise completely redundant ETernary which prevents us from having ? as a unary operator, which could be used for example to allow for things like nullable?.prop, nullable?[index], nullable?(...args) or nullable? <op> other or some other concise syntax to deal with nullness. Just as a reminder: it also prevented us to just use the natural $e : $t for ECheckType and forced us to go with ($e : $t) instead, which on top of it all gets parsed as EParens(ECheckType(e, t)).

All syntax comes at a cost, so it better be worth it. And I think the cost/benefit ratio can only be sustainable if new syntax also adds new semantics. This proposal doesn't and that's why I will plead one last time that it should either be reframed or rejected. But I guess I'm talking against a wall anyway. So let's just hope this won't become an obstacle when we actually want to add something meaningful to the language. Good luck to us all ;)

@RealyUniqueName

This comment has been minimized.

Member

RealyUniqueName commented Nov 28, 2016

@back2dos Actually the main reason for this proposal is described in the first bullet of "Motivation" part.
E.g. for our project short lambdas will dramatically improve readability, thus reducing cost of code review and maintenance.

@clemos

This comment has been minimized.

clemos commented Nov 28, 2016

I don't remember these other language features being discussed before.
If they had been discussed in enough details, then maybe people would have foreseen these issues you mentionned, and fixed them.
I think that's what we should do with the proposal at hand to avoid future flaws as much as possible.
This said, I don't think the proposal needs more sophisticated motivations than "it's a shorter way to define anonymous functions, which goes along particularly well with functional and asynchronous programming".

There are already quite a lot of issues raised by this proposal, regardless of the issues it will bring on its own; most of them seem related to how map literals, array comprehension and check-type have been implemented.
I agree these current issues probably need to be fixed before it's even possible to propose some clean and sustainable short lambda design.

@back2dos

This comment has been minimized.

Member

back2dos commented Nov 28, 2016

@RealyUniqueName "readability" is highly subjective. You might as well say "because it is good". It doesn't mean anything. In fact Nicolas used "readability" for years as an argument against short lambdas.

@Simn

This comment has been minimized.

Member

Simn commented Nov 28, 2016

The decision has been made, so let's not filibust this issue. We will discuss the exact schedule for this internally and announce it here. For now, I'll go ahead and merge this as an accepted proposal.

@Simn Simn merged commit 4850641 into HaxeFoundation:master Nov 28, 2016

@kevinresol

This comment has been minimized.

kevinresol commented Nov 29, 2016

@back2dos Then at least the Drawback section shouldn't be empty.

@0b1kn00b

This comment has been minimized.

0b1kn00b commented Nov 29, 2016

@Simn

This comment has been minimized.

Member

Simn commented Nov 29, 2016

You are free to discuss the merits of this, but the decision has been made and will not be overturned this way. That's why I think the energy is best spent elsewhere. Can't make everyone happy.

@ousado

This comment has been minimized.

ousado commented Apr 26, 2017

So what's the state of this?
ping Haxe Core Team @andyli @hughsando @nadako @ncannasse @Simn @waneck

@back2dos

This comment has been minimized.

Member

back2dos commented Apr 26, 2017

@nadako

This comment has been minimized.

Member

nadako commented Apr 26, 2017

I think the design is fine and since we already have an implementation proposal, I would say we should move this forward.

@Simn

This comment has been minimized.

Member

Simn commented Apr 26, 2017

Would probably help if the implementation proposal was sent as a PR... Preferably with a bunch of tests for "interesting" syntactic cases.

@ousado

This comment has been minimized.

ousado commented Apr 26, 2017

Alright. How should those tests look like? Establishing the equivalence of the expected ASTs using macros, as compared to the standard notation?

@Simn

This comment has been minimized.

Member

Simn commented Apr 26, 2017

That would be nice, but maybe it's too annoying to get right. I'd be content with some pseudo-tests that don't actually assert anything but would fail at typing-time in case something is parsed incorrectly.

@ousado

This comment has been minimized.

ousado commented Apr 26, 2017

There you go, let me know if something is missing, I mainly concentrated on the first argument, since that's not reusing parse_fun_param in all cases.
HaxeFoundation/haxe#6209

@fullofcaffeine

This comment has been minimized.

fullofcaffeine commented Apr 26, 2017

@ousado ZOMG, I can't even believe w're soon going to have short lambdas in Haxe, thanks!

@shalmu

This comment has been minimized.

shalmu commented Dec 21, 2017

Sorry to interrupt your computational processes, but r there any news upon the topic? Half the words in my .hx files are "function"! And in yours too! function function function function function function function function function function function function function function function function function function function function function function function function function function function function.

It's been a year, guys, come on!
"Following the process described in the README, we're at step 4." said Nadako more than a year ago.
So what, did you have a consensus or voting or any other ritual of yours to make it happen?

@nadako

This comment has been minimized.

Member

nadako commented Dec 21, 2017

It was implemented and available in Haxe 4.0 preview and git version.

@shalmu

This comment has been minimized.

shalmu commented Dec 21, 2017

Whoa, thanks Dan! Vivat Haxe!
I bet once 4.0 is in production, we'll see a substantial increase in Haxe popularity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment