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

Allow _.Property or (.Property) shorthand accessor functions #506

Open
wastaz opened this Issue Nov 3, 2016 · 268 comments

Comments

Projects
None yet
@wastaz

wastaz commented Nov 3, 2016

This is a feature that I would love to steal from Elm :)
In Elm, if I define a record as such

type Foo = { bar : int baz : int }

I then automatically get functions such as .bar and .baz that I can use as getters for this record. So instead of writing code like this

foo |> fun f -> f.bar |> (+) 5

I can write code like this

foo |> .bar |> (+) 5

While this may seem useless in this small example, it does make things a lot nicer in larger chains, for example if I have a list that I want to map over

fooList |> List.map .bar |> List.max

Note: While this works in Elm it wont work in F# since List.map.bar and List.map .bar is the same so in the F# version I guess this would have to either be List.map (.bar) or a different character than . should be used. For example List.map #bar or List.map :bar or something similar?

I feel that I am often writing these type of small functions and while it works, this type of construct would be a nice-to-have.

Basically what I would suggest is that .bar would be a special syntax that expands into fun a -> a.bar maybe by generating some type of generic function like this

let inline bar a = (^T : (member bar : 'b) a)

Pros and Cons

The advantages of making this adjustment to F# are

  • less unnecessary visual noise for a pretty common use case
  • improves on the experience when piping values
  • should not interfere with any existing code (at least I think)

The disadvantages of making this adjustment to F# are

  • introduces a special case in parsing code which might be hard to fit into the compiler in a nice way (Not sure about this, someone who knows more about this
  • may be seen as "magic" and be harder to understand?
  • two ways of doing the same thing

Affadavit (must be submitted)

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
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • I would be willing to help implement and/or test this
  • I or my company would be willing to help crowdfund F# Software Foundation members to work on this
@rojepp

This comment has been minimized.

Show comment
Hide comment
@rojepp

rojepp Nov 3, 2016

I really like this suggestion, but it would probably have to be something other than a dot.
List.map .baris the same as
List.map.bar.

rojepp commented Nov 3, 2016

I really like this suggestion, but it would probably have to be something other than a dot.
List.map .baris the same as
List.map.bar.

@wastaz

This comment has been minimized.

Show comment
Hide comment
@wastaz

wastaz Nov 3, 2016

Yea, I just realised the same thing and updated the proposal :)

wastaz commented Nov 3, 2016

Yea, I just realised the same thing and updated the proposal :)

@theprash

This comment has been minimized.

Show comment
Hide comment
@theprash

theprash Nov 3, 2016

Great suggestion. I love the way this works in Elm, but as already mentioned, this is complicated by F# ignoring whitespace between an identifier and the following dot.

Maybe these functions could be defined directly on the type and then always accessed with the type name prefix. This would also help with type inference by disambiguating between different record types with the same label (another problem Elm doesn't have due to structurally typed records). E.g.:

type Foo = { id : int }
type Bar = { id : int }

[{id = 1}] |> List.map Foo.id

The compiler already disallows defining an id member on Foo: The member 'id' can not be defined because the name 'id' clashes with the field 'id' in this type or module, so I think it should be possible to put something here without breaking changes.

Another advantage of this approach is that it doesn't require any syntax changes. It's slightly more verbose than what was originally asked for but seems to fit the general F# style. Maybe there are other pitfalls?

theprash commented Nov 3, 2016

Great suggestion. I love the way this works in Elm, but as already mentioned, this is complicated by F# ignoring whitespace between an identifier and the following dot.

Maybe these functions could be defined directly on the type and then always accessed with the type name prefix. This would also help with type inference by disambiguating between different record types with the same label (another problem Elm doesn't have due to structurally typed records). E.g.:

type Foo = { id : int }
type Bar = { id : int }

[{id = 1}] |> List.map Foo.id

The compiler already disallows defining an id member on Foo: The member 'id' can not be defined because the name 'id' clashes with the field 'id' in this type or module, so I think it should be possible to put something here without breaking changes.

Another advantage of this approach is that it doesn't require any syntax changes. It's slightly more verbose than what was originally asked for but seems to fit the general F# style. Maybe there are other pitfalls?

@theprash

This comment has been minimized.

Show comment
Hide comment
@theprash

theprash Nov 3, 2016

I've just realised that the expression Foo.id alone errors with Field 'id' is not static. So there is already something there that would need to be replaced without breaking it. I suppose some compiler magic would be needed to make it work.

theprash commented Nov 3, 2016

I've just realised that the expression Foo.id alone errors with Field 'id' is not static. So there is already something there that would need to be replaced without breaking it. I suppose some compiler magic would be needed to make it work.

@wastaz

This comment has been minimized.

Show comment
Hide comment
@wastaz

wastaz Nov 3, 2016

@theprash That could work, however I do believe that this should be possible to achieve even without having to declare the type (see the generic constraint function in my original post) which I think should be possible to work with in a nice way with the type inference. However, yes. The dot wont work, and it might be that the code is clearer by writing the typename instead of adding another symbol into the mix.

My hope was to be able to stick as close to the Elm implementation/sematics here as possible given that I really like it and it seems to be doable with the generic constraints (at least from my admittedly very limited point of view). But I can certainly see the point of doing it either way.

wastaz commented Nov 3, 2016

@theprash That could work, however I do believe that this should be possible to achieve even without having to declare the type (see the generic constraint function in my original post) which I think should be possible to work with in a nice way with the type inference. However, yes. The dot wont work, and it might be that the code is clearer by writing the typename instead of adding another symbol into the mix.

My hope was to be able to stick as close to the Elm implementation/sematics here as possible given that I really like it and it seems to be doable with the generic constraints (at least from my admittedly very limited point of view). But I can certainly see the point of doing it either way.

@cloudRoutine

This comment has been minimized.

Show comment
Hide comment
@cloudRoutine

cloudRoutine Nov 3, 2016

Collaborator

duplicate of #159, but this suggestion already has more detail so maybe we expand & extend this one instead?

I think an operator with non-standard semantics, similar to how the dynamic operator (?) can be used in a manner that lets you write unbound identifiers in the middle of an expression, e.g.

let inline (?) (src : 'a) (prop : string) : 'b =  src.GetType().GetProperty(prop).GetValue(src, null) :?> 'b
let x = "arg"?Length : int
> val x : int = 3

But what we'd want in this case is for the identifier following the accessor operator to be used in a static check against the members of the preceding type, like how it does in a SRTP member call.

I don't think this kind of feature should be limited to properties which are a subset of the more general problem of accessing the members of a type passed to a single argument lambda. As such it should support methods as well.

#bar is no good, it'll clash with preprocessors
:bar is no good, it means the type of the preceding expression is bar

Potential Accessor Operators -

 ( @. )     @.Data  
 ( .@ )     .@Data  
 ( @| )     @|Data  
 ( |@ )     |@Data  
 ( =| )     =|Data  
 ( |= )     |=Data  
 ( |- )     |-Data  
 ( -| )     -|Data  
 ( ./ )     ./Data  
 ( /. )     /.Data  
 ( |. )     |.Data  
 ( .| )     .|Data  
 ( !. )     !.Data  
 ( *@ )     *@Data  
 ( @* )     @*Data  
 ( -@ )     -@Data  
 ( @- )     @-Data  
 ( |* )     |*Data  
 ( *| )     *|Data 
Collaborator

cloudRoutine commented Nov 3, 2016

duplicate of #159, but this suggestion already has more detail so maybe we expand & extend this one instead?

I think an operator with non-standard semantics, similar to how the dynamic operator (?) can be used in a manner that lets you write unbound identifiers in the middle of an expression, e.g.

let inline (?) (src : 'a) (prop : string) : 'b =  src.GetType().GetProperty(prop).GetValue(src, null) :?> 'b
let x = "arg"?Length : int
> val x : int = 3

But what we'd want in this case is for the identifier following the accessor operator to be used in a static check against the members of the preceding type, like how it does in a SRTP member call.

I don't think this kind of feature should be limited to properties which are a subset of the more general problem of accessing the members of a type passed to a single argument lambda. As such it should support methods as well.

#bar is no good, it'll clash with preprocessors
:bar is no good, it means the type of the preceding expression is bar

Potential Accessor Operators -

 ( @. )     @.Data  
 ( .@ )     .@Data  
 ( @| )     @|Data  
 ( |@ )     |@Data  
 ( =| )     =|Data  
 ( |= )     |=Data  
 ( |- )     |-Data  
 ( -| )     -|Data  
 ( ./ )     ./Data  
 ( /. )     /.Data  
 ( |. )     |.Data  
 ( .| )     .|Data  
 ( !. )     !.Data  
 ( *@ )     *@Data  
 ( @* )     @*Data  
 ( -@ )     -@Data  
 ( @- )     @-Data  
 ( |* )     |*Data  
 ( *| )     *|Data 
@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Nov 3, 2016

Collaborator

This appears to be a duplicate of #159 or perhaps of #440 .

The original lengthy discussion of #159 on UV is worth taking a look at too.

It's an interesting suggestion to make .bar or (.bar) (or whatever syntax) be precisely shorthand for (fun (x: ^T) -> (^T : (member bar : 'U) x), so let inline f xs = List.map (.bar) xs would get a generalized generic type.

Collaborator

dsyme commented Nov 3, 2016

This appears to be a duplicate of #159 or perhaps of #440 .

The original lengthy discussion of #159 on UV is worth taking a look at too.

It's an interesting suggestion to make .bar or (.bar) (or whatever syntax) be precisely shorthand for (fun (x: ^T) -> (^T : (member bar : 'U) x), so let inline f xs = List.map (.bar) xs would get a generalized generic type.

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Nov 3, 2016

Collaborator

Since there is active discussion here, I'll close #159. I'd be grateful if someone could cherry-pick a summary of the original UV discussion into this thread or the suggestion description.

Collaborator

dsyme commented Nov 3, 2016

Since there is active discussion here, I'll close #159. I'd be grateful if someone could cherry-pick a summary of the original UV discussion into this thread or the suggestion description.

@wastaz

This comment has been minimized.

Show comment
Hide comment
@wastaz

wastaz Nov 3, 2016

Crap, I thought it was strange that no one had suggested this before (and of course someone had). :)

