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

Make the fun keyword optional #168

Open
baronfel opened this issue Oct 20, 2016 · 170 comments
Open

Make the fun keyword optional #168

baronfel opened this issue Oct 20, 2016 · 170 comments

Comments

@baronfel
Copy link
Contributor

baronfel commented Oct 20, 2016

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

@dsyme dsyme removed the open label Oct 29, 2016
@ncave
Copy link

ncave commented Oct 31, 2016

+1 to fat arrows, all in the name of driving adoption for the language.

@cartermp
Copy link
Member

Interesting discussion. I wouldn't support something along the lines of Haskell or Elixir-style lambdas, because instead of shortening what's already there, that would replace it with a new symbol.

I'm also unsure if fat arrows would be needed - is it enough to simply make fun optional? E.g.

Seq.filter (x y -> something x y)

@cloudRoutine
Copy link

cloudRoutine commented Oct 31, 2016

sticking with -> leads to situations like

let fn x y =
    match x with 
    | 1 -> z -> x + y * z
    | _ -> (z -> x - y * z)
// ^ I guess parens would be required here?
let fn': int -> int -> int = x y -> x + y

I'm ambivalent about this approach, => seems better than that
I don't mind fun though

@cartermp
Copy link
Member

cartermp commented Nov 1, 2016

Good point, => would make cases like that unambiguous.

@gusty
Copy link

gusty commented Nov 1, 2016

Using an available user-defined operator will break existing code, specifically => is being used in some projects, for instance Deedle makes heavy use of it.

The ambiguity reported by @cloudRoutine can be solved since we never match against functions, I mean we can expect a pattern between | and ->.

Eventually if a further enhancement allows to decompose a function (if it ever makes any sense) the fun keyword will not be optional in that context.

Maybe even the function keyword can be made optional. Cases like

let f x = function | (Some x) -> ... | None -> ...

become

let f x = | (Some x) -> ... | None -> ...

but that for sure will confuse any Haskeller.

@charlesroddie
Copy link

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 ↦.
~> doesn't seem too bad if we need something different.

@Rickasaurus
Copy link

Rickasaurus commented Jul 21, 2017

I don't hate this, but I don't love it either. Do you really think the small change will bring over people? I've never heard anyone say "I don't use F# because of the fun keyword". The grammar is also a bit more ambiguous and so difficult to parse, so it will likely always have to be in parens, saving one character on average.

We don't currently use \ to mean anything, so why not gift it from haskell if fun is too much?

I do rather like the idea of dropping the "function" keyword though. It's obnoxiously long.

@sonhanguyen
Copy link

sonhanguyen commented Jul 22, 2017

+1 for this, not only fun makes us type more it also creates inconsistency in keywords? Why do some have to be a full word (match) while this one gets abbreviated? Because it's fun?

@wiwiwa
Copy link

wiwiwa commented Jul 28, 2017

+1

But more like Scala's lambda shorthand form Seq.filter(_>1)
By the way, proposal of lambda using => has already been declined in #384

@matthewcrews
Copy link

+1, if it is possible to make fun optional while still being unambiguous, it would be greatly appreciated.

@AnthonyLloyd
Copy link

When I look at my code I see a lot of lets on the left hand side that I think must be redundant. We could remove those too...I'm just joking I think.

@realvictorprm
Copy link
Member

I don't like to remove the 'fun' from F#. I would rather discuss about an alternative syntax for very short lambda expressions.

@MangelMaxime
Copy link

I like the fun keyword because it is easy to read. Sometimes having a lot of symbols make the code harder to read/maintain.

When reading fat arrow or lambda from others language like Javascript, elm or haskell I am always confuse and need time to understand the code.

@TIHan
Copy link

TIHan commented Nov 26, 2017

IMHO, we shouldn't add more ways to do the same thing in this regard. You will not gain that much from trying to shorten syntax; only lead to more inconsitent code across large projects. The property accessor from the other issue is kinda ok, but even then even that can lead to more inconsistencies of just doing the same thing.

@jichang
Copy link

jichang commented Nov 27, 2017

As @MangelMaxime said, less keywords and more symbols make code hard to read, plus, in editors, the fun keyword with appropriate color can easily tell the structure of code

@realvictorprm
Copy link
Member

realvictorprm commented Nov 27, 2017

@TIHan I agree on your point I admit that I'm in general against anything which reduces readability. It's such a strong and important feature of F# which I always use as selling point for beginners!
PS: I'm very against this!

@pchalamet
Copy link

