You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Consider incorporating FSharp.Core.Fluent into FSharp.Core #1073
I'd like to open a discussion about incorporating FSharp.Core.Fluent into FSharp.Core. Or we could consider adding it as a second F# DLL referenced by default, with the option of turning that off.
The advantages of making this adjustment to F# are
reduced barrier to entry for beginners - xs.sum() relies only on dot notation, c.f. xs |> Seq.sum requires knowlesdge of |> and Seq, and knoweledge that input collections support Seq/IEnumerable programming
more discoverable code for simple things like summing a list
it's often more succinct
The disadvantages of making this adjustment to F# are
fluent collection programming has some downsides for clarity, performance
loss of pipeline debugging
multiple ways to do things
complicates library authorship
introduces camelCase naming to methods and dot notation
more type annotations are required
Extra information
Estimated cost (XS, S, M, L, XL, XXL): M
Related suggestions: (put links to related suggestions here)
Affidavit (please submit!)
Please tick this by placing a cross in the box:
This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
This is not a breaking change to the F# language design
I or my company would be willing to help implement and/or test this
For Readers
If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.
Reacted by Denis, Phillip Carter, Adelar da Silva Queiróz, vshapenko, Angel D. Munoz, Daniel Svensson, Max Enko, Dag Brattli, Kris, Paulmichael Blasucci and 14 more
Reacted by Charles Roddie, Dave Thomas, Anton Moldovan, Evgeniy Andreev, Kevin Malenfant, Vladimir Shchur, Josh M, Antonio Jr. Mattos, Joh Dokler, Urs Enzler and 44 more
Reacted by Igor Bagdamyan, Eugene Auduchinok, Evgeniy Andreev, Ambika E, Josh M, Vlad Zarytovskii, cannorin, Amin Khansari, Evgenii Grebeniuk and NicoVIII
Reacted by Lucas Teles
Reacted by Adelar da Silva Queiróz and Lucas Teles
I'm generally in favor of doing this, especially if debugging improvements come in with this motivating it. I would prefer that if this is done, it's just a part of FSharp.Core. Not a separate assembly.
This is clearly in violation of this principle:
whether this gives multiple ways to achieve the same thing
But, ehh, we've violated that one before. Yay for guidelines and not rules, eh?
I think it's worth going through who the intended audience is and why they would benefit from this.
Reacted by Angel D. Munoz, Thecentury and Lucas Teles
I generally love the idea (adding popular module functions as extension methods) but not a fan of camelCase method names, especially in the standard library. It may encourage people to write their methods in camelCase, which I think is not very good.
Reacted by Eugene Auduchinok, Prunkles and gparmigiani
I'm generally in favor of doing this, especially if debugging improvements come in with this motivating it. I would prefer that if this is done, it's just a part of FSharp.Core. Not a separate assembly.
I'm curious what kind of debugging improvements you foresee being motivated by this change? If you're right about it motivating debugging improvements, I suddenly have a strong reason to agree with this suggestion.
As it stands, the reasoning seems somewhat weak to me. I worry about the similarity/simplicity of the syntax taking attention away from the loss of pipeline debugging and performance that might result in overuse of this syntax. It would be a bit of a shame if newcomers to the language/paradigm make a habit of using a syntax that necessarily carries with it these cons when the alternative pipelining syntax is not so much more complicated.
Have others generally found that beginners struggle with adapting to pipelining? Or that developers in general struggle with discovery of module functions because of pipelining? Perhaps my perspective is skewed 😋
whether this gives multiple ways to achieve the same thing
Which may be confusing for users.
Also, question about official (and existing) docs and guides, will they favour one over another (fluent vs classic)? Shall we have an official "suggestion" what to use specifically?
Reacted by Eugene Auduchinok, Paul Biggar and Duskhorn
I'm curious what kind of debugging improvements you foresee being motivated by this change?
Since we now have pipeline debugging/stepping, it's a clear advantage to use them when diagnosing code. So imagine code like this:
m1(asdf).m2(fun x -> (* some code *)).m3(asdf2)
Where you can step into each piece of the call chain and subsequent statements/expressions within them. @dsyme sounds motivated to make that possible/better compared to today. It's an orthogonal improvement, but with this suggestion, a lot more impactful.
One of the things I've learned about programming languages (and software in general) is that when you have multiple ways of doing things, to only do that if the new ways can be done linearly.
The Fluent namespace is a linear change - people can choose to import it, and nobody expects every library and API to support it.
Moving it into FSharp.Core elevates it, saying "Fluent is a 2nd way of doing this thing". Tutorials and docs now need to cover it. People adding new top-level functions now need to make Fluent methods for them or else they're not as "first class" as the builtins (that is, they'll need to break up their tidy Fluent chains with an "ugly" function application or pipe). People now ask why there aren't Fluent versions of APIs, and packages get PRs adding Fluent versions of every function.
I personally like functions and using piping to compose them. But, if you/the community thinks Fluent is better, I would suggest changing the language to have Fluent be the one true way of doing things. One way is better than two ways.
If switching to fluent doesn't sound good, I would suggest instead improving functions so that they don't have the disadvantages you perceive. For example, for
more discoverable code for simple things like summing a list
VSCode (etc) could be made suggest a function with the appropriate type when you press '.'; after the completion, it would replace the . with a |>
conceptually simpler options for beginner code, xs.sum() relies only on dot notation, c.f. xs |> Seq.sum requires knowlesdge of |> and Seq
I do not believe that it is easier for beginners to learn two things than one thing (or to learn one thing only to learn that no-one does that - of course if people start to do that, now you have two things). The question of when to use functions vs methods is already challenging and not well addressed, this change would make it worse, imo.
TL;DR: pick a lane 😊
Reacted by Evgeniy Andreev, Ambika E, Vlad Zarytovskii, Josh M, Joh Dokler, Urs Enzler, amieres, Constantin Tews, Oskar Mathieu Gewalli, Texas Toland and 31 more
VSCode (etc) could be made suggest a function with the appropriate type when you press '.'; after the completion, it would replace the . with a |>
I actually have the beginnings of something along those lines (intellisense for pipelines) here for any F# editor, but it's super early stage and will involve some tricky stuff that's currently beyond me to do in my spare time. Any help appreciated 😄
This is good feedback though, and I'd love to have more written out regarding this to offer more
I think it's worth going through who the intended audience is and why they would benefit from this.
I more immediately think of python developers working with collections for the first time. Maybe they would appreciate FSharp.Core.Fluent as a style, or maybe they would be satisfied with better tooling with pipelines.
Reacted by David Dawkins, Matteo Taveggia, Karthik K Selvan, Niklas, Sean and Duskhorn
I wonder if there's a way to support better autocomplete FSAC tooling for the F# syntax instead - I would prefer that over added language complexity. This could be triggered by typing |> -- maybe also by newline.
Reacted by Shawn Martin, Brian Rourke Boll, Kent Boogaart, Gibran Rosa, Dominik Janiec and Duskhorn
more immediately think of python developers working with collections for the first time. Maybe they would appreciate FSharp.Core.Fluent as a style,
Another way to look at this is that when people see that .sum works, they would expect that the autocomplete options they are offered are the complete set. This would probably lead them to conclude that the standard library is very sparse.
I can see why this is tempting.
However, I'm not too fond of this suggestion. As mentioned above, expectations would quickly come that everything that can be done via a pipeline call is available via Fluent-style. Otherwise, there will be style breaks in the code: a.foo.bar |> zoo (if zoo is not available in Fluent-style). That would make code much harder to read - in addition to having two styles for the same.
The result is added needed effort for library authors (coding, documenting) and is hard to keep consistent.
If both would be available, I'd probably almost always use the pipeline variant because I think fewer type annotations would be needed.
Reacted by Vlad Zarytovskii, marcingolenia, Ambika E, Shawn Martin, pim, Nicolas Joseph More, Jordan Marr, Diego Gómez, Amin Khansari, Paul Biggar and 7 more
Alright, this adds multiple ways of doing things, but as a library that already exists, and not as a language syntax change. This makes the "multiple" argument mote in my view. Everybody can simply use the library right now. Isn't this what we're doing all the time anyway, taking advantage of F#'s power as an algebraic language to express things in less space and more elegantly?
In fact I see all the Cons as the other side of Pros. Some downsides doesn't mean it's bad. Loss of pipeline debugging, yes, but the alternative is there, and debugging can be improved. Library authorship must necessarily be affected. Why not introduce camelCase naming to methods and dot notation?
Very much not a fan. The simplicity and uniformity of F# has often been remarked upon as one of its strongest points, by outside observers as well.
This would compromise that simplicity in order to compensate for a weakness in the tooling - namely, that |> doesn't yet offer Intellisense suggestions. Using fluent style is already a thing for C# compatibility, but it's a subpar experience (type inference, the verbose fun x -> instead of x =>) and raising it to a core library makes the language look worse.
It's totally possible to design a functional language around the fluent style - look at Kotlin, which has the it keyword and several scope functions to improve the readability of fluent chains. But F# has none of those (I believe a more ambitious "make fluent style first-class in F#" suggestion, which included such improvements, would be controversial but it would look a lot better).
On the other side, F# has partial application, free-standing custom operators, and (at the tooling level) pipeline debugging and code lenses to support the pipeline style of coding; Kotlin lacks those, and so I would similarly oppose the introduction of |> in Kotlin.
I also agree with the criticism that third-party libraries would suddenly be expected to provide two aliases for every function, one in each style, and it would be extremely confusing for a new programmer to find that it's neither automatic nor a given.
Reacted by Ruben Bartelink, Eugene Auduchinok, Christopher-Marcel Esser, David Glassborow, mdip32, Niklas, Jon Tyson, OlegAlexander and Adrián Montesinos González
What do you mean by viable? Incorporating FSharp.Core.Fluent is very easy to do. Proper, discoverable, portable IntelliSense for pipelines is very hard to do.
Adding IntelliSense for pipelines seems like it would be nearly impossible since anything with the right shape could be piped in.
But I think part of the beauty of pipelining is that it's not constrained to only a handful of Linq methods.
Besides, learning to use the Seq, List and Array modules is likely in every F# day-one guide anyway, and it's also very intuitive IMO.
Adding IntelliSense for pipelines seems like it would be nearly impossible since anything with the right shape could be piped in.
I'm not sure I follow. If you know the type of the expression passed into the pipe, you would look for a function or var which takes that argument in it's final position.
We do this in Darklang, it required custom code for pipes but wasnt a challenging implementation.
Sure, but what scope would you expect it to search? Through your project maybe, but it could literally be anything in the entire framework, so it would have to index everything by the last position argument type. Seems kind of unlikely to me (and unnecessary).
Sure, but what scope would you expect it to search? Through your project maybe, but it could literally be anything in the entire framework, so it would have to index everything by the last position argument type. Seems kind of unlikely to me (and unnecessary).
I think it is reasonable to search only one level, that is offer Thing.thing whith the required type match
A challenge might be that all Seq and List offerings would be valid.
Adding IntelliSense for pipelines seems like it would be nearly impossible since anything with the right shape could be piped in.
Not impossible, but hard. There are several challenges, in increasing order of difficulty:
Adjusting how we determine a "completion context" for each kind of pipelining you can do (|>, ||>, |||>, >>)
Understanding which is the "last slot" in a given applicable item (function, ctor, method, etc.)
Getting the right kind of information about the thing being piped into something else (and constituent parts of a tuple to splat into functions)
Part of the reason why it's hard is that at the point where completion lists are generated, we aren't working with information that makes it easy to pull apart that information. But assuming that gets done, the next step is a relatively lengthy process of carefully applying the right filtering based on the information above and if the last slot(s) in the candidate list can "match", for some definition of "match" that will include:
Exact type matches
Generics
Subtyping rules
This would likely to go through a few release cycles and usage in the "real world" before it gets to a steady state. I have a very basic prototype that doesn't do this filtering and it's kinda useful, but since it just gives back the full list you still pretty much need to know what you want to do next by typing it out for the list to filter. Each kind of filtering slowly gets you to an ideal kind of list and it would take a few cycles to get that behavior to feel right.
There's some quirks to that, like if you have a generic value and you pipe. No reasonable filtering can be applied for the first one, but the function you pipe into could then influence filtering for the next one. I don't know if people would feel like that's weird or not.
So really it's a complicated thing that would take time to get "right". I think it should happen, which is why I have a branch that sets up basic stuff working and a framework for adding things...but if anyone thinks that this is anywhere close to approaching a similar implementation effort as the suggestion here, they'd be very much mistaken.
Sure, but what scope would you expect it to search? Through your project maybe, but it could literally be anything in the entire framework, so it would have to index everything by the last position argument type. Seems kind of unlikely to me (and unnecessary).
I think it is reasonable to search only one level, that is offer Thing.thing whith the required type match
A challenge might be that all Seq and List offerings would be valid.
Maybe you could have a short list of the most commonly piped core modules (like List, Array, Seq) that could be displayed at the top of completion list if they matched. Just showing the relevant modules (either List and Seq or 'Array and 'Seq) would be a nice jumping off point to show in the completion list instead of showing the many individual functions.
Below that, you could show individual functions that match within a reasonable scope (one level up, or maybe only within the currently opened modules).
Not impossible, but hard. There are several challenges, in increasing order of difficulty:
That all sounds like quite a challenge, and then there would also be performance considerations. Would my poor laptop sound like it was about to launch into space the first time I opened the completion list? 🚀
No. All the data used to gather a list is already brought into scope in F# editor tooling anyways. You can see what the "full" list is just by doing ctrl+space on a blank line in F# code -- that's every single item available to you at that point in your program unfiltered
Activity
cartermp commentedon Sep 9, 2021
I'm generally in favor of doing this, especially if debugging improvements come in with this motivating it. I would prefer that if this is done, it's just a part of FSharp.Core. Not a separate assembly.
This is clearly in violation of this principle:
But, ehh, we've violated that one before. Yay for guidelines and not rules, eh?
I think it's worth going through who the intended audience is and why they would benefit from this.
cannorin commentedon Sep 9, 2021
I generally love the idea (adding popular module functions as extension methods) but not a fan of
camelCasemethod names, especially in the standard library. It may encourage people to write their methods incamelCase, which I think is not very good.sudqijawabreh commentedon Sep 9, 2021
I think this well help with autocomplete since you write dot and then you can discover what operations you can apply.
rosalogia commentedon Sep 9, 2021
I'm curious what kind of debugging improvements you foresee being motivated by this change? If you're right about it motivating debugging improvements, I suddenly have a strong reason to agree with this suggestion.
As it stands, the reasoning seems somewhat weak to me. I worry about the similarity/simplicity of the syntax taking attention away from the loss of pipeline debugging and performance that might result in overuse of this syntax. It would be a bit of a shame if newcomers to the language/paradigm make a habit of using a syntax that necessarily carries with it these cons when the alternative pipelining syntax is not so much more complicated.
Have others generally found that beginners struggle with adapting to pipelining? Or that developers in general struggle with discovery of module functions because of pipelining? Perhaps my perspective is skewed 😋
7sharp9 commentedon Sep 9, 2021
Another con of fluent is more annotations are normally required, which sort of spoils the flow having to go back and add an annotation.
vzarytovskii commentedon Sep 9, 2021
It would be nice but as @cartermp said:
Which may be confusing for users.
Also, question about official (and existing) docs and guides, will they favour one over another (fluent vs classic)? Shall we have an official "suggestion" what to use specifically?
cartermp commentedon Sep 9, 2021
@rosalogia
Since we now have pipeline debugging/stepping, it's a clear advantage to use them when diagnosing code. So imagine code like this:
m1(asdf).m2(fun x -> (* some code *)).m3(asdf2)Where you can step into each piece of the call chain and subsequent statements/expressions within them. @dsyme sounds motivated to make that possible/better compared to today. It's an orthogonal improvement, but with this suggestion, a lot more impactful.
rosalogia commentedon Sep 9, 2021
@cartermp makes sense; thanks for elaborating!
pbiggar commentedon Sep 9, 2021
I don't like this at all! :)
One of the things I've learned about programming languages (and software in general) is that when you have multiple ways of doing things, to only do that if the new ways can be done linearly.
The Fluent namespace is a linear change - people can choose to import it, and nobody expects every library and API to support it.
Moving it into FSharp.Core elevates it, saying "Fluent is a 2nd way of doing this thing". Tutorials and docs now need to cover it. People adding new top-level functions now need to make Fluent methods for them or else they're not as "first class" as the builtins (that is, they'll need to break up their tidy Fluent chains with an "ugly" function application or pipe). People now ask why there aren't Fluent versions of APIs, and packages get PRs adding Fluent versions of every function.
I personally like functions and using piping to compose them. But, if you/the community thinks Fluent is better, I would suggest changing the language to have Fluent be the one true way of doing things. One way is better than two ways.
If switching to fluent doesn't sound good, I would suggest instead improving functions so that they don't have the disadvantages you perceive. For example, for
VSCode (etc) could be made suggest a function with the appropriate type when you press '.'; after the completion, it would replace the
.with a|>I do not believe that it is easier for beginners to learn two things than one thing (or to learn one thing only to learn that no-one does that - of course if people start to do that, now you have two things). The question of when to use functions vs methods is already challenging and not well addressed, this change would make it worse, imo.
TL;DR: pick a lane 😊
cartermp commentedon Sep 9, 2021
I actually have the beginnings of something along those lines (intellisense for pipelines) here for any F# editor, but it's super early stage and will involve some tricky stuff that's currently beyond me to do in my spare time. Any help appreciated 😄
This is good feedback though, and I'd love to have more written out regarding this to offer more
I more immediately think of python developers working with collections for the first time. Maybe they would appreciate FSharp.Core.Fluent as a style, or maybe they would be satisfied with better tooling with pipelines.
stweb commentedon Sep 9, 2021
I wonder if there's a way to support better autocomplete FSAC tooling for the F# syntax instead - I would prefer that over added language complexity. This could be triggered by typing |> -- maybe also by newline.
pbiggar commentedon Sep 9, 2021
Another way to look at this is that when people see that
.sumworks, they would expect that the autocomplete options they are offered are the complete set. This would probably lead them to conclude that the standard library is very sparse.ursenzler commentedon Sep 9, 2021
I can see why this is tempting.
However, I'm not too fond of this suggestion. As mentioned above, expectations would quickly come that everything that can be done via a pipeline call is available via Fluent-style. Otherwise, there will be style breaks in the code:
a.foo.bar |> zoo(if zoo is not available in Fluent-style). That would make code much harder to read - in addition to having two styles for the same.The result is added needed effort for library authors (coding, documenting) and is hard to keep consistent.
If both would be available, I'd probably almost always use the pipeline variant because I think fewer type annotations would be needed.
BentTranberg commentedon Sep 9, 2021
Alright, this adds multiple ways of doing things, but as a library that already exists, and not as a language syntax change. This makes the "multiple" argument mote in my view. Everybody can simply use the library right now. Isn't this what we're doing all the time anyway, taking advantage of F#'s power as an algebraic language to express things in less space and more elegantly?
In fact I see all the Cons as the other side of Pros. Some downsides doesn't mean it's bad. Loss of pipeline debugging, yes, but the alternative is there, and debugging can be improved. Library authorship must necessarily be affected. Why not introduce camelCase naming to methods and dot notation?
13 remaining items
piaste commentedon Sep 13, 2021
Very much not a fan. The simplicity and uniformity of F# has often been remarked upon as one of its strongest points, by outside observers as well.
This would compromise that simplicity in order to compensate for a weakness in the tooling - namely, that
|>doesn't yet offer Intellisense suggestions. Using fluent style is already a thing for C# compatibility, but it's a subpar experience (type inference, the verbosefun x ->instead ofx =>) and raising it to a core library makes the language look worse.It's totally possible to design a functional language around the fluent style - look at Kotlin, which has the
itkeyword and several scope functions to improve the readability of fluent chains. But F# has none of those (I believe a more ambitious "make fluent style first-class in F#" suggestion, which included such improvements, would be controversial but it would look a lot better).On the other side, F# has partial application, free-standing custom operators, and (at the tooling level) pipeline debugging and code lenses to support the pipeline style of coding; Kotlin lacks those, and so I would similarly oppose the introduction of
|>in Kotlin.I also agree with the criticism that third-party libraries would suddenly be expected to provide two aliases for every function, one in each style, and it would be extremely confusing for a new programmer to find that it's neither automatic nor a given.
wallymathieu commentedon Sep 13, 2021
Perhaps a lib generator that does the code gen for certain rules? So that you given a F# style module API get extensions?
Happypig375 commentedon Sep 13, 2021
Let's just add automatic IntelliSense for
Seqmodule functions when we type|>after a seq type.cartermp commentedon Sep 13, 2021
@Happypig375 much easier said than done :)
Happypig375 commentedon Sep 13, 2021
@cartermp Still this would be more viable than adding fluent methods :)
cartermp commentedon Sep 13, 2021
What do you mean by viable? Incorporating FSharp.Core.Fluent is very easy to do. Proper, discoverable, portable IntelliSense for pipelines is very hard to do.
JordanMarr commentedon Sep 13, 2021
Adding IntelliSense for pipelines seems like it would be nearly impossible since anything with the right shape could be piped in.
But I think part of the beauty of pipelining is that it's not constrained to only a handful of Linq methods.
Besides, learning to use the
Seq,ListandArraymodules is likely in every F# day-one guide anyway, and it's also very intuitive IMO.pbiggar commentedon Sep 13, 2021
I'm not sure I follow. If you know the type of the expression passed into the pipe, you would look for a function or var which takes that argument in it's final position.
We do this in Darklang, it required custom code for pipes but wasnt a challenging implementation.
JordanMarr commentedon Sep 13, 2021
Sure, but what scope would you expect it to search? Through your project maybe, but it could literally be anything in the entire framework, so it would have to index everything by the last position argument type. Seems kind of unlikely to me (and unnecessary).
dsyme commentedon Sep 13, 2021
I think it is reasonable to search only one level, that is offer
Thing.thingwhith the required type matchA challenge might be that all
SeqandListofferings would be valid.cartermp commentedon Sep 13, 2021
Not impossible, but hard. There are several challenges, in increasing order of difficulty:
|>,||>,|||>,>>)Part of the reason why it's hard is that at the point where completion lists are generated, we aren't working with information that makes it easy to pull apart that information. But assuming that gets done, the next step is a relatively lengthy process of carefully applying the right filtering based on the information above and if the last slot(s) in the candidate list can "match", for some definition of "match" that will include:
This would likely to go through a few release cycles and usage in the "real world" before it gets to a steady state. I have a very basic prototype that doesn't do this filtering and it's kinda useful, but since it just gives back the full list you still pretty much need to know what you want to do next by typing it out for the list to filter. Each kind of filtering slowly gets you to an ideal kind of list and it would take a few cycles to get that behavior to feel right.
There's some quirks to that, like if you have a generic value and you pipe. No reasonable filtering can be applied for the first one, but the function you pipe into could then influence filtering for the next one. I don't know if people would feel like that's weird or not.
So really it's a complicated thing that would take time to get "right". I think it should happen, which is why I have a branch that sets up basic stuff working and a framework for adding things...but if anyone thinks that this is anywhere close to approaching a similar implementation effort as the suggestion here, they'd be very much mistaken.
JordanMarr commentedon Sep 13, 2021
Maybe you could have a short list of the most commonly piped core modules (like
List,Array,Seq) that could be displayed at the top of completion list if they matched. Just showing the relevant modules (eitherListandSeqor 'Arrayand 'Seq) would be a nice jumping off point to show in the completion list instead of showing the many individual functions.Below that, you could show individual functions that match within a reasonable scope (one level up, or maybe only within the currently opened modules).
JordanMarr commentedon Sep 13, 2021
That all sounds like quite a challenge, and then there would also be performance considerations. Would my poor laptop sound like it was about to launch into space the first time I opened the completion list? 🚀
cartermp commentedon Sep 13, 2021
No. All the data used to gather a list is already brought into scope in F# editor tooling anyways. You can see what the "full" list is just by doing
ctrl+spaceon a blank line in F# code -- that's every single item available to you at that point in your program unfiltereddsyme commentedon Sep 13, 2021
@cartermp
We already do this filtering for extension methods, see
IsApplicableMethApproxhttps://github.com/dotnet/fsharp/blob/c88b79509989ba524c41958d6d96a45951344550/src/fsharp/service/FSharpCheckerResults.fs#L469