Just adding some opinions here again, of the options that @cloudRoutine had I'm quite fond of @.Data or .@Data, at least compared to the other ones. They are probably the ones who would seem the least weird if I found them in some random code somewhere :)

wastaz commented Nov 3, 2016

Crap, I thought it was strange that no one had suggested this before (and of course someone had). :)

Just adding some opinions here again, of the options that @cloudRoutine had I'm quite fond of @.Data or .@Data, at least compared to the other ones. They are probably the ones who would seem the least weird if I found them in some random code somewhere :)

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Nov 3, 2016

Collaborator

@wastaz How about .bar with disambiguation by (.bar) where used in a long identifier? And a new warning on space-separated long identifiers a .b .c?

cheers
don

Collaborator

dsyme commented Nov 3, 2016

@wastaz How about .bar with disambiguation by (.bar) where used in a long identifier? And a new warning on space-separated long identifiers a .b .c?

cheers
don

@wastaz

This comment has been minimized.

Show comment
Hide comment
@wastaz

wastaz Nov 3, 2016

@dsyme I like warnings for space-separated long identifiers. However, would this only be for space-separated identifiers in that case?

I'm thinking if maybe it's confusing if

foo |> List.map .bar

is different than

List.map  
    .bar
    foo

In this case it's probably silly to structure it like that, but if I understand it correctly then the second example would be interpreted as List.map.bar foo which just feels very weird. Also, splitting lines like that is something that I would like to be able to do especially when I have to do interop with C# fluent-style interfaces.

So basically, I think that in the end that special case would be more confusing than helping?

wastaz commented Nov 3, 2016

@dsyme I like warnings for space-separated long identifiers. However, would this only be for space-separated identifiers in that case?

I'm thinking if maybe it's confusing if

foo |> List.map .bar

is different than

List.map  
    .bar
    foo

In this case it's probably silly to structure it like that, but if I understand it correctly then the second example would be interpreted as List.map.bar foo which just feels very weird. Also, splitting lines like that is something that I would like to be able to do especially when I have to do interop with C# fluent-style interfaces.

So basically, I think that in the end that special case would be more confusing than helping?

@rojepp

This comment has been minimized.

Show comment
Hide comment
@rojepp

rojepp Nov 3, 2016

@cloudRoutine ./Data looks very nice, kind of like accessing a file system. Would it conflict with anything?

rojepp commented Nov 3, 2016

@cloudRoutine ./Data looks very nice, kind of like accessing a file system. Would it conflict with anything?

@cloudRoutine

This comment has been minimized.

Show comment
Hide comment
@cloudRoutine

cloudRoutine Nov 3, 2016

Collaborator

I agree with @wastaz, it'll lead to an explosion of warnings across code like

let builder = 
    StringBuilder()
        .Append('\t', indent)
        .Append(sprintf "%s(%s) = " block.BlockType block.ParenthesizedName)
        .AppendLine block.Value

which is just one of several fluent examples I could pick out the project I'm working on right now.

@rojepp None of the operators I listed conflict with existing operators

Collaborator

cloudRoutine commented Nov 3, 2016

I agree with @wastaz, it'll lead to an explosion of warnings across code like

let builder = 
    StringBuilder()
        .Append('\t', indent)
        .Append(sprintf "%s(%s) = " block.BlockType block.ParenthesizedName)
        .AppendLine block.Value

which is just one of several fluent examples I could pick out the project I'm working on right now.

@rojepp None of the operators I listed conflict with existing operators

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Nov 3, 2016

Collaborator

@cloudRoutine The uses of .Foo in that example are not in long identifiers. But yes, you're right that it would apply to

let builder = 
    System.Console
        .WriteLine("abc")

Certainly List.map (.Bar) is a natural notation for F# with high comprehensibility and orthogonality given the rest of the syntax (c.f. active patterns, first-class uses of operators etc.). The question is how irritating the extra parentheses are, and how much that would reduce reasonable use of the feature.

Collaborator

dsyme commented Nov 3, 2016

@cloudRoutine The uses of .Foo in that example are not in long identifiers. But yes, you're right that it would apply to

let builder = 
    System.Console
        .WriteLine("abc")