I really would like fun to disappear of course!

Not sure -> alone would be the best replacement, => is easier to read especially considering match and function prototypes.

@vivainio
Copy link

The most annoing use for "fun" is calling methods or looking up properties in objects in pipelines (as they can't be curried away).

Kotlin style lambda with implicit "it" binding would be optimal:

{ it.DoStuff() }

{ i -> i + 1}

@vilinski
Copy link

A completion snippet for fun and function in ionide would be enough for me to solve such "problem". No need to make it shorter if it more clear what happens. Haskell's backslash or scala's underscores are nice but they are adding PROBABLY more perl into F#. Thanks anyway!

@AndreuCodina
Copy link

AndreuCodina commented Nov 27, 2017

@realvictorprm Instead of:
(fun x y -> x + y)

Proposal 1:
\(x y -> x + y)

shown with a font ligature:
λ(x y -> x + y)

Proposal 2:
{x y -> x + y}

shown with a font ligature:
λ{x y -> x + y}

Examples

[ 1; 2; 3; 4; 5 ]
|> map λ(x -> x + 2)
|> fold (*)
[ 1; 2; 3; 4; 5 ]
|> map { x -> x + 2 }
|> fold (*)

@alfonsogarciacaro
Copy link

For me it was unexpected to see so many opinions in favor of keeping fun, but that's the good thing of open language design: we can get a better understanding of community's opinion :) However, I'd like to see this suggestion implemented so I'll try to lobby a bit for it with some arguments:

  • Lambda syntax looks like a counter example in workshops. I mean we usually try to lure C# or JS people into F# with claims like "it's less verbose!" but then they have to write a pipe chain and it looks like much more code than a fluent API (LINQ) in those languages:
myList
|> List.map (fun x -> x + 1)
|> List.filter (fun x -> x < 5)
myArray
.Select(x => x + 1)
.Where(x => x < 5)
  • I'm not an expert in grammar parsing but I don't think it'd be an issue. I would like to see more examples where removing fun could make the code ambiguous (there's one by @cloudRoutine above). In my experience you already have to surround lambdas with parens most of the time (except in declarations/bindings let f = fun x y -> x + y where you can also use let x y = x + y), especially when you pass as them as arguments, either curried (see above) or tupled:
let foo (f: 'a->'b, x: int) = x
let y = foo(fun x -> 5, 6) // Doesn't compile
  • It's better to have one general solution than many specific ones. This is a common in F# language design (e.g. using CEs instead of adding a specific async mechanism to the language). This discussion has been revived because of the suggestion to allow _.Length for property accessor, but this would fit that and many other cases without introducing syntax for a very specific situation. I paste here the comparison from this comment:
xs |> List.map (fun x->x.Length)
xs |> List.map (x->x.Length)
xs |> List.map (.Length)
xs |> List.map _.Length

