Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC FS-1003 Discussion] nameof Operator #48

Open
enricosada opened this issue Feb 4, 2016 · 42 comments

Comments

@enricosada
Copy link
Contributor

commented Feb 4, 2016

This issue is used to track discussions of F# RFC FS-1003 - "nameof Operator". Please discuss in thread below (if necessary)

@dsyme dsyme referenced this issue Feb 4, 2016
17 of 18 tasks complete
@dsyme

This comment has been minimized.

Copy link
Contributor

commented Feb 4, 2016

I added some comments to the proposed PR here: dotnet/fsharp#908 (comment)

I'm concerned about the implementation strategy and think it should probably be done earlier in the type checker.

Some clarifications to the spec are needed

  • Can nameof be used on ambiguous overloaded methods?
  • Can nameof be used on properties that only have setters?
  • Can nameof be used on constructors?
@thinkbeforecoding

This comment has been minimized.

Copy link

commented Feb 5, 2016

Since nameof is just the name, ambiguous overloaded methods still all have the same name, so it should work.
Same thing for properties with only setters.
For ctors, the question is.. What's the name of a constructor... If it's the same as the type, we can just use that.

@dsyme

This comment has been minimized.

Copy link
Contributor

commented Feb 5, 2016

@thinkbeforecoding I believe nameof(C.M) will raise an ambiguous-overload type checking error in the current implementation if C.M is overloaded?

@forki

This comment has been minimized.

Copy link
Member

commented Jun 20, 2016

I'm concerned about the implementation strategy and think it should probably be done earlier in the type checker.

@dsyme where should I try to hook it in?

@dsyme

This comment has been minimized.

Copy link
Contributor

commented Jun 20, 2016

I think it's captured by this comment:

I believe we should reconsider the implementation of the feature and intercept nameof during type checking. Instead of checking the argument we would require that it is a long identifier and do unqualified expression name resolution on that identifier to a symbol, and then just take the name of the symbol. I believe that is more in the spirit of how the feature is intended to work.

So there would be a special case in the type checker

@forki

This comment has been minimized.

Copy link
Member

commented Jun 21, 2016

Yes I got that. I was wondering if we already have a place where we pattern
match on the tast and rewrite it.
On Jun 20, 2016 2:41 PM, "Don Syme" notifications@github.com wrote:

I think it's captured by this comment:

I believe we should reconsider the implementation of the feature and
intercept nameof during type checking. Instead of checking the argument we
would require that it is a long identifier and do unqualified expression
name resolution on that identifier to a symbol, and then just take the name
of the symbol. I believe that is more in the spirit of how the feature is
intended to work.

So there would be a special case in the type checker


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
#48 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/AADgNGsbc-cTQ_di1BMsi4_XSDEjHNOTks5qNophgaJpZM4HTlfR
.

@dsyme

This comment has been minimized.

Copy link
Contributor

commented Jun 21, 2016

Perhaps the closest is in TcFunctionApplicationThen where we look for seq { ... } before we check the argument. You could modify that to look for a special nameof value, and then apply completely different rules/restrictions for the processing of the argument.

There are also similarities with the processing of the reraise special value, which is only allowed at certain positions. But that has no special type-checking rules and its argument is processed as a normal argument.

@smoothdeveloper

This comment has been minimized.

Copy link
Contributor

commented Jul 22, 2016

I don't see any mention of [<CompiledName("name")>] and I think this is an area which could get users in "principle of least surprise failed" (either way this would be handled I guess).

What is the expected behaviour in interaction with that attribute?

@thinkbeforecoding

This comment has been minimized.

Copy link

commented Jul 22, 2016

Interesting question.
The feature seems quite private to F# and does not seem to be meant for reflection purpose - at least not to get a method info from name....
So I'd say it should return the F# name, not the compiled name.

@Thorium

This comment has been minimized.

Copy link

commented Sep 16, 2016

Main purpose of nameof is probably some logging purposes, and it would be useful on some async scenarios when the stacktrace won't tell you all and you can't reraise. I would also vote the name in F# source code.

@Rickasaurus

This comment has been minimized.

Copy link

