Closed wastaz closed 1 year ago
I really like this suggestion, but it would probably have to be something other than a dot.
List.map .bar
is the same as
List.map.bar
.
Yea, I just realised the same thing and updated the proposal :)
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?
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 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.
duplicate of https://github.com/fsharp/fslang-suggestions/issues/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
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.
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.
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 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
@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?
@cloudRoutine ./Data
looks very nice, kind of like accessing a file system. Would it conflict with anything?
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
@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.
@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 :)
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.
@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.
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.
@dsyme Do you have any thoughts on including the type name?
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 See also #186
@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)
?
@theprash I like the suggestion of _.Foo
. Thanks for bringing it to our attention.
_.Foo
is what used in Scala for same purposes. I like it more than other alternatives.
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 https://github.com/fsharp/fslang-suggestions/issues/506#issuecomment-258126097
Nemerle cannot generalize such a function and infer type from first (local) usage. Scala does not allow such form at all.
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
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
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 Regarding the confusion in the order of parameter there's no need to name them with a number, you can give them any name just use the normal lambda with the fun
keyword and problem solved.
@7sharp9 for me the _
symbol rather than "ignore this" means "a value goes here" and that applies to the current use as well.
@vasily-kirichenko
In the last two examples with |> Seq.map (.FileName, symbolUse)
symbolUse would not be defined and a full lambda will still need to be used. Even (_.FileName, _)
would be come a two parameter lambda.
@7sharp9
I am inclined to agree with you.
The scope of the original shorthand accessor (.Member)
would be largely self evident and convenient but full _ shorthanded lambdas don't seem to add to readability.
My original idea here was only for shorthand property access. I do think that a lot of the other ideas here on making it a more general way of creating lambdas are interesting - but it feels like that work does complicate things a bit. While using something like _.Foo
for mere property access feels simple and non-weird, doing things like (\ String.replicate _2 _) "x" 3
feels like something that might be a good idea but probably requires a bit more thought both to implement and to figure out if it actually makes code more or less readable.
So I would like to propose maybe staggering this into two things to be released (or not) separately. First a shorthand for property access like _.Foo
(or another of the options if we deem them to be better) and then later a better way of creating lambdas as discussed in this thread? This would give us a quick and clear win in eliminating the most "stupid" inline lambda cruft without having to finish the entire creating a lambda discussion first :)
@wastaz I agree having multi-use syntactic features can make things really fuzzy, I look as some of the suggestions here and Im not instantly clear on whats happening whereas the use of (_.Foo)
does seem clear.
Theres already some gnarly bits of F# syntax around, the last thing we want is to add yet more :-)
@wastaz why do you want it to be restricted by property access specifically? For me property access and method calls look the same: _.DoIt(25, ”bar")
, (_.Foo 34) + _.Bar
@vasily-kirichenko I dont actually want it to be restricted. What I was meaning to convey was that this suggestion seems to have branched into two parts. The first one is what we all seem to somewhat agree to as simple and natural, for example property access _.Foo
and I can also see that something like this could be related "1,2,3" |> _.Split(",")
.
However when we start talking about a generalized concept like this (\ String.replicate _2 _) "x" 3
or similiar constructs I think we are veering off into solving a problem that is a lot harder (both to implement, and also to "get right" design-wise). While I would love to have a similar construct in the language (though maybe not with exactly that syntax) I see no reason to have to necessarily linking those two things together. We could implement the simpler restricted version first and release that and while that is going on continue discussion and evaluation of a more generic kind. Taking the easy win first.
I'm afraid you don't get my idea. I meant the feature should allow method invocation in the same way as it does for property accessors. In short, _
should substitute object self identifier 1. only in dot expressions and 2. only in lambdas.
Quick mention to @haf here, as he's offered incredibly useful feedback on similar uservoice suggestions on this.
For 'accessor' functions, lenses offer a vastly more powerful and elegant solution to this problem. You can read more about them, and see Aether (the de-facto standard) here.
For adding the syntax, I have two major objections: a) Adding the feature would encourage adding more members to records, which would promote an overall a less functional style. b) It complicates the language. Scala offers a good argument as to why adding endless syntactic tricks can confuse a language. idiomatic F# remains very easy to read.
As mentioned in the original discussion on lenses on uservoice, this seems to go back to the root of our problem - we don't have higher-kinded types, which would allow us to build this at a library level, rather than needing even more syntax for this.
I've read this thread; and instead of the above suggestion, I suggest you this:
module Strings = System.String
(* Becomes equivalent to
module Strings =
// automatically camelCased, docs are copied;
let split (input:string) (instance:String) = instance.Split(input)
let length (input:string) (instance:String) = instance.Length
let concat // ...
// ...
*)
"1,2,3" |> Strings.split ","
instead of adding underscore as an object identifier. By letting us assign all object instance methods to a module and make the instance the last parameter, we'd get the ability to compose functions. Also, we'd make people move more towards functional programming, away from keeping functions and state together.
Another upside of this suggestion is that it doesn't require any large changes to the compiler as it's very similar to a type alias. That brings down the risk of introducing new bugs and makes the compiler easier to work with.
@haf I like this idea but I believe it really deserves its own suggestion!
@haf Cool idea!
So if I understand you right then property access would be similar to this?
type Foo = {
Bar : int
Baz : string
}
let a = [ foo1; foo2; foo3 ] |> List.map Foo.Bar |> List.sum
let b = foo1 |> Foo.Bar
@haf that seems like it could be better addressed by https://github.com/fsharp/fslang-design/issues/125 than a direct change to the compiler. It's basically the same as the StaticMemberProvider<...>
in my comment 😉
@haf brilliant idea!
I like the automatic currying, but I can't imagine how this works if the method has multiple overloads. Any ideas?
Well, that suggestion a long way from what is proposed here. I agree with @cloudRoutine that the feature feels much more like adhoc macro meta-programming and should be covered by #125 and related type provider suggestions.
The automatic currying is not aligned with existing elements of the F# design, and won't cope with interop features such as params-args, named arguments, optional arguments or method overloading. Also the point of the feature described here is succinctness, where Strings.split
requires a type-name qualification at every use point.
My beef with the feature as described in this thread is that it introduces syntactic sugar that you need to learn the semantics of. Instead, the assign to module way would let you explore the available functions and documentation whilst reaching the same goal – to make it quicker and more succinct to use instance methods. So I'm personally against this suggestion, because it increases the mental burden of the programmer to understand what _ refers to.
Like Don says, there are many edge cases due to how interop works which is why a type provider would probably not be enough – the compiler would be a good position to hide all that complexity and provide a unified naming standard for interop function suffixes to the functions.
If I could "spend time" on the compiler, I'd explore the stronger type system, compile-to-binary, making opt-in structural typing like F* has and making performance improvements to async, or providing guidance around system programming. Or making equals behave well with arrays and mutable data structures, like Vesa wrote about the other day.
Succinctness btw?
module _ = System.String
There we go! ;)
I do sympathize with the argument of "yet more special syntax to learn". I could tolerate (.Property)
- which feels like it is very easy to learn - and could probably tolerate _.Property
. But the _ + 1
suggestion has very many corner-case rules to learn and offers many opportunities to write cryptic code, and that concerns me.
there are many edge cases due to how interop works which is why a type provider would probably not be enough - the compiler would be a good position to hide all that complexity
To be honest, these are also the reasons why we wouldn't put this in the compiler, and instead push it off it to a beefed-up type-provider/meta-programming mechanism, if at all. Auto-interop mechanisms are just too heuristic, too edgy, likely to change over time, and offers a second, largely idiosyncratic way to write F# code.
to make it quicker and more succinct to use instance methods.
I don't particularly see how the suggestion will make using instance methods more succinct (or even quicker). Typing
expr |> LongTypeName.methodNameWithOverloadQualifier arg1 arg2
is clearly much less succinct and less quick than
expr.MethodName(arg1,arg2)
I'm assuming you are using an editor that offers type-based completion. Likewise
exprs |> List.map VeryLongTypeName.propertyName
is not more succinct or quicker than
exprs |> List.map (.PropertyName)
or
exprs |> List.map _.PropertyName
I think (.Property)
is a concise extension to the language that would help to make simple expression clearer, I see it similar in use to function
not in behaviour but in generally comprising simple syntax removing unnecessary boilerplate. I don't think I would want tit to become any wider in usage.
The discussion on generating functional accessors for bcl wrapping could be done with type providers now if anyones comfortable with a bit of stringly reflection. Admittedly a bit of a powerup to type providers would make it a lot easier, even just finishing off unchecked quotations in providedtypes.fs
would help there too.
@7sharp9 The problem with (.Property)
is that it encourages using the language in the "wrong" way. You're calling methods on objects instead of applying functions to arguments. If you wanted to program in an OO style, then why not just use C#?
@varon using (.Prop)
is shorthand for (fun p -> p.Prop)
your just eliminating the boiler plate on such a function.
I understand that, @7sharp9. I think you've either completely missed what I'm getting at here.
In the case of (.Prop)
or (fun p -> p.Prop)
, it's obvious by inspection that they're semantically identical. We should try to get completely away from having to 'dot into' objects at any point. Adding syntactic sugar for this is a kludge fix for a problem we don't encounter at all if we access members by function instead of by methods. You would simply call the function prop
on p
to get the value of Prop
from it.
@varon the proposed syntax simplifies interop with .net framework and with almost all the libraries available. What's more, F# records encourage using dot notation and it's not oop in any way.
I can see the argument for trying to veer away from OO, although F# is designed to mix rather well with interop with OO.
I also like the idea of having free functions, although the tooling is not yet there (opposed to the dot-completion that really sunk deep in the developers workflow and is supported in all languages), that being said, I see that resharper is bringing post-fix completion to C++ (https://blog.jetbrains.com/dotnet/2016/11/01/resharper-ultimate-2016-3-eap-6/):
https://d3nmt5vlzunoa1.cloudfront.net/dotnet/files/2016/10/cpp_postfix_completion.gif
And I believe completion tooling should evolve to next step, with feature such as finding functions that can be called with nearby symbols and proposing functions / symbols that are nested.
In day to day F# code I write, it is true that having short hand access to members instead of full fledged lambda would reduce some amount of clutter, I hope that suggestion is going to get good proposal / RFC draft with community input.
This is a feature that I would love to steal from Elm :) In Elm, if I define a record as such
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
I can write code like this
This 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
Questions
Are indexers allowed
Are method calls allowed
Is this 1-place placeholder syntax?
Is this multi-place placeholder syntax?
Pros and Cons
The advantages of making this adjustment to F# are
The disadvantages of making this adjustment to F# are
Affadavit (must be submitted)
Please tick this by placing a cross in the box:
Please tick all that apply: