fsharp / fslang-suggestions

The place to make suggestions, discuss and vote on F# language and core library features
346 stars 21 forks source link

Make the `fun` keyword optional #168

Open baronfel opened 8 years ago

baronfel commented 8 years ago

Submitted by Jorge Fioranelli on 3/21/2014 12:00:00 AM
271 votes on UserVoice prior to migration

Make the `fun`` optional. Otherwise it is more verbose than C#.

Original UserVoice Submission Archived Uservoice Comments

cartermp commented 4 years ago

Note that there isn't much agreement that a new syntax is used. This is about making the fun keyword optional in lambdas, which use the -> syntax. We wouldn't want users to have to balance two different syntaxes. Simplifying an existing syntax (which we have done before) is fine though.

lenzenmi commented 4 years ago

I'm starting to see some more issues related to this now that I'm looking deeper. #634 #506

It seems from comments like this https://github.com/fsharp/fslang-suggestions/issues/634#issuecomment-468430210 that possibly an implementaiton would help move this along.

I see the argument for making it optional, and I see the argument for using fat arrows. In my opinion it'd be bad to do both, so I'm considering them effectively mutually exclusive choices. I expect that's one of the sticking points for why this hasn't gone anywhere over the past few years.

To my eyes, the strongest reason not to adopt the optional fun is readability. Eg: https://github.com/fsharp/fslang-suggestions/issues/634#issuecomment-356406036

match operation with
| Add n -> (+) n
| Sub n -> x -> x - n
| Collatz -> x -> if x % 2 = 0 then x / 2 else x * 3 + 1

vs

match operation with
| Add n -> (+) n
| Sub n -> x => x - n
| Collatz -> x => if x % 2 = 0 then x / 2 else x * 3 + 1

I find the second form much more readable.

The opposite is probably true in this case

let addOne = x -> x + 1 vs let addOne = x => x + 1

Yet these look good to my eyes:

let addOne = (x => x + 1)
| Sub n -> (x -> x - n)

Maybe there's some style convention (whether enforced by the compiler) that would make either of these choices just fine.

Anyways, it seems I still have more questions: 1) To move this forward, is the correct next-step to hammer out the syntax and get agreement? 2) Is the correct next step to pick one, implement it, and have it generally accepted so we can go on to a formal RFC to hammer out the exact language syntax? 3) Is this the right issue to have this discussion? I chose it because it's the second most popular across all issues.

cartermp commented 4 years ago

To move this forward, is the correct next-step to hammer out the syntax and get agreement?

The next step is for @dsyme to either approve or deny. That said, any sample that shows a potential implementation can help, as can a sample design. Generally speaking it's a lot better to go into an implementation with a proposed design than try to create a design out of a proposed implementation.

There's no additional or alternate syntax that we would accept here. The fun keyword would simply just be optional. There's a case where that can be ugly, but it's probably also rare enough that it might not matter thouhg.

RealmPlume commented 4 years ago

let addOne = (x => x + 1) | Sub n -> (x -> x - n)

I agree. When using parentheses, fun can be omitted. When the parentheses are omitted, fun is required. Use two different way to determine the scope of lambda, fun to the end, or between parentheses.

abelbraaksma commented 4 years ago

I don't think it's possible to get rid of fun without introducing ambiguities. Esp in cases where there's an operator defined that itself returns a function. Unless we allow disabling the feature in presence of such function (which poses an implementation challenge, parsing happens before functions overload resolution and library loading).

charlesroddie commented 4 years ago

Can you give an example of such an ambiguity @abelbraaksma ?

abelbraaksma commented 4 years ago

@charlesroddie, consider current code:

let f x y = fun z -> x >> fun y -> z >> y

Then, let's ditch the fun:

let f x y = z -> x >> y -> z >> y

What is y? In the first example, y is out of scope and basically a dummy param. In the second example, it isn't (or at the very least, it appears it isn't). Furthermore, one could argue that the second example has unclear operator precedence rules (though that could be covered in the parser, surely).

A similar example is this:

let f g x = x + 42 |> fun x -> g x

Becomes:

let f g x = x + 42 |> x -> g x

TBH, before I wrote that, I thought you could override -> as an operator. Not sure why I thought that, never really tried it. As it turns out, you can create an operator function for ->, it is recognized and compiled correctly, but you just cannot use it (which certainly takes away one of the chances of ambiguity):

image

It becomes a whole lot trickier if we drop the fun keyword in a recursive function (and with recursive modules and namespaces, this may not be just a remote chance of happening). Imagine what happens if we remove fun in the following example:

let rec r a b = fun r c -> a + b

Or what about DU deconstruction vs function call? Currently not an issue. But since function call has highest precedence, this becomes interesting to desugar:

type Function = Function of (int -> int)
let Function = id

let f = fun (Function x) -> x   // clear: deconstruction
let g = Function x -> x   // unclear, currently illegal, but binds as function call

Anyway, this was just off the top of my head. With a little effort, I'm sure more would arise. For instance, consider deconstruction syntax on the lh-side of the -> operator, or tupled vs non-tupled arguments.

gusty commented 4 years ago

@abel very interesting analysis.

In your last snippet, you remove fun but also remove the parens, the translation without fun should be let g = (Function x)-> x.

To me in all the rest of your snippets I can put back the fun by following the rule look for the -> then count identifiers back until finding something else (ie another operator) or nothing ,There goes the fun.

Having said that, this is not my favorite suggestion, I mean it's probably lot of work which doesn't buy us any new functionality. It just saves us 4 keystrokes. In the best case I would consider it low priority.

abelbraaksma commented 4 years ago

look for the -> then count identifiers back until finding something else (ie another operator) or nothing ,There goes the fun.

Problem with that is that you don't know beforehand whether these are arguments of the current implicit fun, or whether they are function calls, because one of the highest-priority operators in F# is the ` (space) and it means: apply an argument to a function. Inlet rec r a b = r c -> a + bit's therefore ambiguous whether it'sr (c -> ...or(r c ->...`. And the minute we have to add parens, we lose the benefit.

Other (big) problem here is that you should be aware that the parser does not have any type information of any kind. It's only lexical analysis that must be able to disambiguate. While we have context-sensual keywords where analysis is postponed, we don't, afaik, have context-sensual absence-of-keywords. Not saying it's impossible, we can always build something, but it'll be hard if the most fundamental building block (functions) needs to be postponed for later analysis during type resolution.

The other issue is the scoping, as mentioned. fun introduces scope and is fundamental in that. The first two examples above have unclear scope after dropping fun.

I do see a (slight) advantage in cases where it is currently unambiguous that a lambda should be provided (i.e., if it is already surrounded by parens). But I totally agree with your point that this feature should be low-prio as it really doesn't anything except for three less keystrokes.

you remove fun but also remove the parens,

That was deliberate. But with the parens, the ambiguity doesn't disappear. Function would still be considered a function-call with current priority rules applied (the same "space trumps all" rule, unless priority added by parens, which, once applied as you did, makes the ambiguity only larger: the compiler first has to resolve what's inside the parens...).

codybartfast commented 4 years ago

This might be moot if the implementation is even harder than anticipated, but there may be value in succinct code beyond saving keystrokes.

However, I think the real value would be to make it easier to move to F# form C#, or to switch between the two.  I haven't used C# recently and I'm typing fun x -> without thinking about it and it seems natural and a trivial issue.  But when I first learnt F# (having been a C# monoglot) I repeatedly tripped over this and I tripped over it a few more times whenever I switched between C# and F#.  E.g., using x => and when that didn't work sometimes changing it to fun x => or x -> before finally getting it right. (No claims here of being a 10x programmer)

If x => were possible in F# I think it would completely remove this friction.  x -> would still remove some of this friction.

I’m in no position to judge the effort involved nor its importance relative to other features, but its value would not be in the functionality it adds but in the ease of use for the novice.  I would certainly have appreciated it when I started F#.

abelbraaksma commented 4 years ago

@codybartfast, while I sympathize with the issues of switching between languages, I don't think that "should look like C#" is generally a strong argument.

Likewise, I keep forgetting the semicolon when writing C#, and I wished they dropped it, but they won't. Or allow curlies for scope in F# instead of indentation.

The fact of the matter is that these languages are fundamentally different. Both in paradigm and in syntax. If you were to switch from Python to F# you'd have less issues with indentation driving the scope, but would still have the paradigm shift. Coming from Haskell, you'll probably find F# verbose. Coming from C# you wonder how to change state.

If x => were possible in F# I think it would completely remove this friction. x -> would still remove some of this friction

This is nearly impossible, given that => is a valid operator. Besides, it doesn't align well with existing ->, which cannot be dropped. However, people writing the C#-style lambdas accidentally, could be helped by improving existing tooling by offering a fix in the tooltip smart fixes list.

I'm not opposing dropping fun, and I agree that it's more than just saving a few keystrokes. However, currently most of the parsing is forward only and requires little to no backtracking. Removing fun is likely not impossible, but from what I've seen of the lexer and parser, is a big undertaking. And that's before making any decisions on how to deal with aforementioned ambiguities.

I may be wrong, in fact I hope I'm wrong, but I don't see this moving forward unless @dsyme can sign off on this "in principle", and we have a proof of concept that shows this is doable with moderate effort.

(personally, I see more in allowing implicit lambdas using shortcut syntax to get properties and record fields, as at least that solves a common use case, which is already a proposal elsewhere)

baronfel commented 4 years ago

It's not super hard to play whack-a-mole with the most common places that beginners would use the => lambda syntax and provide an editor codefix to replace the syntax: lambda

That could go a long way towards getting past this particular hurdle.

abelbraaksma commented 4 years ago

@baronfel that was quick, looks great!

smoothdeveloper commented 4 years ago

@baronfel the other I was pondering about this type of stuff as I was copying some sample C# code from documentation.

I felt, when F# tooling detects C# code, it should propose code fixes like above 😄 and show an error like "we know you probably pasted that from C# answer from StackOverflow"

codybartfast commented 4 years ago

@abelbraaksma Thank you very much taking the time address my point fully, even though I knew it was likely the arugments against would vastly outweigh the argumenet for, I still think there's value in making the 'losing' argument just in case :-) The only point I would disagree with is the suggestion that my argument was F# "should look like C#", in a sense it is the opposite. I think the problem in this instance is that F# already looks like C#, but isn't the same. I think the most peristant tripping points are not those where things are the same (e.g. dotting in objects) or completely different (e.g. function definitions) but rather where they kinda look alike but are different.

I'm sure tooling as suggested by @baronfel is the way to. Thanks again.

wallymathieu commented 4 years ago

I kind of like the fun keyword, though perhaps for the wrong reason? I feel like it encourages you to make small named functions using let.

realvictorprm commented 4 years ago

F# doesn't look like C#. The heck?

voronoipotato commented 3 years ago

I kind of like the fun keyword, though perhaps for the wrong reason? I feel like it encourages you to make small named functions using let.

I'd agree with you often, but there are times too when a lambda is the best tool for the job.

JustinWick commented 3 years ago

I don't care about saving typing (what percentage of the time is any coder really typing), but reading code is hard, so anything that's easy-to-read but reduces the linguistic overhead--like reading "fun" which has nothing to do with the semantics of the code, merely a syntax marker--is worth consideration.

We go from: foo |> List.map (fun b -> b |> bar) |> List.filter (fun b -> b > baz) |> List.reduce (fun a b -> qux a b) to: foo |> List.map (b => b |> bar) |> List.filter (b => b > baz) |> List.reduce (a b => qux a b) or maybe even: foo |> List.map (_ |> bar) |> List.filter (_ > baz) |> List.reduce (qux _1 _2)

This emphasizes the operations, and the => operator is easy enough to remember.

This is one of the few F# suggestions that I'm strongly in favor of. My only concern is tooling changes, but at some point the needs of the language and its users have to outweigh the convenience of the IDE devs. I'll happily buy a coffee/beer for whomever puts together the final PR.

MangelMaxime commented 3 years ago

@JustinWick When writing pipeline in general, I add new lines to help structure the code. I think the F# guide lines also consider it a good practice.

So the comparison would become:

foo 
|> List.map (fun b -> b |> bar) 
|> List.filter (fun b -> b |> baz) 
|> List.reduce (fun a b -> qux a b) 
foo 
|> List.map (b => b |> bar) 
|> List.filter (b => b |> baz) 
|> List.reduce (a b => qux a b) 
foo 
|> List.map (_ |> bar) 
|> List.filter (_ |> baz) 
|> List.reduce (qux _1 _2) 

I changed _ > baz to _ |> baz because I think this is a typo

About making fun keyword optional or not, for me it is a matter of personal preference. Personally like I mentioned in another comment, I like the fun keyword because symbols can quickly become cryptics.

I find the third option not intuitive:

In F# _ means, I don't care about this value but here it seems like we care about it because we use it.

And also, the third line is adding a new exception forcing the user to think: "Why is it written differently from the other lines?"

JustinWick commented 3 years ago

@MangelMaxime (Edit: forgot to tag you)

@JustinWick When writing pipeline in general, I add new lines to help structure the code. I think the F# guide lines also consider it a good practice.

I usually do as well if I at least three pipeline operators and I should have done that here. Thanks for taking the time to reformat, you are correct in that most solid F# programmers probably do the same.

So the comparison would become:

foo 
|> List.map (fun b -> b |> bar) 
|> List.filter (fun b -> b |> baz) 
|> List.reduce (fun a b -> qux a b) 
foo 
|> List.map (b => b |> bar) 
|> List.filter (b => b |> baz) 
|> List.reduce (a b => qux a b) 
foo 
|> List.map (_ |> bar) 
|> List.filter (_ |> baz) 
|> List.reduce (qux _1 _2) 

I changed ` > bazto |> baz` because I think this is a typo

About making fun keyword optional or not, for me it is a matter of personal preference. Personally like I mentioned in another comment, I like the fun keyword because symbols can quickly become cryptics.

I normally agree that too many symbols can become problematic (see the manifold misc-symbol variables in Perl), however the basic unit of a functional program is the lambda. This is the least bad thing you could possibly give a special symbol to, and the most important thing to make brief.

Polluting lambdas with unnecessary words is a great way to slow down reading of this kind of code, and Python is even worse about this. Let the math look like math, and names look like names, and let the programmer decide if they wish to emphasize noun or verb.

(NOTE: I was trying to keep the lambdas simple which unfortunately means that you could just pass the "bar" and "baz" functions without creating an additional lambda, but please imagine it's a more complicated.)

I find the third option not intuitive:

In F# _ means, I don't care about this value but here it seems like we care about it because we use it.

Actually it means "I am not binding this value to a name" which is exactly how I am using it here. The _ is already used as a placeholder for "unbound value", and this follows that convention absolutely. That doesn't mean the value is unimportant, only that the name is not necessary. We often give temporary variables names like "x" because their context makes their meaning evident, and _ allows us to skip the explicit binding process for meaningless names, and instead focus on the structure of what we are doing with that value.

You can already do what I describe in Scala without even bothering with the =>:

listOfIntegers.map(_ * 2)

See also the unbelievably prolific and convenient $_ in Perl.

And also, the third line is adding a new exception forcing the user to think: "Why is it written differently from the other lines?"

I wouldn't call this an "exception"--it's an extension of the existing convention. I do agree that this is a bit iffy, but if the user understands that _ means "value not bound to a name", it's easy to interpret _1 as "value not bound to a name 1" and likewise _2 as "value not bound to a name 2". Note that this convention is used in other languages/libraries--Boost uses it in the exact same way specifically for lambdas, and Scala does this with the anonymous fields in tuples.

The best thing we could possibly do to encourage a functional style is to remove as much friction and overhead as possible from defining functions, especially anonymous ones. Let me put into words only that which need be words, and leave the math to speak for itself.

julian-a-avar-c commented 2 years ago

I'm a C# user and I use LINQ and lambdas all the time. I started trying out F# because I heard it would let me take the jargon away. This was specially motivating when considering to interop them. But I don't know, the language still requires a lot of extra keywords and spacing, and this fun thing is really making me have to lean backward to make my code more readable.

I don't think I see the advantage to use F# here, specially with C# getting more and more functional-styled features.

I'm not nearly smart enough to contribute much to the conversation, but I think using a fat arrow, or even the _ syntax would be better. Otherwise it's like using lambda functions in python, no one wants to use them, list comprehensions are easier.

smoothdeveloper commented 2 years ago

@jaacko-torus, would your feeling be the same if the tooling was helping more?

Meaning, for example, that the editor would prompt fun _ -> ... for you in places you may be about to type it.

I personally am not bothered too much, there is more to F# than the lambda syntax, being a bit more verbose, in some specific contexts, than in C#.

julian-a-avar-c commented 2 years ago

@smoothdeveloper well no. Im just complaining about the noise. C# is always noisy, but it's getting better.

This is one feature would reduce half of that noise. Even if the IDE helped typing it, just reading them is a hassle.

It's like in JS how you can write function (a) { return a + 2; }, but you can also take the noise away and type a => a + 2. Or in C# and delegates. It's not just longer, the noise actually makes you have to look twice. I don't need more things to look twice at.

That said IDE tools are always great 😃

smoothdeveloper commented 2 years ago

In meantime, to me, F# is more (fun _ -> printfn ""; "fun")() than C# remains noisy new Func<string>(() => { Console.WriteLine(); return "noisy"; })(), but I think this suggestion has some attention.

I know the feeling, also, about noise 🙂, about conciseness seeming elusive due to some syntactic choices in F#, and the fact that some languages adopt great lambda syntax overall, but F# remains very concise and consistent.

Potential issues with the language grammar need to be investigated out for this suggestion to come true, one day; if you like the idea, and if this suggestion would get to "approved in principle" state, maybe you'd like to contribute to writing a RFC for it?

julian-a-avar-c commented 2 years ago

@smoothdeveloper Absolutely. I'm just a beginner, but I wouldn't mind giving it a go. I'm not sure how I would even go about it tho, could you point me to where I would have to look in terms of contributing documents, or what an RFC would look like? I apologize for the troubles.

lucasteles commented 2 years ago

I would love to see this, I always feel the fun keyword is a bit noisy, to be honest

About new people learning... Whenever I tried to teach F# to someone from another language they have disliked the fun keyword. Cleaner lambda syntax is familiar today (C#, JS, Dart, Scala, Java) and I don't believe this would hit F# legibility in a bad way for any means

ken-okabe commented 2 years ago

@abelbraaksma commented on Nov 25, 2020

@charlesroddie, consider current code:

let f x y = fun z -> x >> fun y -> z >> y

Then, let's ditch the fun:

let f x y = z -> x >> y -> z >> y

What is y? In the first example, y is out of scope and basically a dummy param. In the second example, it isn't (or at the very least, it appears it isn't). Furthermore, one could argue that the second example has unclear operator precedence rules (though that could be covered in the parser, surely).

A similar example is this:

let f g x = x + 42 |> fun x -> g x

Becomes:

let f g x = x + 42 |> x -> g x

TBH, before I wrote that, I thought you could override -> as an operator. Not sure why I thought that, never really tried it. As it turns out, you can create an operator function for ->, it is recognized and compiled correctly, but you just cannot use it (which certainly takes away one of the chances of ambiguity):

image

It becomes a whole lot trickier if we drop the fun keyword in a recursive function (and with recursive modules and namespaces, this may not be just a remote chance of happening). Imagine what happens if we remove fun in the following example:

let rec r a b = fun r c -> a + b

Or what about DU deconstruction vs function call? Currently not an issue. But since function call has highest precedence, this becomes interesting to desugar:

type Function = Function of (int -> int)
let Function = id

let f = fun (Function x) -> x   // clear: deconstruction
let g = Function x -> x   // unclear, currently illegal, but binds as function call

Anyway, this was just off the top of my head. With a little effort, I'm sure more would arise. For instance, consider deconstruction syntax on the lh-side of the -> operator, or tupled vs non-tupled arguments.


I agree. the simple omission and using fat arrows is careless implementation.

Of course, in JavaScript, x => x + 1 works and we easily tend to think the same goes in F#, however;

  1. we must add another arrow => on top of the current -> just for lambda expressions, that is redundant.
  2. and => is against math standard, @charlesroddie

    I don't like => since ⇒ means logical implication. The mathematical standard is ↦. However there doesn't seem to be a good way to write it in ascii. -> may be the best ascii approximation of ↦.

  3. F# avoid () and have the functionality of function application to f[SPACE]a.
  4. F# has many infix binary operators between functions unlike JavaScript/C#.

Having said that, I know fun x -> x + 1 syntax is from OCaml, and I feel this is really noisy, redundant, and confusing considering the rest F# syntax is so well-designed.

let f x y = fun z -> x >> fun y -> z >> y

This is really hard to read because in F#, [SPACE] has the functionality of a function application, unlike JavaScript/C#, so fun[SPACE]z reminds us of things at least.

Surely in VSCode, this appears like

image

or, as the pure lambda expression; let g = fun x -> fun y -> fun z -> x >> fun y -> z >> y

image

Obviously, this is a bad syntax, and that is why this proposal exists.


The proven rock-solid syntax in Haskell is \x -> x + 1

\ comes from λ, so in your brain, you read \x as "lambda x" instead of fun x.

Just replace it.

let g = \x -> \y -> \z -> x >> \y -> z >> y

image

This is good syntax. Concise, no extra [SPACE], easy to distinguish the placeholder letter \x from the value x, and most importantly, proven in Haskell, no mess guaranteed.

Extremely easy to implement. Just replace fun x to \x. Also, every F# developer can easily adapt to this.


I hope we will avoid reinventing the wheel in a bad way. Just leave the legacy fun x for backward compatibility. Just add new replacement option to write \x instead of the fun x. Please do not try to add extra messy stuff on the simple lambda expression, please respect KISS Principle, we are not as smart as we think to control extra complications.

abelbraaksma commented 2 years ago

@dsyme, you marked this as approved in principle recently. I assume that is for the original part of the proposal of dropping fun. However, I noted a few potential issues with that in terms of ambiguities arising with >> or similar composition or piping operators. They may be resolvable by prioritization rules.

In particular, I'm concerned about scenarios like this:

let f() = 
    let g x = x
    let h x y = x
    g >> h >> fun g h -> g h

    // dropping `fun`, what're these `g` and `h`? What's the precedence? Clearly, as a variable they're in scope
    g >> h >> g h -> g h

    // one interpretation
    (g >> h >> g) h -> g h

    // another interpretation
    (g >> h >>) g h -> g h

    // another interpretation, lh-side is a pattern (but this would be illegal and be a compile error)
    (g >> h >> g h) -> g h

The compiler can sort this out, and maybe such writing should raise a warning. Currently, fun introduces a new scope in its definition, but such scope is less clear here.

More ambiguity may arise with patterns used in the lambda function declaration.

ken-okabe commented 2 years ago

For those of you who want to try the new potential lambda syntax now, I have just released a VSCode extension as the proof of concept:

Lambda for fun F

https://marketplace.visualstudio.com/items?itemName=KenOkabe.lambda-for-fun-fsharp https://github.com/stken2050/vscode-lambda-for-fun-fsharp

Write Haskell style Lambda Expression in F#: \x instead of fun x

Before

After

Even in Comments

Of course, the direct feedback to my repo issue, not here. Thanks.


Having said that, looking at the actual VSCode presentation along with the automatic type annotation by F# Inoide extension, I confirmed the Before (current)

image spoils the readability, and which is my perspective. The backward compatible legacy fun_ will be preserved, I guess, so nothing to lose even for ones who still like the fun_.

konst-sh commented 2 years ago

Also find myself just omitting "fun" too often, and since we already have to mostly write anonymous functions in brackets its will look pretty natural to allow just omit it when the lambda is in brackets: ( x y -> stuff)

Happypig375 commented 2 years ago

@konst-sh Maybe we can simplify the arrow as well while we're here.

Happypig375 commented 2 years ago

Consider how functions use =. Wouldn't it be great if lambdas can use something like = as well? (But not ambiguous with equality)

Happypig375 commented 2 years ago

Lambda calculus uses \x.x to represent fun x -> x. Can we also adopt that?

ken-okabe commented 2 years ago

Also find myself just omitting "fun" too often, and since we already have to mostly wright anonymous functions in brackets its will look pretty natural to allow just omit it when the lambda is in brackets: ( x y -> stuff)

Just to let you know, omitting, an implicit thing is obviously a much harder task than an explicit thing. Like @abelbraaksma repeatedly mentions, fun_ or \ in Haskell lambda introduces a scope, at the same time, it clarifies that \x is a new placeholder. That is the way the current compiler/parser works.

For a simple scenario adding \ might make the coder feel noisy but it certainly removes the ambiguity for both the compiler and humans. Just fun_ is too noisy syntax in any expression I believe.

I really don't understand why some people prefer to guess things that must be extra work for our brains, and the same goes for compilers. That is the risk of complexities.

Like I did on my VSCode extension, adopting \x is a peace of cake. It's just a matter of Regex pattern match. On the other hand, omitting fun_ or / whatever is far from "natural". This is an extreme task for compiler implementation.

konst-sh commented 2 years ago

@stken2050 I'm not against your approach, \x ->... looks totally fine to me, just saying that most of the time in F# we see lambdas inside brackets and it starting to look like part of the syntax, and from this point "fun" looks really redundant. The advantage of brackets is that they naturally define precedence and scoping when lambda nested with operators. Regarding the parsing complexity - your approach looks simpler for sure, but I think this one should be as recognizable as pattern matching case. And finally this approach is not introducing any new syntax, it just the evolution of the existing one which would allow it to be easily adopted by the users.

@Happypig375

Consider how functions use =. Wouldn't it be great if lambdas can use something like = as well? (But not ambiguous with equality) I believe"=" wouldn't work here as using it you can't tell if this equation is equality or lambda: (x = y x). Would be nice to replace two symbol arrow with a single symbol, but I'm afraid this can make this syntax cryptic.

ken-okabe commented 2 years ago

@konst-sh Thanks, your post certainly clarifies the point here.

The bracket in C family languages such as C# Java JavaScript Rust is familiar to most of us. However, at the same time, depending on the bracket explicitly has a severe problem.
The reason JS/TS Bracket pair colorization becomes a must-have for years and is now the default feature of VSCode, is that we are no longer able to code for readability without it.

Since the C family depends on the brackets so much, there is nothing to do, and all they can do is colorized by IDE.

The virtue of F# or Haskell is carefully avoiding such a mistaken language design. The philosophy of the syntax is different.

For instance, Rust has modern syntax, but still, it's the C family language, so we must write as below:

Rust

fn main() {
  let z = {
    let x = 5;
    let y = 10;
    x + y
  };
}

In F#,

let main() =
  let z =
    let x = 5
    let y = 10
    x + y

It's good to have bracket-pair-free syntax for the F# coder. In F#, or Haskell, we can write

let f = \x -> \y -> x + y
f 1 2

not like C

f(1)(2)

F# auto-curry looks natural to have multiple parameters without bracket pairs https://fsharpforfunandprofit.com/posts/currying/

For lambda expression,

image

You probably insist the g function that is C style, but I think we should avoid bracket pairs as much as possible simply because it's rather against F# design philosophy. We should use an indent in F# to clarify the scopes.

PS. @GratianPlume and @pblasucci , thank you for downvoting my post, sometimes repeatedly. We would appreciate your opinion on human words, in the programming aspect because I think this is not voting but a discussion that reading others' thoughts.

konst-sh commented 2 years ago

@stken2050 The thing with brackets is that they are already there in most cases, all the lambdas in pipelines in F# are in brackets, this means that we will have to take in brackets any competing lambda syntax if we are not going to change global syntax rules and token precedence. This approach implies that having auto-curring you will normally not be writing lambdas in curried style explicitly: (a b c d -> a + b + c + d) - definitely shorter to me than your example with "g" function, even with all the brackets.

Happypig375 commented 2 years ago

@konst-sh I'd also like to have a simplification for this style:

SomeActionRunner <| fun x y ->
    // Action

Can we also do

SomeActionRunner <| x y ->
    // Action

?

RealmPlume commented 2 years ago

We can't just care about syntax coloring. How about this? image Is it still concise to use Haskell when there are a lot of ignored parameters?

And what about this? image Lambdas are usually passed in as parameters, I don't know how to omit the parentheses?

I don't think it's good to pursue absolute unbracketization, sometimes symmetry and neatness can make people feel safe in patterning, And I put a layer of parentheses above the latter function, although it is not needed. @stken2050

ken-okabe commented 2 years ago

@konst-sh First off, your statement "all the lambdas in pipelines in F# are in brackets" is not true, and the same goes to "we will have to take in brackets any competing lambda syntax if we are not going to change global syntax rules and token precedence."

We should be very careful when we discuss syntax, and I think you depend on your personal experience too much rather than facts.

The fact is, pipe operator itself has relatively lower precedence.

https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/symbol-and-operator-reference/#operator-precedence

image

So, let x = 1 |> fun a -> a * 2 is a valid syntax against your statement.

The higher precedence is function application.

image

In some typical pipeline usage, the below is not valid without brackets,

let squareAndAddOdd values =
    values
    |> List.filter fun x -> x % 2 <> 0
    |> List.map fun x -> x * x + 1

So, in this case, since the f x (function application) has higher precedence than |>, the compiler tries to evaluate List.filter fun x -> x % 2 <> 0 first. That is where syntax error occurs, not pipeline.

The error message is

[error FS0010: Unexpected keyword 'fun' in binding. Incomplete structured construct at or before this point in binding]

So obviously the compiler tries to evaluate List.filter(fun)


So the above is the fact base analysis. and from now on, it's my own prediction. According to the current "just omit fun" opinion, the code would go like this:

let x = a -> a + 1
let squareAndAddOdd values =
    values
    |> List.filter x -> x % 2 <> 0
    |> List.map x -> x * x + 1

In the prior code, somewhat safely detecting the error thanks to fun keyword because itself is not a value, however, in this case, x can be a value and List.filter x can be a valid syntax.

Of course, the following -> make the inconsistency, but the point is we definitely removed some safety net guaranteed by the keyword fun_ or \ in Haskell.

I am not sure, but again, my impression is people really like to re-inventing the wheel potentially in a bad way taking the unnessesary extra risk. My personal opinion is adopting the existing Haskell lambda syntax is much less noisy than the current fun_, extremely easy to implement and robust.

konst-sh commented 2 years ago

@stken2050

We should be very careful when we discuss syntax, and I think you depend on your personal experience too much rather than facts.

Sorry, I should have wrote that lambdas in pipelines are mostly in brackets, the case when you apply lambda directly with pipe operator is far not that frequent compared to the case when you pass higher order function partially applied with lambda. And in the last case the brackets are used with no alternative.

And the fact that compiler treats the keyword "fun" as a binding name if it is passed without brackets look exactly like syntax problem related with token precedence, even before operator precedence takes part, otherwise why would compiler try to bind keyword. How your syntax could make use without brackets in this case?

According to the current "just omit fun" opinion, the code would go like this:

It is not "Just omit fun", but rather allow to omit "fun" when in brackets, which is already very often case. And regarding your example - the compiler would detect the unknown syntax in the same way when it tries to use keyword as binding name, because there will remain only three cases when the arrow token will be allowed in the expression: in type annotations , in pattern matching and in lambda - nothing changes from this point.

ken-okabe commented 2 years ago

@konst-sh To me, I have to observe, unfortunately, some members here including you try to invent something new somehow, and I strongly oppose the attitude. That is my basic principle.

Writing lambda expression, here we don't have to invent something new at all, and we should not.

For instance, you mentioned earlier,

(a b c d -> a + b + c + d) - definitely shorter to me than your example with "g" function, even with all the brackets.

What is this?? There is no such a thing like (a b c d -> a + b + c + d) in math. Please avoid messing with the simple math.

You also try to screw the consistency of the bracket.

fun_ keyword is noisy, probably such an idea is shared among us, but having said that, we should not invent some new math concept, that is a bad attitude.

RealmPlume commented 2 years ago

Obviously pipe operator has higher priority than fun, and f x has higher priority than fun @stken2050

RealmPlume commented 2 years ago

Finding pure mathematical expression in F# is the same as finding reality in cartoons. In addition, it is too double-standard to talk about finding the feeling of mathematics while talking about getting rid of parentheses.

konst-sh commented 2 years ago

@stken2050

There is no such a thing like (a b c d -> a + b + c + d) in math. Please avoid messing with the simple math.

There is a thing like syntax, which we use to describe math concepts, everything you type on the screen is a syntax that somebody employed for his use, one shouldn't make the idol of it or mix it with the concept itself. The difference between ( -> ) and (\ -> ) is that the first is the modification of the existing syntax which makes lamda definition 3 symbols shorter and still keeps it recognizable for users, and the other introduces new syntax, which was not here before.

ken-okabe commented 2 years ago

Finding pure mathematical expression in F# is the same as finding reality in cartoons.

Sorry, I don't get it. what do you mean? What exactly does it relate to the syntax for lambda expression?

In addition, it is too double-standard to talk about finding the feeling of mathematics while talking about getting rid of parentheses.

It's not double standard.

Indent is a proven method to express math expression in Haskell and F# etc.

Surely, you have a choice not to employ indent, and purely depends on the bracket, that is one philosophy that F# as the philosophy did not employ. Taking advantage of indentation, which obviously takes the priority of readability.

ken-okabe commented 2 years ago

@konst-sh

Well, if you try to invent a new lambda expression, this thread is too small for you. Or, try to build your new programming language. Please do not try to insert such a thing into the current lambda expression. I would say off-topic.

The binary operator definition is as same as the function definition, which is expandable infinitely; however, what you claim here is lambda expression syntax itself. The layer is different.

abelbraaksma commented 2 years ago

I agree. This thread is about make the fun keyword optional, as in the title. The math side-discussion may be interesting, but is irrelevant, as this is about syntax. Same is true for trying to invent new syntax, like Haskell \x.x, fat arrows, or what have you. Those should go in separate language suggestions (as suggested above, for easy adoption from C#, fat arrows could be replaced by the editor).

The main focus should be, imo, to find out whether we can resolve possible ambiguities. If we compare it with how C# does it, they require parentheses for multiple arguments, i.e. (a, b, c) => .... Since that's used for tuples in F#, that seems to be a no-go, as with the ->, the lh-side can accept a pattern.

I think the easiest way to go is to make it optional for one-arg functions. Still, we'd have to consider order of operations, i.e. in match x with y -> z -> 42. Or worse, a function returning a function: a -> b -> c -> a + b + c would be short for fun a -> fun b -> fun c -> a + b + c, the latter being totally fine currently.

For multiple args, this gets tricky. This is totally fine now: fun a b -> fun b c -> fun c d -> a + b + c. But what the hack is this? a b -> b c -> c d -> a + b + c? Does this mean a takes a function b -> .... or is this right-to-left and the same as the one without fun?

cartermp commented 2 years ago

@abelbraaksma for the cases you raised, I think this would be the appropriate resolution:

The compiler can sort this out, and maybe such writing should raise a warning.

We have precedent already where we'll emit a warning when there's an ambiguity like this, and we can suggest to either add parentheses to disambiguate or use the fun keyword. I think what's gained in the majority case outweighs these cases.