Certainly List.map (.Bar) is a natural notation for F# with high comprehensibility and orthogonality given the rest of the syntax (c.f. active patterns, first-class uses of operators etc.). The question is how irritating the extra parentheses are, and how much that would reduce reasonable use of the feature.

@wastaz

This comment has been minimized.

Show comment
Hide comment
@wastaz

wastaz Nov 3, 2016

@dsyme I agree that List.map (.Bar) feels like a natural notation. However, I do wonder if it is easier to implement in the compiler with a "new" operator that is not used anywhere else instead? Since there are less disambiguation needed then. Though I'm way too much of a noob in the compiler code to be able to tell if this is a concern or not :)

wastaz commented Nov 3, 2016

@dsyme I agree that List.map (.Bar) feels like a natural notation. However, I do wonder if it is easier to implement in the compiler with a "new" operator that is not used anywhere else instead? Since there are less disambiguation needed then. Though I'm way too much of a noob in the compiler code to be able to tell if this is a concern or not :)

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Nov 3, 2016

Collaborator

However, I do wonder if it is easier to implement in the compiler with a "new" operator that is not used anywhere else instead?

(.Bar) would be really very simple to implement

One concern is the clumsiness of x |> (.Bar) though I hope people would never use that and just do x.Bar instead. People might want to do (.Bar) >> (.Baz) >> (.Foo) though that's pretty readable, even if 6 characters longer.