@AndreuCodina Ligatures are really nice (I've also seen fun being replaced by λ in emacs and looks awesome) but personally I don't think they should be taken into account for language design. Nowadays we read a lot of code in Github & friends so we shouldn't rely everything to our favorite editor (at least in terms of readability).

@eiriktsarpalis
Copy link

👎 If aesthetics is the sole motivator for this, I don't think it justifies the risk of introducing yet-to-be-discovered breaking changes.

@codybartfast
Copy link

I'm only a lightweight F# user, but I find 'fun' a small, verbose niggle.

When I switch between F# and C# I always use the wrong arrow for a little while, so for '.net' consistency I would like '=>'.

Given how much F# has inspired C#, I think it's only polite for F# to return the favour ;-)

@vilinski
Copy link

I don't need literally 100% but I really wanted to avoid seeing x -> y -> z -> x*y+z as much as is reasonably possible.

Just note, there is no reason to avoid x -> y -> z -> .... This is Currying and lambda calculus then related to Combinatory logic.

really? technically yes, but alot less key strokes without

@ken-okabe
Copy link

ken-okabe commented Jan 14, 2023

@Happypig375 @vilinski
Sorry, I should have been careful to mention this.
There is no reason to avoid x -> y -> z -> ...., yes, this is in theory; lambda calculus is based on unary functions.

However, conveniently, as a syntax sugar in F#, I suppose, we can write the identical mathematical entity as,

// Lambda expressions with parameter lists.
fun x y z -> ...

and since there is no syntax sugar in the type annotations, the type is like int -> int -> int -> int, and no one claims that reasonably, this type should be avoided.

Then, as proposed here, dropping fun, becomes

// Lambda expressions with parameter lists.
x y z -> ...

should be fine, but for

 (x y z -> ... )

This is not like making fun optional.

Do you suggest lambda without fun always requires the extra ( ) parenthesis?

@abelbraaksma
Copy link
Member

abelbraaksma commented Jan 16, 2023

Let’s reiterate: we know that dropping ‘fun’ on single-arg functions (which technically is every function), can be done unambiguously. This part is approved in principle, and we gotta start somewhere.

Syntax like a b c -> .., be it with or without parentheses, is ambiguous, esp if a or b are shadowing a variable holding a function value. fun introduces new scope, but parentheses do not, hence it being problematic. Turning this into a -> b -> c -> … would again be unambiguous, as it is the same as fun a -> fun b -> fun c -> ...

The single arg option can relatively easily be introduced without fun by virtue of -> not being an overloadable operator. It’ll require a little look-ahead, but it’s (probably) possible to be fixable in the lexer or parser phase (ie before the TAST is built). Multi-arg require a certain disambiguation, for instance, it may only be uniquely possible if there are no same-named variables in scope.

Also, don’t forget that each lambda argument is a pattern, which complicates potential determinism. Again, as mentioned before, I’d vote for focusing on the part we know is possible and accepted by @dsyme (single arg) and learn from that experience before we introduce the same idea for multiple args (if at all possible to begin with).

Example:

what about (x y z -> x + y + z)

@Happypig375 Won’t work if:

let x a = a * 42
let y = 12
(x y z -> x + y + z)

// function application will turn this into:
((x y) (z -> x + y + z))

// error that (x y) does not resolve to a function itself
// or error that ‘x + y’ is invalid because ‘x’ is a function and ‘y’ is 12

@Tarmil
Copy link

Tarmil commented Jan 17, 2023

The thing, though, is that this is already illegal:

x y fun z -> x + y + z
// error FS0010: Unexpected keyword 'fun' in expression

It's never been possible to pass a lambda as argument to a function without parentheses. So there's no reason that making fun optional should change that. I think it should be possible to make x y z -> x + y + z unambiguously equivalent to (fun x y z -> x + y + z).

@abelbraaksma
Copy link
Member

@Tarmil the problem is with function application. It has the highest precedence. Whereas fun has a much lower precedence. Dropping fun in multi arg pattern expressions (which is what args are) leads therefore to a conflict. That’s also why you get an error above, even though fun a -> fun b -> fun c -> .. would’ve been fine. You need parentheses because fun has lower precedence than function application.

Its similar to f x + y. First f x is evaluated, because of higher precedence, then + y.

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

@voronoipotato
Copy link

voronoipotato commented Jan 17, 2023

let x a = a * 42;;
//val x: a: int -> int
let y = 12;;
//val y: int = 12

fun x y z -> x + y + z;;
//val it: x: int -> y: int -> z: int -> int

(fun x y z -> x + y + z) 3 4 5;;
//val it: int = 12
(x y z -> x + y + z) 3 4 5;;
//desugars to (fun x y z -> x + y + z); you'd need parentheses for other reasons anyways. 

@abelbraaksma Everything between ( and -> would be an argument, just like how it is for fun. It does mean there's no way to type funless lambdas without parentheses, but that seems like a preferable choice over x -> y -> z -> x + y + z everywhere. It's just noisy and beginners need a single minute to understand the syntax before being thrown into currying. While currying isn't complex, it is "weird" to people who were trained in OO first like non-js devs.

@abelbraaksma
Copy link
Member

@voronoipotato in your example, the issue still isn’t resolved. ( doesn’t introduce scope, fun does. So, (space) is still taking precedence. Your code isn’t necessarily ambiguous, but the function x will be evaluated first. So, order of precedence is ((((x y) z -> x + y + z) 3) 4) 5.

There may be a way to introduce preprocessing to overcome this issue and invalidate long standing precedence rules, but it’s likely gonna be very hard.

@voronoipotato
Copy link

voronoipotato commented Jan 18, 2023

X will not be a function, it just desugars to fun. It will not be hard since it's just syntax sugar where the first paren gets a fun put after it. I just tested this in fsi and i couldn't see how it breaks. When you type ( followed by -> it desugars to (fun followed by ->. There's no room for any function to be run or even interpreted.

Edit, there is one edge case i just thought of which is match statements but feel like you could look for the vertical bar.

@abelbraaksma
Copy link
Member

abelbraaksma commented Jan 19, 2023

But that’s not what your code shows. x was declared as a function: let x a = a * 42. And a rule like ( followed by -> sometime later is not how parsers work. Between function application and fun in terms of precedence are literally all expressions. This is no simple look-ahead.

  • first the full expression needs to be evaluated
  • then, the compiler needs to decide which of the expressions (here x, y or z) it’s going to invalidate and process instead as a pattern (which can have any level of nested parens)
  • then it needs re-evaluate the full expression again
  • repeat until you find a combination of pattern and expression that’s legal
  • repeat to find if other expressions are also legal
  • accept if there’s only one legal outcome , error otherwise

We can’t suddenly say “paren combined with -> is a new special token”, the grammar doesn’t work that way. Even if we wanted to do that, and we’d find a way, we’d still need to deal with any pattern for the lh side of ->, including nested parens, DU and re record deconstruction, and active patterns that themselves can have (active) arguments.

I know I’m rehashing what I’ve tried to explain before. Simple: one arg approach. Very hard, maybe impossible: multi arg approach.

I’d love it to be different, just trying to explain why, from a grammar & parser perspective, this is very hard, or impossible. I’d love be proven wrong, but no example or grammar rule adjustment I’ve seen so far has shown it’s possible. Try it by updating the lexing/yacc files of F#: I don’t think it can be done.

Which is why this has been accepted in principle by @dsyme: the part of this proposal dealing with single arg. No scoping needed, or disambiguation wrt in-scope let bindings by the same name.

@voronoipotato
Copy link

voronoipotato commented Jan 19, 2023

I personally think we should just experiment with it in the dumbest way possible, and see what breaks. I'm just going to make a fake step in a build after work tonight that blindly removes all "fun" and then does a find/replace for ( and -> injecting fun in , and I'll report back on what catches fire. I know it will be more complex in practice, but if it works or can be mitigated in this trivial example then there's hope for an analogous approach. I think you may be correct, and it's very likely my ignorance saying otherwise, but sometimes the "stupid" find bizzare meandering approaches that work because they were too "stupid" to learn that what they were doing was supposed to be impossible.

I really loathe the second order social consequences of making it single argument aka a -> b -> c -> a + b + c, and I'm pretty sure Don would hate it too if he saw it. You know people will do this. I would like for us to at least attempt or experiment with doing it correctly before we kick off some extremely cursed programming norms that will take a long time (or forever) to fix even after we get multi-argument fun-less lambdas.

@abelbraaksma
Copy link
Member

and I'm pretty sure Don would hate it too if he saw it

He proposed it himself, in this thread. It was what he ‘approved-in-principle’ explicitly.

I would like for us to at least attempt or experiment with doing it correctly before

That sounds like a great idea. And if some trial and error shows it isn’t as hard as I make it look, we can always go for the Full Monty. But if not, just sticking with the simple version for single arg would be a great step in the right direction.

@voronoipotato
Copy link

Well I don't doubt he approved it as is, I just would be surprised if he did it with the usage of chaining in mind. If he did consider that as a thing people do and didn't mind, then I don't care. I generally trust his judgement since the design so far has been pretty good at making bad things to do, hard to do.

@davidglassborow
Copy link

I personally think we should just experiment with it in the dumbest way possible, and see what breaks. I'm just going to make a fake step in a build after work tonight that blindly removes all "fun" and then does a find/replace for ( and -> injecting fun in , and I'll report back on what catches fire.

How did you get on ?

@voronoipotato
Copy link

Had personal things come up last night, thank you for reminding me. I'll give it a go tonight. Any suggestions on the repo I should try it on?

@abelbraaksma
Copy link
Member

This should probably go in the lexer, if possible, or as a postfix in the parser, fixing the AST.

@voronoipotato
Copy link

dealing with an ulcer bigger than a quarter, so it might take me a minute. I'm mostly sleeping in my free time right now, but maybe next weekend when I'm hopefully??? feeling a little better.

@davidglassborow
Copy link

oh no, hope you feel better soon

@voronoipotato
Copy link

@davidglassborow Starting to feel better, planning to try it tonight.

@voronoipotato
Copy link

voronoipotato commented Jan 31, 2023

@abelbraaksma @davidglassborow I played with it some, given my current understanding, I am having trouble finding any ambiguous situation. At least so long as there is a ( before where the fun would go. fun without a preceding ( is very rare and is nearly always preceded by an operator. The only tricky thing in my very dumb way of doing it was type signatures because I had difficulty selecting for : in my regex but with an actual compiler that wouldn't be as hard. I used FSharpPlus for my repo to test against because if there was an edge case I figured I'd find it there.

I removed all (fun managed to catch 1023/1682 of them with two very simple regex, and I wasn't really able to find cases where it put fun somewhere it shouldn't, just places where I failed to put it. The one exception was where I put fun in a type signature because of SRTP.

The fun I didn't remove , around 300 or so, were always preceded by an operator, comma, or whitespace. While I understand this isn't the same thing as a real attempt, this is what I'm currently capable of given my current understanding of compilers. I am honestly astonished with the regularity of lambdas as I was expecting to find a scenario that clearly would not work.

(?!\sof)([\w\s]+)\(((?!match)(?!fun)(?!function)\s*[\w\s]+)->
$1(fun $2 ->
\(\(\)\s->
(() ->

@abelbraaksma
Copy link
Member

abelbraaksma commented Jan 31, 2023

At least so long as there is a ( before where the fun would go
I am honestly astonished with the regularity of lambdas as I was expecting to find a scenario that clearly would not work.

I'm not too surprised, tbh. Wherever you replace (fun x y ... -> ...) with (x y ... -> ...), there cannot be ambiguity, simply because (...) itself removes ambiguity.

The trick is to find out if it is possible to change things like return a, b, fun x y z -> ... with return a, b, x y z -> ..., which I think is not possible. Here, x y z, per the present rules, would become function applications. Likewise, it's somewhat unclear whether a, b belongs to the arguments of ... ->....

But I'd be rather happy if we can get somewhere, with what you've showed already, to have:

  • fun is optional with any amount of arguments
  • in most cases, this will require parentheses to be added for disambiguation
  • in some cases, most likely single-arg cases, parentheses won't be needed
  • all cases where parentheses were already present, will (of course) just work, unless perhaps if more complex pattern expressions are involved

@daveyostcom
Copy link
Contributor

Another wrinkle to keep in mind. This

    |> Seq.filter (fun x -> x.IsEmpty())

could be allowed to shed the parentheses,

    |> Seq.filter fun x -> x.IsEmpty()

It seems to me that there is no ambiguity in losing the extra clutter here, on the last argument.

Swift does a similar thing to simplify the syntax of a lambda that appears as the last argument.

@vzarytovskii
Copy link

Another wrinkle to keep in mind. This

    |> Seq.filter (fun x -> x.IsEmpty())

could be allowed to shed the parentheses,

    |> Seq.filter fun x -> x.IsEmpty()

It seems to me that there is no ambiguity in losing the extra clutter here, on the last argument.

Swift does a similar thing to simplify the syntax of a lambda that appears as the last argument.

I think that should be a separate suggestion, it's better we keep an existing one for one very specific feature (omitting fun keyword).

@daveyostcom
Copy link
Contributor

I think that should be a separate suggestion, it's better we keep an existing one for one very specific feature (omitting fun keyword).

Yes I’m just pointing out possible wider ramifications of making fun optional.

@profK
Copy link

profK commented Apr 15, 2023

I'm teaching a class in F# next year and will need to call C# libraries.
This is particularly awkward when building adapter layers.

A friend of mine suggested this, which I like:
csObj |> _.MethodToCall

@vzarytovskii
Copy link

I'm teaching a class in F# next year and will need to call C# libraries.
This is particularly awkward when building adapter layers.

A friend of mine suggested this, which I like:
csObj |> _.MethodToCall

Underscore shorthand is a separate suggestion

@smoothdeveloper
Copy link
Contributor

Reading fun all day lifts my mood, I think this is unstated significant feature of F#.

With underscore, it already saving a lot of this typing where fun may look like noise, but overall, there is no need to slash fun from programming in F#.

@abelbraaksma
Copy link
Member

abelbraaksma commented Mar 18, 2024

I'm teaching a class in F# next year and will need to call C# libraries.
This is particularly awkward when building adapter layers.
A friend of mine suggested this, which I like:
csObj |> _.MethodToCall

Underscore shorthand is a separate suggestion

@profK, this has been implemented and is now part of F# 8:

let readOnlyFiles = 
    System.IO.DirectoryInfo("path").GetFiles() 
    |> Seq.filter _.IsReadOnly // shorthand

@ken-okabe
Copy link

ken-okabe commented Mar 31, 2024

In fact, I'd like to withdraw my previous statements because I'v noticed fun_ keyword works pretty well under the condition of F# 4 spaces indent.

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

No branches or pull requests