Adding a dot before the parameter and property name when calling methods
I propose we allow apply values to parameter or property when calling method with the following syntax:
let a = ObjectConstructor(
.Parameter = xxx,
.Property = xxx) // add a dot before the parameter or property name
Pros and Cons
The advantages of making this adjustment to F# are:
- It's a way to not break
f (x=1)for #151. - Make parameter and property names more discoverable when coding, as they are flooded easily by assorted of names in the completion list.
The disadvantages of making this adjustment to F# are add a new syntax to do the same thing.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): S-M
Related suggestions: #151, #969
Affidavit (please submit!)
Please tick these items by placing a cross in the box:
- [x] This is not a question (e.g. like one you might ask on StackOverflow) and I have searched StackOverflow for discussions of this issue
- [x] This is a language change and not purely a tooling change (e.g. compiler bug, editor support, warning/error messages, new warning, non-breaking optimisation) belonging to the compiler and tooling repository
- [x] 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
- [x] I have searched both open and closed suggestions on this site and believe this is not a duplicate
Please tick all that apply:
- [x] This is not a breaking change to the F# language design
- [x] 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 :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.
It is a problem on tooling side, which can and should be fixed in tooling. I remember fixing it in FSAC, but it seems to be broken again
It is a problem on tooling side, which can and should be fixed in tooling. I remember fixing it in FSAC, but it seems to be broken again
Yes in VS Code + Ionide it can sometimes shows the method parameters and properties in the top of completion list, but the top is always abs and other things.
I was just going to propose the exact same thing. I support this wholeheartedly ‒ the fact that the following code has completely different interpretation depending on which of the blocks you uncomment is troubling:
(*
module Test =
let Call x = x
*)
(*
type Test =
static member Call x = x
*)
let x = 20
let y = Test.Call(x = 1)
y is either bool or int. This could easily become an issue if you move code from static classes to modules, since there is no syntax to express that x = 1 is really a parameter and not equality check, and it could cause hard-to-detect bugs. When it is already possible to express the converse (Call((x = 1)) to enforce equality), it should be possible to remove the ambiguity on both sides. That is similar to indexing where you can do x.[1] or x([1]) to make either interpretation explicit.
The proposed syntax is great to remove the ambiguity. Dot is already used in a few languages that I know of for this purpose, for example Pawn.
I do like the additional safety and robustness (against "code somewhere above changes") this brings. At the same time, it is another of those "two ways for doing the same thing" situations making the language more complex to beginners.
One could argue that similar situation was there with indexers - with . and without it. But with indexers, the change was about a new recommended default better aligned with the industry, not to maintain 2 equally viable alternatives.
Making (.Prop=1) the new recommended default is not something I would see as doable now , especially if the motivation of this suggestion can be solved via tooling instead.
@T-Gro I am perfectly okay with the dotless syntax staying the recommended default, after all dotless indexing is also the default. However, my case is not solvable via tooling ‒ I really need to be sure that any arbitrary method has the exact same parameter name as written in code and that parameter name will be used as opposed to a variable. The fact that F# cannot express this in syntax (as opposed to a lot of other languages) is a blocker for me.
Interestingly, I have also opted-in to the dotted indexer syntax in some places, for the exact same reason ‒ I really need to be clear that the code calls the indexer instead of calling a function with a list as its argument. I am extremely appreciate having both options, one that is quick and concise but syntactically ambiguous, and one that is clear and explicit, at the cost of being slightly longer.
@IS4Code : What if you could have a warning that triggers every time an equality expression in method arguments without explicit parens (x = 1) shadows a settable property/parameter symbol with the same name ?
@T-Gro Hmm, a warning might help actually, but it needs to be unconditional ‒ any time an unparenthesized equality expression appears in the arguments and it does not correspond to a property/parameter, it should be a warning. This is the situation that I need to prevent:
type C =
static member M(p : bool) = p
// Different parameter name
static member N(q : bool) = q
open type C
// Same logic but as a function
let O(p : bool) = p
do
let p = false
// Calls a method with the correct parameter name ‒ prints "false"
let r1 = M(p = false)
printf $"{r1}"
// Calls a method with an incorrect parameter name ‒ "The member or object constructor 'N' has no argument or settable return property 'p'." ‒ this is okay too.
let r2 = N(p = false)
printf $"{r2}"
// Succeeds but does a silent comparison ‒ prints "true".
let r3 = O(p = false)
printf $"{r3}"
I would need the last case to produce a diagnostic.
To explore the present situation:
- Calling a method with parenthesized arguments (i.e.
M(x),M(x, y)etc.) always validates that the name is an actual parameter/property. This is great. - Calling a function in the same style never validates parameters (because it is not a method).
I can easily prevent an equality expression being bound to a parameter/property because I can just write it in parentheses. The problem is the converse ‒ an equality expression that could bind to a parameter/property, but does not and results in the real equality operation.
In other words, I would need a warning (opt-in) for all of this:
f(x = expr) // whenever `=` would lead to the equality operation when used in a single expression
f(..., x = expr, ...) // or as a part of a tupled call
f(...)(x = expr) // or as a part of a curried call
(expr)(x = expr) // and even in this
// and maybe other similar situations?
This warning should also appear even when x is not bound, i.e. it should appear in addition to the error "The value or constructor 'x' is not defined." (pretty much as soon as it tries to look for it), so that the programmer knows why it is trying to look up that.
I suggest this message for the warning:
This call expression uses the syntax 'name = value' for one of its arguments, but '
x' is not bound to a parameter or property in this context. If the actual equality operation is intended here, write it in parentheses as: '(name = value)'.
I would be fine with this as the solution.
Copy/pasting most of this comment here for the record:
I assume Don meant that we wouldn't want to use OCaml's f ~x:3 for labeled function arguments—i.e., specifically using : instead of =—since we already have C.M (x=3) for methods:
Given the issue with
x=y, would it not be too terrible to have OCAML syntax?We would just put it under a
/langversion:5.0switch.There's no chance we would use
~syntax here, given we already support named arguments for member calls.
But using ~ to unambiguously indicate a parameter label in both method and function applications, while keeping =, might work.
E.g.,
let f x = x
type C = static member M x = x
let x = 3
// Current
f x // 3
f (x=x) // true
C.M (x=x) // 3
C.M ((x=x)) // true
// Proposed: all of the above remain valid, but we can now explicitly label parameters in applications with ~
f ~x=x // 3
C.M (~x=x) // 3
(Note that I am not proposing that ~ be used anywhere other than application.)
~ has the advantage over . that it could be used in function applications without requiring extra parenthesization:
let x = 3
let y = 4
f ~x=x ~y=y
. would require parentheses, or else the parameter label would be interpreted as a member lookup on the function:
let x = 3
f .x=x // This would be parsed as `(f.x) = x`.
The semantics would be that ~symbol can only ever resolve to a function/method parameter name, and name resolution would prevent from resolving it to any other scope?
@T-Gro The ~ syntax should also be usable as tuple field names (OxCaml does that) - #1434
It would match tuple field names in method/function parameters if possible. It would define a new tuple otherwise.
So
f (~x = 1, ~y=false) can either:
- Call function with two parameters
xandy - Call function
fwith a single tupled parameters, which is a named tuple with fieldsxandy - Both, because for every practical usage they will be treated like the same concept?
I'd say both - also applicable for both methods and functions
I am happy with that solution (definitely "both") as well. Anything that distinguishes an assignment to parameter/property/field from the equality operator (and only from the equality operator) is fine by me.
https://github.com/fsharp/fslang-suggestions/issues/1414#issuecomment-3052692011
So
f (~x = 1, ~y=false)can either:
- Call function with two parameters
xandy- Call function
fwith a single tupled parameters, which is a named tuple with fieldsxandy- Both, because for every practical usage they will be treated like the same concept?
I think f (~x = 1, ~y=false) would be a function taking a tuple.
C.M (~x = 1, ~y=false) would be a method taking two parameters or a single tuple parameter (which are currently normally interchangeable).
f ~x=1 ~y=false would be a function taking two parameters.
Oh, I see what you mean - I've always thought of the compiled representation of
let f (x, y) = x + y
let f' x y = x + y
having the same compiled representation i.e. "two arguments in C# view" but from F# one is statically with 2 arguments and one is statically with one argument of a tuple.
We can go further. When a function/method's parameter has the same name as a property of the returned object, there should also be a way to distinguish them: use ~x=1 to denote the parameter x, and .x=1 to denote the property x. For example:
type A() =
member val x = 1 with get, set
static member M (?x: int) =
A()
A.M(x = 2) // Set method parameter `x` to 2; cannot set the object property `x`
// And all of these cannot work when we want to set the property `x`
// A.M(2, x = 3)
// A.M(?x = Some(2), x = 3)
// Current workable approach
A.M(x = 2) |> fun i -> i.x <- 3; i
After introducing the disambiguation syntax:
A.M(~x = 2, .x = 3) // Set method parameter `x` to 2 and object property `x` to 3
The prepend . can be used for all kinds of properties including returned object's property, record properties in copy syntax (#1456, and maybe in construction and pattern match if we want nesting field in these situations), and the ~ for parameters and tuple labels.