Another concern is whether you could reasonably give autocomplete on xs |> List.map (. when the . is pressed. It's probably doable fairly easily though would need to bee put under test.

Collaborator

dsyme commented Nov 3, 2016

However, I do wonder if it is easier to implement in the compiler with a "new" operator that is not used anywhere else instead?

(.Bar) would be really very simple to implement

One concern is the clumsiness of x |> (.Bar) though I hope people would never use that and just do x.Bar instead. People might want to do (.Bar) >> (.Baz) >> (.Foo) though that's pretty readable, even if 6 characters longer.

Another concern is whether you could reasonably give autocomplete on xs |> List.map (. when the . is pressed. It's probably doable fairly easily though would need to bee put under test.

@wastaz

This comment has been minimized.

Show comment
Hide comment
@wastaz

wastaz Nov 3, 2016

@dsyme

I think that you might not do x |> (.Bar), however I could easily see myself doing something like

(sorry for the contrived example)

foo
|> convertToBar
|> doSomeAwesomeCalculation
|> (.Results)
|> List.map (.NumberOfChickens)
|> List.sum

...actually..typing that didn't really feel too bad, I'm not sure if it really would be that clumsy.

wastaz commented Nov 3, 2016

@dsyme

I think that you might not do x |> (.Bar), however I could easily see myself doing something like

(sorry for the contrived example)

foo
|> convertToBar
|> doSomeAwesomeCalculation
|> (.Results)
|> List.map (.NumberOfChickens)
|> List.sum

...actually..typing that didn't really feel too bad, I'm not sure if it really would be that clumsy.

@cloudRoutine

This comment has been minimized.

Show comment
Hide comment
@cloudRoutine

cloudRoutine Nov 3, 2016

Collaborator

I hope this won't be limited to get_Prop(), I find myself writing

fun (str:string) -> str.Split ...

and many other similar lambdas to use instance methods far more often than I do to access a property.

Collaborator

cloudRoutine commented Nov 3, 2016

I hope this won't be limited to get_Prop(), I find myself writing

fun (str:string) -> str.Split ...

and many other similar lambdas to use instance methods far more often than I do to access a property.

@theprash

This comment has been minimized.

Show comment
Hide comment
@theprash

theprash Nov 3, 2016

@dsyme Do you have any thoughts on including the type name?

theprash commented Nov 3, 2016

@dsyme Do you have any thoughts on including the type name?

@theprash

This comment has been minimized.

Show comment
Hide comment
@theprash

theprash Nov 3, 2016

The original User Voice thread has a gem of an idea in it that I don't think anyone addressed:

luketopia:
What if we allowed the underscore to represent missing arguments to a member (including the instance), in which case a function for applying those arguments would be produced? Then we could do the following:

customers 
|> Seq.map _.Name 
|> File.WriteAllLines(@"C:\CustomerList.txt", _)

This would allow us to partially apply ordinary CLR methods with more than one argument, something I have always wanted.

I've always wanted some short syntax for partial application in an arbitrary order but never realised it could help with record access too:

type Foo = { id: int }

[{id = 1}] |> List.map _.id

[1; 2; 3] |> List.map (String.replicate _ "x")

[1; 2; 3] |> List.map (1 - _)

// Multiple 'slots' allowed
(String.replicate _ _)  3 "x"

I'm not sure about the underscore but the idea is there. It may be difficult to integrate with current syntax so there may need to be a prefix symbol or keyword.

theprash commented Nov 3, 2016

The original User Voice thread has a gem of an idea in it that I don't think anyone addressed:

luketopia:
What if we allowed the underscore to represent missing arguments to a member (including the instance), in which case a function for applying those arguments would be produced? Then we could do the following:

customers 
|> Seq.map _.Name 
|> File.WriteAllLines(@"C:\CustomerList.txt", _)

This would allow us to partially apply ordinary CLR methods with more than one argument, something I have always wanted.

I've always wanted some short syntax for partial application in an arbitrary order but never realised it could help with record access too:

type Foo = { id: int }

[{id = 1}] |> List.map _.id

[1; 2; 3] |> List.map (String.replicate _ "x")

[1; 2; 3] |> List.map (1 - _)

// Multiple 'slots' allowed
(String.replicate _ _)  3 "x"

I'm not sure about the underscore but the idea is there. It may be difficult to integrate with current syntax so there may need to be a prefix symbol or keyword.

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Nov 3, 2016

Collaborator

@theprash See also #186

Collaborator

dsyme commented Nov 3, 2016

@theprash See also #186

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Nov 3, 2016

Collaborator

@wastaz @theprash It would be good to come up with a list of examples that can be used to assess the syntax against. Can someone create gist containing the examples (and any others you care to add) above for variations _.Foo and (.Foo) and perhaps .Foo disambiguated by (.Foo)?

Collaborator

dsyme commented Nov 3, 2016

@wastaz @theprash It would be good to come up with a list of examples that can be used to assess the syntax against. Can someone create gist containing the examples (and any others you care to add) above for variations _.Foo and (.Foo) and perhaps .Foo disambiguated by (.Foo)?

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Nov 3, 2016

Collaborator

@theprash I like the suggestion of _.Foo. Thanks for bringing it to our attention.

Collaborator

dsyme commented Nov 3, 2016

@theprash I like the suggestion of _.Foo. Thanks for bringing it to our attention.

@vasily-kirichenko

This comment has been minimized.

Show comment
Hide comment
@vasily-kirichenko

vasily-kirichenko Nov 5, 2016

_.Foo is what used in Scala for same purposes. I like it more than other alternatives.

vasily-kirichenko commented Nov 5, 2016

_.Foo is what used in Scala for same purposes. I like it more than other alternatives.

@vasily-kirichenko

This comment has been minimized.

Show comment
Hide comment
@vasily-kirichenko

vasily-kirichenko Nov 5, 2016

Also it's used in Nemerle for both substituting lambda arguments and partial application in exactly the same form as @theprash suggested, see https://github.com/rsdn/nemerle/wiki/Quick-guide#anonymous-functions-and-partial-applications. I like it very much as it makes .NET Frameworks and C#-oriented libraries interoperability much, much nicer.

Such form of partial application makes code more readable in some scenarios. For example, in Scala it look like this:

def foo(i: Int, s: String, d: Double, a: Any) : Unit = {}
val f = foo(1, _, 2.2, _)
val x = f("bar", null)

It'd be fantastic if

let getName = _.Name

can be automatically generalized to (fun (x: ^T) -> (^T : (member Name: 'U) x) as @dsyme suggested here #506 (comment)

Nemerle cannot generalize such a function and infer type from first (local) usage. Scala does not allow such form at all.

vasily-kirichenko commented Nov 5, 2016

Also it's used in Nemerle for both substituting lambda arguments and partial application in exactly the same form as @theprash suggested, see https://github.com/rsdn/nemerle/wiki/Quick-guide#anonymous-functions-and-partial-applications. I like it very much as it makes .NET Frameworks and C#-oriented libraries interoperability much, much nicer.

Such form of partial application makes code more readable in some scenarios. For example, in Scala it look like this:

def foo(i: Int, s: String, d: Double, a: Any) : Unit = {}
val f = foo(1, _, 2.2, _)
val x = f("bar", null)

It'd be fantastic if

let getName = _.Name

can be automatically generalized to (fun (x: ^T) -> (^T : (member Name: 'U) x) as @dsyme suggested here #506 (comment)

Nemerle cannot generalize such a function and infer type from first (local) usage. Scala does not allow such form at all.

@vasily-kirichenko

This comment has been minimized.

Show comment
Hide comment
@vasily-kirichenko

vasily-kirichenko Nov 5, 2016

A couple of examples

func.TryGetFullDisplayName() 
|> Option.map (fun fullDisplayName -> processIdents func.FullName (fullDisplayName.Split '.'))
|> Option.toList

func.TryGetFullDisplayName() 
|> Option.map (processIdents func.FullName (_.Split '.'))
|> Option.toList

func.TryGetFullDisplayName() 
|> Option.map (processIdents func.FullName (.Split '.'))
|> Option.toList

func.TryGetFullDisplayName() 
|> Option.map (processIdents func.FullName ((.Split) '.')))
|> Option.toList
uses
|> Seq.map (fun symbolUse -> (symbolUse.FileName, symbolUse))
|> Seq.groupBy (fst >> Path.GetFullPathSafe)
|> Seq.collect (fun (_, symbolUses) -> 
      symbolUses 
      |> Seq.map snd 
      |> Seq.distinctBy (fun s -> s.RangeAlternate))
|> Seq.toArray

uses
|> Seq.map (_.FileName, symbolUse)
|> Seq.groupBy (fst >> Path.GetFullPathSafe)
|> Seq.collect (fun (_, symbolUses) -> 
      symbolUses 
      |> Seq.map snd 
      |> Seq.distinctBy (_.RangeAlternate))
|> Seq.toArray

uses
|> Seq.map (.FileName, symbolUse)
|> Seq.groupBy (fst >> Path.GetFullPathSafe)
|> Seq.collect (fun (_, symbolUses) -> 
      symbolUses 
      |> Seq.map snd 
      |> Seq.distinctBy (.RangeAlternate)
|> Seq.toArray

vasily-kirichenko commented Nov 5, 2016

A couple of examples

func.TryGetFullDisplayName() 
|> Option.map (fun fullDisplayName -> processIdents func.FullName (fullDisplayName.Split '.'))
|> Option.toList

func.TryGetFullDisplayName() 
|> Option.map (processIdents func.FullName (_.Split '.'))
|> Option.toList

func.TryGetFullDisplayName() 
|> Option.map (processIdents func.FullName (.Split '.'))
|> Option.toList

func.TryGetFullDisplayName() 
|> Option.map (processIdents func.FullName ((.Split) '.')))
|> Option.toList
uses
|> Seq.map (fun symbolUse -> (symbolUse.FileName, symbolUse))
|> Seq.groupBy (fst >> Path.GetFullPathSafe)
|> Seq.collect (fun (_, symbolUses) -> 
      symbolUses 
      |> Seq.map snd 
      |> Seq.distinctBy (fun s -> s.RangeAlternate))
|> Seq.toArray

uses
|> Seq.map (_.FileName, symbolUse)
|> Seq.groupBy (fst >> Path.GetFullPathSafe)
|> Seq.collect (fun (_, symbolUses) -> 
      symbolUses 
      |> Seq.map snd 
      |> Seq.distinctBy (_.RangeAlternate))
|> Seq.toArray

uses
|> Seq.map (.FileName, symbolUse)
|> Seq.groupBy (fst >> Path.GetFullPathSafe)
|> Seq.collect (fun (_, symbolUses) -> 
      symbolUses 
      |> Seq.map snd 
      |> Seq.distinctBy (.RangeAlternate)
|> Seq.toArray
@7sharp9

This comment has been minimized.

Show comment
Hide comment
@7sharp9

7sharp9 Nov 5, 2016

Member

Im not sure _ makes all code easier to understand. As _ is used elsewhere to ignore things.

Im also unsure whether this language idea is for shorthand access to properties or a way to partially apply functions.

I think _.Foo works as great as syntactic shortcut but things like:

[1; 2; 3] |> List.map (String.replicate _ "x")

Seem a little obtuse, intension is too hidden

Member

7sharp9 commented Nov 5, 2016

Im not sure _ makes all code easier to understand. As _ is used elsewhere to ignore things.

Im also unsure whether this language idea is for shorthand access to properties or a way to partially apply functions.

I think _.Foo works as great as syntactic shortcut but things like:

[1; 2; 3] |> List.map (String.replicate _ "x")

Seem a little obtuse, intension is too hidden

@theprash

This comment has been minimized.

Show comment
Hide comment
@theprash

theprash Nov 5, 2016

Using this for creating lambdas is definitely quite a big change to the language and would take some getting used to but it's potentially so powerful. It almost obviates the need for currying!

The compiler could see if a value expression (as opposed to a pattern expression) contains a _. If it does, then it can be treated as a function.

records |> List.map _.recordLabel
[1] |> List.map (_ - 1)
[Some 1] |> List.choose _   // `_` equivalent to `id`

This would be much more useful if you could refer to multiple parameters. But then there's also a difficulty in knowing which _ refers to which parameter and being forced to write your code in a way where the parameters appear from left to right in their function application order. This could be resolved by explicitly numbering any extra parameters, naming them _2, _3, etc.

// Instead of...
let flip f = fun a b -> f b a   // A helper somewhere
(flip String.replicate) "x" 3

// Simply...
(String.replicate _2 _) "x" 3

And then that also allows to referring to the same parameter twice:

// Takes one parameter
let square = _ * _

This could definitely be confusing, especially if there is a _ buried in a large expression:

let func x =
    let a = 1
    let b = 2
    a + _ + b + x
// func actually takes two parameters!

So probably quite dangerous to just throw into the language like this. But what if it required a prefix symbol? Let's try the above examples prefixing with \ as in a normal Haskell lambda:

records |> List.map \_.recordLabel
[1] |> List.map (\_ - 1)
[Some 1] |> List.choose \_

(\ String.replicate _2 _) "x" 3

let square = \ _ * _

let func x =
    let a = 1
    let b = 2
    \ a + _ + b + x

It's more explicit but less pretty, and probably confusing to someone used to \ being equivalent to fun. Maybe this could all be improved with a different choice of symbols. It doesn't have to be backslash and underscore.

And is this compatible with automatically generic record labels? Maybe not?

I've convinced myself that having this new lambda syntax, as opposed to just record label accessors, would take a lot more thought to be viable, if it is at all.

theprash commented Nov 5, 2016

Using this for creating lambdas is definitely quite a big change to the language and would take some getting used to but it's potentially so powerful. It almost obviates the need for currying!

The compiler could see if a value expression (as opposed to a pattern expression) contains a _. If it does, then it can be treated as a function.

records |> List.map _.recordLabel
[1] |> List.map (_ - 1)
[Some 1] |> List.choose _   // `_` equivalent to `id`

This would be much more useful if you could refer to multiple parameters. But then there's also a difficulty in knowing which _ refers to which parameter and being forced to write your code in a way where the parameters appear from left to right in their function application order. This could be resolved by explicitly numbering any extra parameters, naming them _2, _3, etc.

// Instead of...
let flip f = fun a b -> f b a   // A helper somewhere
(flip String.replicate) "x" 3

// Simply...
(String.replicate _2 _) "x" 3

And then that also allows to referring to the same parameter twice:

// Takes one parameter
let square = _ * _

This could definitely be confusing, especially if there is a _ buried in a large expression:

let func x =
    let a = 1
    let b = 2
    a + _ + b + x
// func actually takes two parameters!

So probably quite dangerous to just throw into the language like this. But what if it required a prefix symbol? Let's try the above examples prefixing with \ as in a normal Haskell lambda:

records |> List.map \_.recordLabel
[1] |> List.map (\_ - 1)
[Some 1] |> List.choose \_

(\ String.replicate _2 _) "x" 3

let square = \ _ * _

let func x =
    let a = 1
    let b = 2
    \ a + _ + b + x

It's more explicit but less pretty, and probably confusing to someone used to \ being equivalent to fun. Maybe this could all be improved with a different choice of symbols. It doesn't have to be backslash and underscore.

And is this compatible with automatically generic record labels? Maybe not?

I've convinced myself that having this new lambda syntax, as opposed to just record label accessors, would take a lot more thought to be viable, if it is at all.

@Soldalma

This comment has been minimized.

Show comment
Hide comment
@Soldalma

Soldalma Jan 16, 2018

In Mathematica you can do something like this: Map[f[#] + g[#] &, {a, b, c}]. The ampersand means it is a lambda and the # stands for the variable. Compared to List.map (fun x -> f x + g x) you save two characters going from "fun" to "&", plus four characters for "-> " plus one character for the variable name on the LHS of the arrow. (I am not considering the square brackets, which are unavoidable in Mathematica) The equivalent in F# would be List.map (f # + g #)& [a; b; c]. You can also use #1 and #2 if there are two variables. Of course it might be better to have the ampersand before, not after the expression with the #. Personally I don't mind writing "fun x -> ...".

Soldalma commented Jan 16, 2018

In Mathematica you can do something like this: Map[f[#] + g[#] &, {a, b, c}]. The ampersand means it is a lambda and the # stands for the variable. Compared to List.map (fun x -> f x + g x) you save two characters going from "fun" to "&", plus four characters for "-> " plus one character for the variable name on the LHS of the arrow. (I am not considering the square brackets, which are unavoidable in Mathematica) The equivalent in F# would be List.map (f # + g #)& [a; b; c]. You can also use #1 and #2 if there are two variables. Of course it might be better to have the ampersand before, not after the expression with the #. Personally I don't mind writing "fun x -> ...".

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Jan 16, 2018

Collaborator

mapping to a lambda seems the easiest ...

@gusty Just to mention there are technical problems with using traits for _.M method calls. At the point we process _.M we would need a trait signature for the trait. But what if M is generic? How many arguments should the trait signature have? What if M is some funky method like a ParamArray or has byref parameters etc. All of these unknowns are handled by one-off overload resolution, but they are not captured in the flexible unknowns of a trait signtature, which, for example, commits to the number of arguments the method has.

I think traits would have worked if only _.P properties were allowed. But extending it to work for methods is not at all easy.

thx

Collaborator

dsyme commented Jan 16, 2018

mapping to a lambda seems the easiest ...

@gusty Just to mention there are technical problems with using traits for _.M method calls. At the point we process _.M we would need a trait signature for the trait. But what if M is generic? How many arguments should the trait signature have? What if M is some funky method like a ParamArray or has byref parameters etc. All of these unknowns are handled by one-off overload resolution, but they are not captured in the flexible unknowns of a trait signtature, which, for example, commits to the number of arguments the method has.

I think traits would have worked if only _.P properties were allowed. But extending it to work for methods is not at all easy.

thx

@gusty

This comment has been minimized.

Show comment
Hide comment
@gusty

gusty Jan 16, 2018

I see. I thought methods were not being considered.
But yes, if we consider them there will be many issues, at least the way overload resolution is right now.

Let’s go for the lambda, in anycase it would be easy to upgrade to traits at anytime in the future if overload resolution mechanism gets refined.

gusty commented Jan 16, 2018

I see. I thought methods were not being considered.
But yes, if we consider them there will be many issues, at least the way overload resolution is right now.

Let’s go for the lambda, in anycase it would be easy to upgrade to traits at anytime in the future if overload resolution mechanism gets refined.

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Jan 29, 2018

Collaborator

Still on the "it"/"_" discussion: I was talking today with some people and they pointed out that a possible delimiting syntax for the lambda would be (% ... ) or (\ ...) or the like.

I don't really like any of these a priori, but it's relevant to this discussion because there's the dangling question of whether (_.M arg1 arg2) is allowed from my comment up here #506 (comment)

The problem with using % is that it conflicts with the use of %expr for quotation splicing in <@ ... @> literals. Anyway, I've listed the various syntax suggestions below

Samples with % and _

xs |> List.map _.Property1.Property2
xs |> List.map _.Method
xs |> List.map (% _.Method(1))
xs |> List.map (% _.Method(1,2))
xs |> List.map (% _.Method().Property)
xs |> List.map (% _.Method(1).Property)
xs |> List.map (% _ + 1)
xs |> List.map (% _ > 10)
xs |> List.map (% _.[1])
xs |> List.map (% _.[1..10])
xs |> List.map (% _.Method(_2,_3,_4))
xs |> List.map (% (Some _1, Very(Complex(_1,_2, _1 |> List.map (% _1 + _2))))

Samples with % and it

xs |> List.map it.Property1.Property2
xs |> List.map it.Method
xs |> List.map (% it.Method(1))
xs |> List.map (% it.Method(1,2))
xs |> List.map (% it.Method().Property)
xs |> List.map (% it.Method(1).Property)
xs |> List.map (% it + 1)
xs |> List.map (% it > 10)
xs |> List.map (% it.[1])
xs |> List.map (% it.[1..10])
xs |> List.map (% it1.Method(it2,it3,it4))
xs |> List.map (% (Some it1, Very(Complex(it1,it2, it1 |> List.map (% it1 + it2))))

Samples with \ and _

xs |> List.map _.Property1.Property2
xs |> List.map _.Method
xs |> List.map (\ _,Method(1))
xs |> List.map (\ _,Method(1,2))
xs |> List.map (\ _.Method().Property)
xs |> List.map (\ _.Method(1).Property)
xs |> List.map (\ _ + 1)
xs |> List.map (\ _ > 10)
xs |> List.map (\ _.[1])
xs |> List.map (\ _.[1..10])
xs |> List.map (\ _.Method(_2,_3,_4))
xs |> List.map (\ (Some _1, Very(Complex(_1,_2, _1 |> List.map (\ _1 + _2))))
Collaborator

dsyme commented Jan 29, 2018

Still on the "it"/"_" discussion: I was talking today with some people and they pointed out that a possible delimiting syntax for the lambda would be (% ... ) or (\ ...) or the like.

I don't really like any of these a priori, but it's relevant to this discussion because there's the dangling question of whether (_.M arg1 arg2) is allowed from my comment up here #506 (comment)

The problem with using % is that it conflicts with the use of %expr for quotation splicing in <@ ... @> literals. Anyway, I've listed the various syntax suggestions below

Samples with % and _

xs |> List.map _.Property1.Property2
xs |> List.map _.Method
xs |> List.map (% _.Method(1))
xs |> List.map (% _.Method(1,2))
xs |> List.map (% _.Method().Property)
xs |> List.map (% _.Method(1).Property)
xs |> List.map (% _ + 1)
xs |> List.map (% _ > 10)
xs |> List.map (% _.[1])
xs |> List.map (% _.[1..10])
xs |> List.map (% _.Method(_2,_3,_4))
xs |> List.map (% (Some _1, Very(Complex(_1,_2, _1 |> List.map (% _1 + _2))))

Samples with % and it

xs |> List.map it.Property1.Property2
xs |> List.map it.Method
xs |> List.map (% it.Method(1))
xs |> List.map (% it.Method(1,2))
xs |> List.map (% it.Method().Property)
xs |> List.map (% it.Method(1).Property)
xs |> List.map (% it + 1)
xs |> List.map (% it > 10)
xs |> List.map (% it.[1])
xs |> List.map (% it.[1..10])
xs |> List.map (% it1.Method(it2,it3,it4))
xs |> List.map (% (Some it1, Very(Complex(it1,it2, it1 |> List.map (% it1 + it2))))

Samples with \ and _

xs |> List.map _.Property1.Property2
xs |> List.map _.Method
xs |> List.map (\ _,Method(1))
xs |> List.map (\ _,Method(1,2))
xs |> List.map (\ _.Method().Property)
xs |> List.map (\ _.Method(1).Property)
xs |> List.map (\ _ + 1)
xs |> List.map (\ _ > 10)
xs |> List.map (\ _.[1])
xs |> List.map (\ _.[1..10])
xs |> List.map (\ _.Method(_2,_3,_4))
xs |> List.map (\ (Some _1, Very(Complex(_1,_2, _1 |> List.map (\ _1 + _2))))
@voronoipotato

This comment has been minimized.

Show comment
Hide comment
@voronoipotato

voronoipotato Jan 29, 2018

[1;2;3;4;5] |> List.map ((+) 1)
[1;2;3;4;5] |> List.map ((<) 10)  

seem fine to me and already work. infix notation takes about a half-second to adjust to and works just as well.

personally I only see use for

xs |> List.map _.Property1.Property2
xs |> List.map _.Method

if we end up removing fun from lambda declation it's

(\  _.method(1,2))
//vs
(x -> x.method(1,2))

I love how tight and expressive F# is without losing clarity, but it does seem like there is a point of diminishing returns.

voronoipotato commented Jan 29, 2018

[1;2;3;4;5] |> List.map ((+) 1)
[1;2;3;4;5] |> List.map ((<) 10)  

seem fine to me and already work. infix notation takes about a half-second to adjust to and works just as well.

personally I only see use for

xs |> List.map _.Property1.Property2
xs |> List.map _.Method

if we end up removing fun from lambda declation it's

(\  _.method(1,2))
//vs
(x -> x.method(1,2))

I love how tight and expressive F# is without losing clarity, but it does seem like there is a point of diminishing returns.

@jwosty

This comment has been minimized.

Show comment
Hide comment
@jwosty

jwosty Mar 2, 2018

Contributor

I agree with @voronoipotato. The great majority I've found myself writing code that desires this type of feature is for the really simple uses akin to xs |> List.map _.Property1, and occasionally xs |> List.map _.Property1.Property2. I say it's better than not having this at all -- I really want this!

Contributor

jwosty commented Mar 2, 2018

I agree with @voronoipotato. The great majority I've found myself writing code that desires this type of feature is for the really simple uses akin to xs |> List.map _.Property1, and occasionally xs |> List.map _.Property1.Property2. I say it's better than not having this at all -- I really want this!

@eugbaranov

This comment has been minimized.

Show comment
Hide comment
@eugbaranov

eugbaranov Mar 2, 2018

Personally I would prefer (as some also suggested) a lighter syntax for lambdas, e.g.
x -> x.Property or x => x.Property, instead of fun x -> x.Property.
Having even shorted syntax, such as it.Property1, \ _.Property1 or it.Property would be nice but I think => will pay off better and appeal to anyone coming from C#.

eugbaranov commented Mar 2, 2018

Personally I would prefer (as some also suggested) a lighter syntax for lambdas, e.g.
x -> x.Property or x => x.Property, instead of fun x -> x.Property.
Having even shorted syntax, such as it.Property1, \ _.Property1 or it.Property would be nice but I think => will pay off better and appeal to anyone coming from C#.

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Mar 3, 2018

Collaborator

Am marking this as approved-in-principle - we will do this in some form, in many ways the above is an RFC discussion.

Collaborator

dsyme commented Mar 3, 2018

Am marking this as approved-in-principle - we will do this in some form, in many ways the above is an RFC discussion.

@cmeeren

This comment has been minimized.

Show comment
Hide comment
@cmeeren

cmeeren Mar 9, 2018

Just want to say I too would absolutely love this in some form or another.

cmeeren commented Mar 9, 2018

Just want to say I too would absolutely love this in some form or another.

@xp44mm

This comment has been minimized.

Show comment
Hide comment
@xp44mm

xp44mm May 22, 2018

it is useful when a lambda's body just only one expression. let me name it as expression function:

fun x -> x+1
fun x -> x.ToString()

when use expression function at high-order function like map

[1;2;3]|>Seq.map(fun x->x+1)

it seem frequently be used. so we can sugar it, suppose x is keyword:

[1;2;3]|>Seq.map(fun x -> x+1)
[1;2;3]|>Seq.map(x+1)

[1;2;3]|>Seq.map(fun x -> x.ToString())
[1;2;3]|>Seq.map(x.ToString())

the keyword x scope limit in a expression. different expression or parent expression's x is different.
just sugar lambda's head fun x ->.

xp44mm commented May 22, 2018

it is useful when a lambda's body just only one expression. let me name it as expression function:

fun x -> x+1
fun x -> x.ToString()

when use expression function at high-order function like map

[1;2;3]|>Seq.map(fun x->x+1)

it seem frequently be used. so we can sugar it, suppose x is keyword:

[1;2;3]|>Seq.map(fun x -> x+1)
[1;2;3]|>Seq.map(x+1)

[1;2;3]|>Seq.map(fun x -> x.ToString())
[1;2;3]|>Seq.map(x.ToString())

the keyword x scope limit in a expression. different expression or parent expression's x is different.
just sugar lambda's head fun x ->.

@voronoipotato

This comment has been minimized.

Show comment
Hide comment
@voronoipotato

voronoipotato May 23, 2018

@xp44mm This is interesting but it's an entirely different proposal. I would also be extremely careful about having "x" be a keyword. "_" would at least denote the fact that something magical is happening

[1;2;3] |> Seq.map( _ + 1 )
[1;2;3] |> Seq.map( _.ToString )

In my humble opinion though

[1;2;3] |> Seq.map( _ + 1 )
// is more elegantly put as
[1;2;3] |> Seq.map( (+) 1 )
//which is already supported.

voronoipotato commented May 23, 2018

@xp44mm This is interesting but it's an entirely different proposal. I would also be extremely careful about having "x" be a keyword. "_" would at least denote the fact that something magical is happening

[1;2;3] |> Seq.map( _ + 1 )
[1;2;3] |> Seq.map( _.ToString )

In my humble opinion though

[1;2;3] |> Seq.map( _ + 1 )
// is more elegantly put as
[1;2;3] |> Seq.map( (+) 1 )
//which is already supported.
@xp44mm

This comment has been minimized.

Show comment
Hide comment
@xp44mm

xp44mm May 28, 2018

@voronoipotato how about?

[1;2;3] |> Seq.map( (float x)**2.0+1.0 )

xp44mm commented May 28, 2018

@voronoipotato how about?

[1;2;3] |> Seq.map( (float x)**2.0+1.0 )
@wallymathieu

This comment has been minimized.

Show comment
Hide comment
@wallymathieu

wallymathieu May 28, 2018

@xp44mm that looks like you are capturing a variable x, could lead to some confusion.

wallymathieu commented May 28, 2018

@xp44mm that looks like you are capturing a variable x, could lead to some confusion.

@voronoipotato

This comment has been minimized.

Show comment
Hide comment
@voronoipotato

voronoipotato May 28, 2018

also the likely problem that I'm already using 'x' in a larger scope.

voronoipotato commented May 28, 2018

also the likely problem that I'm already using 'x' in a larger scope.

@xp44mm

This comment has been minimized.

Show comment
Hide comment
@xp44mm

xp44mm May 29, 2018

may be default argument of only one expression of lambda's body can use @, %, # or @@, @0, @1.

[1;2;3]|>Seq.map(fun x -> express x)
//sugar to
[1;2;3]|>Seq.map(express @@)

[1;2;3]|>Seq.map(fun x y -> express x y)
//sugar to
[1;2;3]|>Seq.map(express @0 @1)

if import ... spread operator, and break the @:

let a = [1;2]
let b = [3;4]
let c = a @ b
//transform to
let c = [...a;...b]
//also
let c = [yield!a;yield!b]

then default argument of only one expression of lambda's body can use @, it is better:

[1;2;3]|>Seq.map(fun x -> x+1)
//sugar to
[1;2;3]|>Seq.map(@+1)

[1;2;3]|>Seq.map(fun x -> x.ToString())
//sugar to
[1;2;3]|>Seq.map(@.ToString())

the keyword @ scope limit in a expression. different expression or parent expression's @ is different.
just sugar lambda's head fun @ ->.

xp44mm commented May 29, 2018

may be default argument of only one expression of lambda's body can use @, %, # or @@, @0, @1.

[1;2;3]|>Seq.map(fun x -> express x)
//sugar to
[1;2;3]|>Seq.map(express @@)

[1;2;3]|>Seq.map(fun x y -> express x y)
//sugar to
[1;2;3]|>Seq.map(express @0 @1)

if import ... spread operator, and break the @:

let a = [1;2]
let b = [3;4]
let c = a @ b
//transform to
let c = [...a;...b]
//also
let c = [yield!a;yield!b]

then default argument of only one expression of lambda's body can use @, it is better:

[1;2;3]|>Seq.map(fun x -> x+1)
//sugar to
[1;2;3]|>Seq.map(@+1)

[1;2;3]|>Seq.map(fun x -> x.ToString())
//sugar to
[1;2;3]|>Seq.map(@.ToString())

the keyword @ scope limit in a expression. different expression or parent expression's @ is different.
just sugar lambda's head fun @ ->.

@amieres

This comment has been minimized.

Show comment
Hide comment
@amieres

amieres Jul 11, 2018

It is hard to follow such a lengthy discussion.
What is the proposed behavior for _.Method(args)?
is it:
1- (fun x -> x.Method) (args)
or
2- (fun x -> x.Method(args) )

Personally, I prefer option 1 as is simpler and more intuitive

amieres commented Jul 11, 2018

It is hard to follow such a lengthy discussion.
What is the proposed behavior for _.Method(args)?
is it:
1- (fun x -> x.Method) (args)
or
2- (fun x -> x.Method(args) )

Personally, I prefer option 1 as is simpler and more intuitive

@7sharp9

This comment has been minimized.

Show comment
Hide comment
@7sharp9

7sharp9 Jul 16, 2018

Member

I too have lost the gist of whats proposed/agreed now, maybe its time for a draft RFC by whoever knows what the latest is?

Member

7sharp9 commented Jul 16, 2018

I too have lost the gist of whats proposed/agreed now, maybe its time for a draft RFC by whoever knows what the latest is?

@jwosty

This comment has been minimized.

Show comment
Hide comment
@jwosty

jwosty Jul 16, 2018

Contributor

@7sharp9 I agree; this would be nice in whatever form it ends up taking. Maybe a few different people could draft up some RFCs and we decide from there? I'd offer but I haven't been following this well enough.

Contributor

jwosty commented Jul 16, 2018

@7sharp9 I agree; this would be nice in whatever form it ends up taking. Maybe a few different people could draft up some RFCs and we decide from there? I'd offer but I haven't been following this well enough.

@xp44mm

This comment has been minimized.

Show comment
Hide comment
@xp44mm

xp44mm Jul 17, 2018

i found fun x -> some expression must use () to surround it in a lot of contexts, although start with fun:

arr |> Array.map(fun x -> x.ToString())

it is root source of so complex. so just to omit the began fun useless when () surround it and be unary function that just only one parameter(i.e. fun x -> or fun (x,y) ->not fun x y ->). but other separator is needed:

arr |> Array.map( x => x.ToString())

and arguments x can use pattern match still!

xp44mm commented Jul 17, 2018

i found fun x -> some expression must use () to surround it in a lot of contexts, although start with fun:

arr |> Array.map(fun x -> x.ToString())

it is root source of so complex. so just to omit the began fun useless when () surround it and be unary function that just only one parameter(i.e. fun x -> or fun (x,y) ->not fun x y ->). but other separator is needed:

arr |> Array.map( x => x.ToString())

and arguments x can use pattern match still!

@akbcode

This comment has been minimized.

Show comment
Hide comment
@akbcode

akbcode Sep 27, 2018

I have been playing around with the F# parser. If we allow _.Prop as a shorthand for fun x -> x.Prop and _ to be used as an implicit parameter in a lambda expression, then what does this mean?

x |> List.map (_.SomeMethod(_.Prop1))

Is _.Prop1 accessing the property Prop1 of the implicit parameter _ or is it a shorthand for fun x -> x.Prop1? Deciding by type would be tricky if there is an overload for both cases.

akbcode commented Sep 27, 2018

I have been playing around with the F# parser. If we allow _.Prop as a shorthand for fun x -> x.Prop and _ to be used as an implicit parameter in a lambda expression, then what does this mean?

x |> List.map (_.SomeMethod(_.Prop1))

Is _.Prop1 accessing the property Prop1 of the implicit parameter _ or is it a shorthand for fun x -> x.Prop1? Deciding by type would be tricky if there is an overload for both cases.

@gusty

This comment has been minimized.

Show comment
Hide comment
@gusty

gusty Sep 27, 2018

The latter, it will desugar to:

x |> List.map (fun x -> x.SomeMethod(fun x -> x.Prop1))

or to be clear

x |> List.map (fun x1 -> x1.SomeMethod(fun x2 -> x2.Prop1))

gusty commented Sep 27, 2018

The latter, it will desugar to:

x |> List.map (fun x -> x.SomeMethod(fun x -> x.Prop1))

or to be clear

x |> List.map (fun x1 -> x1.SomeMethod(fun x2 -> x2.Prop1))
@akbcode

This comment has been minimized.

Show comment
Hide comment
@akbcode

akbcode Sep 28, 2018

Ok, doing that also means this code won't compile

xs |> List.map (_.Substring(_.IndexOf("\"))

Does that mean that this code will also not compile?

xs |> List.filter (_.Prop = 10)
xs |> List.filter ( _ % 2 )

As it will desugar to

xs |> List.filter ((fun x -> x.Prop) = 10)
xs |> List.filter ((fun x -> x) % 2 )

akbcode commented Sep 28, 2018

Ok, doing that also means this code won't compile

xs |> List.map (_.Substring(_.IndexOf("\"))

Does that mean that this code will also not compile?

xs |> List.filter (_.Prop = 10)
xs |> List.filter ( _ % 2 )

As it will desugar to

xs |> List.filter ((fun x -> x.Prop) = 10)
xs |> List.filter ((fun x -> x) % 2 )
@gusty

This comment has been minimized.

Show comment
Hide comment
@gusty

gusty Sep 28, 2018

Exactly, if you read the whole discussion, you'll see that I advised against it, but my proposal wasn't that popular.

gusty commented Sep 28, 2018

Exactly, if you read the whole discussion, you'll see that I advised against it, but my proposal wasn't that popular.

@achkasov

This comment has been minimized.

Show comment
Hide comment
@achkasov

achkasov Sep 28, 2018

Couldn't you just have a stupid and dirty replacement of (_. to (fun x -> x., and the remaining _ with x?

achkasov commented Sep 28, 2018

Couldn't you just have a stupid and dirty replacement of (_. to (fun x -> x., and the remaining _ with x?

@JackMatusiewicz

This comment has been minimized.

Show comment
Hide comment
@JackMatusiewicz

JackMatusiewicz Sep 28, 2018

Hi all,

@TobyShaw and I are tackling this.

We believe two things are true:

_.Foo syntax is the way to go.
Allowing things like (_ + 5) is massively increasing the scope of this extension, without providing much benefit.

We also believe that the following things should be valid:

_.Foo.Bar  => fun o -> o.Foo.Bar
_.Foo.[5]  => fun o -> o.Foo.[5]
_.Foo()    => fun o -> o.Foo()
_.Foo(5).X => fun o -> o.Foo(5).X

Or in other words:

_. binds only to dot-lookups and atomic function application.
Spaces break the scope (unless inside brackets).

_.Foo x y => (fun o -> o.Foo) x y

There is a very unfortunate consequence of this design decision, but we believe it does not take away
from the overall usefulness of the feature. Consider a curried instance method:

type T() =
    member __.F x y = x + y
	
T()
|> _.F(5)(6) // yuck

In order to bind multiple arguments to curried methods, these ugly chained brackets are required.

Other than that, we feel like it is a small extension with enough benefits to outweigh this.

Would people be happy with this feature (as described above) in F#? Do people feel like the scope
is too limited, or too large? Are people happy with the syntax?

JackMatusiewicz commented Sep 28, 2018

Hi all,

@TobyShaw and I are tackling this.

We believe two things are true:

_.Foo syntax is the way to go.
Allowing things like (_ + 5) is massively increasing the scope of this extension, without providing much benefit.

We also believe that the following things should be valid:

_.Foo.Bar  => fun o -> o.Foo.Bar
_.Foo.[5]  => fun o -> o.Foo.[5]
_.Foo()    => fun o -> o.Foo()
_.Foo(5).X => fun o -> o.Foo(5).X

Or in other words:

_. binds only to dot-lookups and atomic function application.
Spaces break the scope (unless inside brackets).

_.Foo x y => (fun o -> o.Foo) x y

There is a very unfortunate consequence of this design decision, but we believe it does not take away
from the overall usefulness of the feature. Consider a curried instance method:

type T() =
    member __.F x y = x + y
	
T()
|> _.F(5)(6) // yuck

In order to bind multiple arguments to curried methods, these ugly chained brackets are required.

Other than that, we feel like it is a small extension with enough benefits to outweigh this.

Would people be happy with this feature (as described above) in F#? Do people feel like the scope
is too limited, or too large? Are people happy with the syntax?

@TD5

This comment has been minimized.

Show comment
Hide comment
@TD5

TD5 Oct 5, 2018

@JackMatusiewicz & @TobyShaw, your proposal would be super useful for me and it seems to strike a good balance of scope, simplicity, value etc.

TD5 commented Oct 5, 2018

@JackMatusiewicz & @TobyShaw, your proposal would be super useful for me and it seems to strike a good balance of scope, simplicity, value etc.

@wallymathieu

This comment has been minimized.

Show comment
Hide comment
@wallymathieu

wallymathieu Oct 5, 2018

That space breaks the scope seems like a thing that could cause a lot of confusion in some cases. I guess if you have good error reporting in that case it would be OK enough.

wallymathieu commented Oct 5, 2018

That space breaks the scope seems like a thing that could cause a lot of confusion in some cases. I guess if you have good error reporting in that case it would be OK enough.

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Oct 9, 2018

Collaborator

@JackMatusiewicz How bad is it if you simply didn't allow argument application at all, i.e. disallow these:

_.Foo()    => fun o -> o.Foo()
_.Foo(5).X => fun o -> o.Foo(5).X

Having spaces break the scope feels very odd for F#.

Collaborator

dsyme commented Oct 9, 2018

@JackMatusiewicz How bad is it if you simply didn't allow argument application at all, i.e. disallow these:

_.Foo()    => fun o -> o.Foo()
_.Foo(5).X => fun o -> o.Foo(5).X

Having spaces break the scope feels very odd for F#.

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