commented Nov 3, 2016

Main purpose of nameof is probably some logging purposes, and it would be useful on some async scenarios when the stacktrace won't tell you all and you can't reraise. I would also vote the name in F# source code.

I want nameof mostly for some amount of type safety in my reflection code. When the name of a type is changed I want to be able to get an error (or gui rename-update) for that change. I think ideally we would be able to just copy what C# nameof does.

@dsyme

This comment has been minimized.

Copy link
Contributor

commented Jan 18, 2017

New implementation at dotnet/fsharp#2290

@smoothdeveloper

This comment has been minimized.

Copy link
Contributor

commented Jan 18, 2017

@Rickasaurus comment makes me think we could also have exprTypeOf operator which returns a System.Type in reflection scenarios, does that sound like "fslang-design new issue" worthy?

@neoeinstein neoeinstein added this to the 4.1 milestone Jan 27, 2017
@neoeinstein neoeinstein modified the milestones: 4.2, 4.1 Apr 11, 2017
@abelbraaksma

This comment has been minimized.

Copy link

commented Sep 23, 2017

I had some trouble understanding the whole requirements list on RFC FS-1003, would it be ok if I pulled it and add one or two examples to each bullet point for clarity?

I also noticed two other things that are either missing or deliberately not included:

  • Works with both fully qualified names, global:: and unqualified names, provided they are in scope (i.e. nameof String and nameof System.String (yes, this seems obvious, but implementation details are not part of the RFC and it could've been implemented at tokenization phase).
  • Works with constants (i.e., it should be allowed to create a literal)

Also, I was wondering about the first couple of bullet points. It seems to me this should work as a keyword (like if or let), after all it is resolved at compile time, but some examples suggest it can be used assigned to a function, piped to etc. I don't necessarily object to that, but is that the current train of thought of how this is supposed to be implemented, or is discussion still open?

@cartermp cartermp removed this from the 4.2 milestone Oct 3, 2018
@dzmitry-lahoda

This comment has been minimized.

Copy link

commented Feb 10, 2019

C# got some nice "statically" typed patterns with this. Hope F# would get that soon.

@vasily-kirichenko

This comment has been minimized.

Copy link

commented Feb 10, 2019

@dzmitry-lahoda I don’t think anyone is gonna finish it, the last attempt was mine, rebased and fixed @forki’s implementation, the last thing was left to done is making it work it on overloads, Don promised to help, but only promised, as it happens more and more these days.

@dzmitry-lahoda

This comment has been minimized.

Copy link

commented Feb 10, 2019

We will enable and encourage strong community participation in F# by continuing to build the necessary infrastructure and tooling to complement community contributions. We will make F# the best-tooled functional language on the market, by improving the language and tooling experience, removing road blocks for contributions, and addressing pain points to narrow the experience gap with C# and VB. As new language features appear in C#, we will ensure that they also interoperate well with F#. F# will continue to target platforms that are important to its community. 1

@abelbraaksma

This comment has been minimized.

Copy link

commented Feb 11, 2019

Don promised to help, but only promised, as it happens more and more these days.

When I see the amount of work on his plate and how thoroughly active he is on many threads, that's not too surprising. There are so many issues, it's hard to keep up, while also keeping improving the language with new features. I highly respect how much he and others of the team are participating, and surely, some issues and up under the rug, though I doubt that's intentional in any way.

Also, I don't think you we should strive for 100 percent parity, not all C# features are equally well suited for F#, but that's another discussion entirely.

@dsyme

This comment has been minimized.

Copy link
Contributor

commented Mar 12, 2019

The revamped implementation of this feature is at dotnet/fsharp#6325

@jwosty

This comment has been minimized.

Copy link
Contributor

commented Jun 7, 2019

For reference, this is being continued in: dotnet/fsharp#6809

@cartermp cartermp added this to the F# 4.7 milestone Aug 3, 2019
@cartermp

This comment has been minimized.

Copy link
Member

commented Aug 3, 2019

Closing as this is in and shipping

@cartermp cartermp closed this Aug 3, 2019
@dsyme dsyme reopened this Aug 16, 2019
@dsyme

This comment has been minimized.

Copy link
Contributor

commented Aug 16, 2019

Reopened as the feature is in preview

@cartermp

This comment has been minimized.

Copy link
Member

commented Aug 16, 2019

I think something that could be specified in the RFC is some formalism around what 'T actually is for the nameof function. For example, we had two people already mention that taking the name of an instance member, without the instance, should be allowed. But that's not a valid expression in the traditional sense. Yet it makes perfect sense for nameof. So that implies a new kind of expression, similar to how C# does it by defining a named-expression.

@dsyme

This comment has been minimized.

Copy link
Contributor

commented Aug 16, 2019

I think something that could be specified in the RFC is some formalism around what 'T actually is for the nameof function.

Yes, I agree, it's currently syntactically an expression but is processed as a name resolution item (with slightly varied resolution rules).

@Happypig375

This comment has been minimized.

Copy link
Contributor

commented Sep 29, 2019

Please implement for literal definitions.

let rec [<Literal>] SomeBindedPropertyName = nameof SomeBindedPropertyName
// Error FS0837 This is not a valid constant expression
// Error FS0267 This is not a valid constant expression or custom attribute value
// Error FS0039 The value or constructor 'SomeBindedPropertyName' is not defined.

Compare:

let rec SomeBindedPropertyName = nameof SomeBindedPropertyName // OK for F# non-literals
const string SomeBindedPropertyName = nameof(SomeBindedPropertyName); // OK in C#
@abelbraaksma

This comment has been minimized.

Copy link

commented Sep 29, 2019

I thought the rfc mentioned somewhere that this operator was allowed in literals, or at least that it was planned.

@Happypig375

This comment has been minimized.

Copy link
Contributor

commented Sep 29, 2019

Use in attributes is permitted but not in literals.

@abelbraaksma

This comment has been minimized.

Copy link

commented Sep 30, 2019

@Happypig375, I see, you are right, there indeed isn't any mention of literals or constants in the FS-1003 RFC text. In fact, there is only one mention of a "can be used in", which could perhaps be expanded with some locations where it can, or cannot be used.

Though, arguably, your example uses SomeBindedPropertyName, which to me suggests that it points to an instance property. At the moment, an instance of a class is needed for instance members (as opposed to static members). Same is true for overloads. As it currently stands, that can't work in attributes, nor in literals.

@Happypig375

This comment has been minimized.

Copy link
Contributor

commented Sep 30, 2019

This won't compile either:

let rec [<Literal>] X = nameof X
@Happypig375

This comment has been minimized.

Copy link
Contributor

commented Oct 9, 2019

member vals do not have the ability to refer to themselves. Therefore, nameof on themselves are not permitted.

type X() as this =
    inherit Dictionary<string, string>()
    member val XValue = this.TryGetValue("XValue") |> function true, x -> ValueSome x | false, _ -> ValueNone // OK
    member val YValue = this.TryGetValue(nameof YValue) |> function true, y -> ValueSome y | false, _ -> ValueNone // FS0039 The value or constructor 'YValue' is not defined.
    member val rec ZValue = this.TryGetValue(nameof ZValue) |> function true, z -> ValueSome z | false, _ -> ValueNone // FS0010 Unexpected keyword 'rec' in member definition. Expected identifier or other token.

Please enable this.

@cartermp

This comment has been minimized.

Copy link
Member

commented Oct 9, 2019

@Happypig375 #403

@dsyme

This comment has been minimized.

Copy link
Contributor

commented Oct 10, 2019

My preferred resolution of these:

  1. Issue: nameof on an operator gives the compiled name of the operator

    Proposed resolution: by design (I'm really not fussed by this nit, and it is really expensive to resolve - e.g. if the user explicitly uses op_Addition do they get op_Addition?)

  2. Issue: Resolving instance members requires an artificial instance object.

    Proposed resolution: by design (I'm really not fussed by this nit)

  3. Issue: nameof may not be used with the name of a generic type parameter.

    Proposed resolution: by design (I'm really not fussed by this nit, and the decision was by design because expression syntax doesn't include type variables)

  4. Issue: Resolving overloaded members requires a type instantiation.

    Proposed resolution: by design (I'm really not fussed by this nit)

  5. Issue Using nameof in a literal, e.g.., let [<Literal>] Foo = nameof Bar

    Proposed resolution: Fix this for sure

  6. Issue: Specifying nameof on a member val

    Proposed resolution: Have you tried

    nameof this.YValue
    

    (I haven't double checked as would need to compile the branch but I think it will work)

  7. Issue: Specifying nameof on a value without needing to use rec, e.g., let x = nameof x

    This is certainly by design, why would you be able to use nameof when x is not in scope?

@cartermp

This comment has been minimized.

Copy link
Member

commented Oct 10, 2019

I think if we go with the current design where the value is a normal value, then (7) makes sense to resolve as you've put it. But Given that there's already a desire to make it a named-expr akin to C# then perhaps different resolution rules can apply, much like how in C# you can take the name of something by fully qualifying it even though it's not a static member. I'm personally fine with requiring rec, but worth thinking about.

@dsyme

This comment has been minimized.

Copy link
Contributor

commented Oct 10, 2019

I feel the guiding principle for nameof(x) is that it roughly corresponds to statichandleof(x).Name, for some general statichandleof(x) construct that returns whatever reflection handle is available for x. This construct doesn't actually exist but it could - FSharp.Core for example has a methodhandleof(x) construct that's used internally.

So in this way of thinking I guess the question is "what's the natural F# way to get a static handle for the thing being defined in let x = ...". I don't have an immediate answer for that as it's just not a normal thing to be able to do in an ML-family language. Simply referring to the thing via x doesn't seem at all right as there might be another x in scope, e.g.

type x = A | B

let f x = 
    let x, y, z = nameof(x), statichandle(x), typeof(x)
    (x, y, z)

Here each x potentially resolves to a different semantic thing. Now, for nameof(x) no one cares if you're logically speaking referring to a different semantic thing because they have the same name. But I'm just queasy with having random rules for nameof, it will just be a bug farm and full of whacky corner cases.

So if x is not in scope the whole language feature just to feel iffy - a somewhat random collection of complex new resolution rules, for little clarifying purpose, and not orthogonal to anything else in the language. I'm somewhat OK with this approach if it focuses very much on software-engineering utility, which is the only rationale for this whole feature - but I feel we've done that with the feature already and it captures the 99% of utility.

@abelbraaksma

This comment has been minimized.

Copy link

commented Oct 10, 2019

On (2) and (4), I don't know what the complexity is of resolving this, but a common use case for nameof is in logging and exception message generation.

I can think of numerous issues with requiring an instance, but to name the two biggest elephants in the room: side effects of constructors, and impossibility of using nameof from within a constructor (on instance members). Imo these are severe limitations on what ought to be only a compile time feature.

I don't know if the artifical instance is optimized away, but if it is, that would give other problems, for instance with predictability of code execution or the 'whatifs' when the instance is reused.

It also limits its use cases, i.e. instances cannot be used in attributes or literals, and the production of a nameof expression should yield a constant. Therefore, its expression must be a constant expression. Requiring an instance negates that.

Finally, I think it's just hard to explain that for a supposedly compile time feature you need an instance.

Resolving this might also resolve (4), which aids the usefulness.

@cartermp

This comment has been minimized.

Copy link
Member

commented Oct 10, 2019

I'm somewhat OK with this approach if it focuses very much on software-engineering utility, which is the only rationale for this whole feature

This is correct. It's a compile-time only feature that aids primarily in the removal of magic strings in codebases, making things easier to refactor over periods of time. It's already not a normal function (you can't just call it with an older compiler, you can't use it as a first-class value), so I think we've sort of crossed the rubicon on the issue of not being orthogonal to the rest of the language.

Needing to add type annotations or construct an ephemeral object just feels strange for this kind of feature. If it were a normal function that at runtime gives you a string, sure. But that's clearly not the case here.

@dsyme

This comment has been minimized.

Copy link
Contributor

commented Oct 11, 2019

Finally, I think it's just hard to explain that for a supposedly compile time feature you need an instance.

Note you can use Unchecked.defaultof<C>.M for the instance

The F# name resolution rules just make C.M a static method and c.M an instance method (for instance c), and there are subtle differences in the name resolution for each. Is the proposal that C.M be usable for nameof as well? I mean, it's possible I think, but just odd to have whole new sections of the spec and implementation for this one construct.

Anyway yes, for (2) and (4) I'm ok with making the type annotations optional and allowing C.M for instance methods. I'm no great fan but hey...

so I think we've sort of crossed the rubicon on the issue of not being orthogonal to the rest of the language.

To my knowledge we've not crossed the line where it is unaligned with the resolution rules for some future statichandleof or propertyof or public methodof etc. Note that all of these are genuinely useful for removing magic logic from reflection programming, like typeof.

The main thing here is (4), i.e. methodof requires a type instantiation to pinpoint an exact method overload.

Perhaps we should aim for nameof and other future xyzof to align apart from (4).

@abelbraaksma

This comment has been minimized.

Copy link

commented Oct 11, 2019

Note you can use Unchecked.defaultof<C>.M for the instance

In some scenarios, yes (though I didn't know that a null reference would work here, it is good to know). But an important use-case for nameof is in constants and in attributes that require a string that points to a member. For that to work, the expr in nameof (expr), must itself be expressible as a constant expression for instance members.

With other use-cases, like for making code with ArgumentNullException etc more maintainable, this is less of a concern I guess.

Anyway yes, for (2) and (4) I'm ok with making the type annotations optional and allowing C.M for instance methods. I'm no great fan but hey...

I understand the reluctance, but C# does it the same way, which may ease the transition between the languages.

The main thing here is (4), i.e. methodof requires a type instantiation to pinpoint an exact method overload.

I don't understand this part, if there's a method group (of overloads) I assume it doesn't matter whether you pick the first, the last or anything in-between, they will all have the same name anyway, or is this is a detail that is simply difficult to work-around given the current state of internal compiler library methods?

@cartermp

This comment has been minimized.

Copy link
Member

commented Oct 11, 2019

Yeah, I don't quite understand the issue with overloads here. C# doesn't do any overload resolution, since each named method is the same. I brought this up in the original PR a while back. I certainly wouldn't expect a different name based on overloading.

Note that all of these are genuinely useful for removing magic logic from reflection programming

I imagine so. And we may need to consider those cases differently should they be on the table for a future F# release. But I don't think this quite applies to nameof. Although nameof is helpful when writing reflection code, the primary utility is as a software engineering tool that lets you rename constructs over time without fear that you're logging a name of something incorrectly. It feels kind of unique in that sense, hence the alternative proposal that we consider a similar design to C# wherein the expr is really a named-expr with slightly different resolution rules. It certainly feels more natural when using it in practice to do it that way.

@dsyme

This comment has been minimized.

Copy link
Contributor

commented Oct 15, 2019

Although nameof is helpful when writing reflection code, the primary utility is as a software engineering tool that lets you rename constructs over time without fear that you're logging a name of something incorrectly.

Right, but that's exactly the point - we must have accuracy in knowing which symbol things resolve to in order to implement renaming. So for example consider renaming the different 'x' here:

let f  x =
    let x = nameof x
    let x = nameof x
    x

When you rename the x in the first let x does the nameof(x) rename? The suggestion to make x accessible out of scope would make the renaming very strange here.

The same applies to method overloading. In the VF# tools, renaming is based on a specific overload, e.g.

type C() = 
    member c.M1() = 1
    member c.M1(x: int) = 1

module Say =
    let c = C()

    let y = c.M1()
    let z = c.M1(3)
    0

When you rename the first M1 only that specific overload is renamed. So for

    let n = nameof(c.M1)

do we rename or not? There is no right answer anymore....

@dsyme

This comment has been minimized.

Copy link
Contributor

commented Oct 15, 2019

I don't understand this part, if there's a method group (of overloads) I assume it doesn't matter whether you pick the first, the last or anything in-between, they will all have the same name anyway

That's correct for nameof though not for methodof

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.