Anonymous fields #2824
Replies: 52 comments
-
Isn't a function, with required storage, just a class? Why not just implement your libraries with classes that just keep track of their associated storage/state, and which clients interact with through an exported method? |
Beta Was this translation helpful? Give feedback.
-
I second this, however, I could see the point of method-scoped static (or maybe local) variables, e.g.: public (int Globally, int InThisInstance) HowManyTimesWasThisFunctionCalled()
{
static int g = 0; // only initialized once statically
instance int i = 0; // only initialized once per instance
return (++g, ++i);
} which would be translated to something like this: public class Class1
{
// compiler generated
private static int $_field1 = 0;
private int $_field2 = 0;
public (int Globally, int InThisInstance) HowManyTimesWasThisFunctionCalled()
{
return (++$_field1, ++$_field2);
}
} |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi this would have been practical except the storage needs to be call site bound on the caller side, not the implementing function's. |
Beta Was this translation helpful? Give feedback.
-
Why? Why can't hte caller provide the appropriate instance in they want to execute the function on?
I don't know what this means. Since you are allocating a class (or struct if you so wish) it can just keep whatever storage it needs already associated with it. |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi you are absolutely right, the caller should provide the appropriate instance; what is being discussed here is whether the compiler, or the user, should supply the declaration of said instance.
In other words, the allocated storage is an implementation detail of the called function and, from the API user's point of view, having to explicitly declare instances of said storage type is at best a meaningless chore, and at worse providing an opportunity to err. Going back to your initial comment ("isn't it just a class"), encapsulating the needed data and functionality using a class does not solve the problem; you would then have a type, whose instances, although intended to be call site bound, need to be named - which contradicts intended use, and enables misuse. |
Beta Was this translation helpful? Give feedback.
-
Define 'call site associated with the called function'. For example, say i have: void Foo<T>()
{
YourFunction();
} if i call
What about: class C
{
void Foo() { YourFunction(); }
}
...
new C().Foo();
new C().Foo(); Is this a single callsite? You say that the number of instances is known at compile time. So in this case, this could only be a single callsite. but your OP says:
Indicating that you get a (non-static) field in the class. So i don't see how you'd know the number of instances ahead of time. |
Beta Was this translation helpful? Give feedback.
-
Currently your use case is too vague to understand why existing solutions would be insufficient. I get that you're saying they're not sufficient. My point is that you haven't given a good enough explanation for why that is, and it's not self evident given your description. Can you give a realistic use-case for why i'd want this feature and why existing approaches won't work well here? |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi I have provided three real world use cases for this. Let's consider the possible use taken from an existing, relatively (10+ years) mature library and expand on this; first without anonymous fields, and this is how the API is currently being used: using static EditorGUILayout;
class MyView{
Vector2 pos1, pos2, pos3;
void OnGUI(){
pos1 = BeginScrollView(pos1); // call site 1
// Render content of first scrollable view
EndScrollView();
pos2 = BeginScrollView(pos2); // call site 2
// Render content of 2nd scrollable view
EndScrollView();
pos3 = BeginScrollView(pos2); // call site 3
// Render content of 3rd scrollable view
EndScrollView();
}
} As above from the API user point of view, having to declare pos1, pos2, pos3 is not meaningful. In short, the API user are enlisted into explicitly declaring and re-assigning pos1, pos2, pos3. This is clutter. Also, it is clearly open to misuse (did you notice that I forgot to rename Let's modernize this example (no anonymous fields just yet): class MyView{
Vector2 pos1, pos2, pos3;
void OnGUI(){
BeginScrollView(ref pos1); // call site 1
// Render content of first scrollable view
EndScrollView();
BeginScrollView(ref pos2); // call site 2
// Render content of 2nd scrollable view
EndScrollView();
BeginScrollView(ref pos3); // call site 3
// Render content of 3rd scrollable view
EndScrollView();
}
} We avoided some repetition. However the situation has not changed: the API user still does not care about explicitly storing pos1, pos2, pos3, which at best can only introduce errors. Let's use anonymous fields. class MyView{
void OnGUI(){
BeginScrollView(); // call site 1
// Render content of first scrollable view
EndScrollView();
BeginScrollView(); // call site 2
// Render content of 2nd scrollable view
EndScrollView();
BeginScrollView(); // call site 3
// Render content of 3rd scrollable view
EndScrollView();
}
} With As above, the API designer could have used a The term "call site" is used as defined here. For want of a specific example related to my own use cases, see here and look at how |
Beta Was this translation helpful? Give feedback.
-
Stick some control statements in there and all of that goes out the window.
Mutable state is much more out of place in a set of functional APIs. C# is an OOP language, it is idiomatic to use classes here to contain state. It doesn't make sense to bend the language in this manner because you don't want to use any of the myriad of facilities that already exist to support your scenarios. |
Beta Was this translation helpful? Give feedback.
-
I will reframe this proposal, hoping to promote understanding, and appease OOP sensibilities. Assume a stateful service represented by type T. By design, assume the service is intended to be used on a one instance per call site basis (see my illustration, below). Now, let's further assume that the only use of the service is via a function class T{
public void F(){}
} Now, illustrating how class U{
T t0 = new T(), t1 = new T();
public void Use(){
t0.F();
t1.F();
}
} As per the current implementation of class U{
T t = new T();
public void Use(){
t.F();
t.F();
}
} Therefore, I am suggesting the use of anonymous fields, where class T{
public void Imp(){ ... }
static void F(implicit T arg) => arg.Imp();
} Finally, illustrating how the service can now be used safely, assuming compiler generated, anonymous members (and their mandated uses): class U{
// T t0 = new T(), t1 = new T(); // auto-generated
public void Use(){
T.F( /* t0 */ ); // argument supplied by the compiler
T.F( /* t1 */ ); // argument supplied by the compiler
}
} Other than trying to refocus on the core issue being addressed, I removed uses of As to the APIs, patterns and libraries discussed above, they are offered for illustration purposes. Whether they are good APIs, bend the language, or anything else... Yes, if no good API can use the proposed enhancement, that is a problem. Aside; primarily, I came across the proposed enhancement while designing a control framework. I am not about to "just stick some control statements in there". |
Beta Was this translation helpful? Give feedback.
-
Sounds like a design issue somewhat easily solvable via fluent APIs. That also gives you the advantage of being able to flow any state you want through the API without the consumer having to worry about it (or even be aware of it).: class U {
public void Use() {
T.F() // call site 1
.F() // call site 2
.F(); // call site 3
}
} Combining with lambdas it also provides the scoping that allows you to avoid the class U {
public void Use() {
T.ScrollView(() => {
// do stuff with first scroll view here
}).ScrollView(() => {
// do stuff with second scroll view here
}).ScrollView(() => {
// do stuff with third scroll view here
});
}
} |
Beta Was this translation helpful? Give feedback.
-
I don't see why it would feel out of place.
Which would make the code extremely clear and easy to reason about. If I see --. Your example also doesn't make sense since since each |
Beta Was this translation helpful? Give feedback.
-
You may also be interested in this proposal: #1151 It seeks to add something like Kotlin lambdas with receivers where the block is invoked with an implicit |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi specifically in the case of the widget rendering API, each widget is rendered using a function call. Not one widget here is objectified, as such it would be a quirk/exception, hence feel "out of place" (or much of the API would have to evolve). However in discussing this widget library my original intent was only to summon, as you might put it "real world" examples, by which I mean APIs which have matured, and been used over and over by many developers - this, in addition to my own examples, where anonymous fields have already been implemented, and feel helpful. If I had to emit an opinion on the GUI framework, the best I might say here is that, compared to both OOP style (Java's Swing) and reactive (Qt's) the GUI framework referred here feels at once quirky, lightweight and effective. Mutating the UI is done via regular control flow statements, which feels refreshing and speedy compared to managing object hierarchies (Swing). On whether naming the scroll views makes the code easier to reason about, I simply have a different opinion. I don't find either of these much easier or harder to reason about. To me, naming these scroll views doesn't feel more informative than labeling With that, if to you, one form feels more explicit and easier to reason with, I totally respect that. You write: But how could each actually be paired since there now is this implicit state ? It is really essential, to further this conversation, that we both have a clear understanding of how the enhancement is supposed to work. If anything else is unclear, please ask again. |
Beta Was this translation helpful? Give feedback.
-
I'm not really understanding. If i have three scroll views... what is weird about having 'objects' for those three scroll views? It's the norm in practically every UI toolkit i've ever used. It's simple, easy to understand and solves the communication/state-encapsulation well. In other words, it doesn't actually seem like something that is actually a problem that i would want to not do.
Yes. I understand that. but your example says: BeginScrollView(); // call site 1
// Render content of first scrollable view
EndScrollView();
BeginScrollView(); // call site 2
// Render content of 2nd scrollable view
EndScrollView();
BeginScrollView(); // call site 3
// Render content of 3rd scrollable view
EndScrollView(); You have three calls to |
Beta Was this translation helpful? Give feedback.
-
@HaloFour goes without saying. Already clarified that my proposed enhancement is about managing control state in either real-time or otherwise iterative processes, so there's at least a fair possibility that this is general enough to interest more than one library. Surely given the many requests for clarification I am getting here, re- starting from the one example I understand best would not be harmful? To me there isn't so much overlap between:
|
Beta Was this translation helpful? Give feedback.
-
Note: the statefulness here actually makes me feel like things are less clear. That's not the behavior i would have expected from any language. I'm not sure how i could effectively reason about any language where flow control was implicitly stateful. |
Beta Was this translation helpful? Give feedback.
-
Being concise has to be balanced with being clear. What you're proposing here is actually somewhat radical. I don't mean that in a negative sense. Just in a true assessment of hte scale of what you're asking C# to potentially do. It's something extremely new, extremely different, and very much against the grain of where the language has come from so far. That doesn't mean it shouldn't be done. But it does mean it has to be so super compelling that the benefits are obvious to lots of people immediately, thus making it worth the effort involved her.e So far, i'm not really seeing those benefits. In your examples so far, it actually doesn't seem to have helped things. It actively seems to make things more difficult to reason about, requiring a ton of knowledge and awareness of all this implicitness --
Why do (E)DSLs need implicit state for flow control? Can you point to existing DSLs in other domains where that's the case? |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi state implicit to a function call is definitely not new to C# or other OOP languages so this isn't at issue here. It can be done statically, or instance bound. It can even be site-bound although doing it this way is a little clunky in C#6~8. I've been pretty clear that I'm not implementing an EDSL, let alone a DSL. Why should I worry about these? Another commenter brought this up. Finally. To the extent that you are surprised, and trying to wrap your head around something that looks relatively new. In my opinion this is a good thing. If every suggested language enhancement just added a thin layer of polish, things would be pretty boring. |
Beta Was this translation helpful? Give feedback.
-
I'll be very clear about why I insist that this isn't related to EDSLs.
This might be okay for a number of applications (preferably a small percentage of the time put into any project) but, it produces code that's hard to debug (because what you're debugging isn't what you wrote), and doesn't feel native of the language its written in (almost definitionally). With an EDSL I've seen (Boost Spirit I think) just understanding compile time errors took considerable time. I am here because I do not wish to write another EDSL. What in my examples looks like a DSL? The use of conditional operators with three valued logic? A slightly intricate pattern for control flow ( |
Beta Was this translation helpful? Give feedback.
-
I specifically was referring to: "where flow control was implicitly stateful." |
Beta Was this translation helpful? Give feedback.
-
How are they implicitly stateful flow control in C#? |
Beta Was this translation helpful? Give feedback.
-
They are not implicitly stateful. The state is all explicitly stated by the user. |
Beta Was this translation helpful? Give feedback.
-
Sorry. Happy to table that then. But, in that case, i still go back to:
Not only is your own personal use case not coming across as compelling every time i look at it, you're also not providing any other use cases where this feature actually appears to be a good thing. |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi in this case maybe the proposed enhancement does not interest you right now, which is fine - as I understand you don't regularly write user facing, interactive applications, or video games? There's a big difference in how people perceive enhancements to a language, factoring:
With that, each programmer have their own style. In my daily practice I avoid co-routines; but they are popular with many game developers. Took me significant effort (and goodwill) just figuring why they use them, and how. End result still not using them, but some respect for how others approach them. Please contribute something here. Repeating endlessly that you don't see the point of this (and you've been doing it from the very beginning, at a point where your understanding of the feature wasn't even that clear) is still just one vote out. I dedicated time and effort to explaining this feature to you specifically. I'd be delighted if you could come with one case where you'd find it useful. Otherwise, never mind. |
Beta Was this translation helpful? Give feedback.
-
@eelstork I'm not sure that your attack on @CyrusNajmabadi is justified. I hope you're aware that this repo has a code of conduct. The onus is on you - as the proposer of the feature - to clearly explain what it does and to provide a good motivation why existing solutions are so poor that language support is required. So far, your explanations haven't made sense to me - and I suspect that I'm not the only one. I applaud @CyrusNajmabadi efforts to get you to clearly explain what it is that you want the compiler to do. FWIW, I've read through this entire thread multiple times trying to understand what you're asking for - and I still have no idea at all. |
Beta Was this translation helpful? Give feedback.
-
I'm glad it's not just me, then :-)
…On Fri, 4 Oct 2019 at 10:32, Bevan Arps ***@***.***> wrote:
@eelstork <https://github.com/eelstork> I'm not sure that your attack on
@CyrusNajmabadi <https://github.com/CyrusNajmabadi> is justified. I hope
you're aware that this repo has a code of conduct.
The onus is on you - as the proposer of the feature - to clearly explain
what it does and to provide a good motivation why existing solutions are so
poor that language support is required.
So far, your explanations haven't made sense to me - and I suspect that
I'm not the only one. I applaud @CyrusNajmabadi
<https://github.com/CyrusNajmabadi> efforts to get you to clearly explain
what it is that you want the compiler to do.
FWIW, I've read through this entire thread multiple times trying to
understand what you're asking for - and I still have no idea at all.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<https://github.com/dotnet/csharplang/issues/2824?email_source=notifications&email_token=ADIEDQOLHXGADTKJWJYTHJLQM4EUBA5CNFSM4I2EZFGKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEALDCQA#issuecomment-538325312>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ADIEDQNI42HK2OMS6EK4SWDQM4EUBANCNFSM4I2EZFGA>
.
|
Beta Was this translation helpful? Give feedback.
-
There's a long discussion on Gitter where the OP explained their idea in a way which I was able to understand. |
Beta Was this translation helpful? Give feedback.
-
My goal is to be able to get the proposal to a point where it could be championed. You may not like that that means asking questions and seeking clarity in the areas I have. But it's vital to this actually being able to go anywhere and not just sit here going nowhere. Cheers! :) |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi I've been appreciative of your efforts in seeking clarifications, and this wasn't meant to be taken as a sweeping statement, only signaling that I'm trying to avoid redundancy in the thread. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
C# has support for anonymous functions. Anonymous functions are useful, in that they avoid explicitly naming functions which are used only once (DRY principle) and may be tied to their call site (closures).
Likewise, an anonymous field provides instance/static storage tied to a specific call site. This is helpful when a function requires storage, which:
To illustrate, let's assume a function
F(implicit ref T foo)
.implicit ref T foo
is used to specify anonymous storage, so that every occurence ofF();
in the definition of a classX
requires aT F_fooN;
field to be implicitly created by the compiler.I have implemented a library which relies on anonymous fields. The strategy used to implement this in C# 7 is hackish (via caller information); however a couple of examples taken from the library (which may be appraised out of context) will clarify how anonymous fields may be used.
I also provide an example from another API, which does not (but could) take advantage of anonymous fields.
Currently, the closest I can think of (except anonymous functions) is the
foreach
loop, where the iterator is hidden from the user. A key difference is thatforeach
allocates function-local variables, not instance/static fields.Use cases
Example 1 : Latch
In the above, please assume that:
latch
of typebool
is used.running
,failing
ordone
).task
is an expression which returnsStatus
.Initially the latch is blocking (
false
). Therefore,task
will not be evaluated. Ifcond
is true, the latch switches state (true
); every subsequent invocation of (1) then proceeds as follows:task
isrunning
, the latch value remains unchanged regardless ofcond
.task
isfailing
ordone
the latch resets to its initial value offalse
.In summary, the
Latch
function implements a latch, which conditionally starts a timely/incremental task. Once the task has completed or failed, the latch resets.In the above example:
Latch
implies storage, as if callingLatch(bool cond, ref bool latch)
Latch()
in the source requires unique storage (call site binding) on a per object basis.Having the API user allocate storage manually is not desirable.
latch
should always initialize to its default value offalse
.latch1
,latch2
, ... is what a user might pick)NOTE: the
Func( LHS ) ? [ RHS ]
pattern as a whole is not relevant here; will detail on request, but I think it would be somewhat of a digression.Example 2: Cooldown
In this case the
Cooldown
function implements a regular cooldown. As above, task is a status expression.Initially the cooldown is passing (
true
). Iftask
returns a value offailing
ordone
, the cooldown then switches to blocking (false
). Subsequently, the cooldown does not evaluatetask
until the specified duration (which may change throughout iterations) has elapsed.Similar to the previous example, site-bound instance storage is needed (here, an object of type
NS.Cooldown
would be needed to store state (passing/blocking) and a time-stamp)Example 3: Scroll-view position
This example is taken from a popular library, which uses a functional API to render user interface elements. Scrollable areas are implemented using the following idiom:
Again we see the same pattern emerging - site bound storage is required to provide the desired functionality. Meanwhile, there is an interesting difference: here it is sometimes meaningful for the user to modify
pos
in order to programmatically change the scroll position. Therefore we have cases where an anonymous, compiler generated field is wanted, and others where the user would prefer an explicitly declared field.Suggested semantics
I propose using the
implicit
keyword to specify anonymous fields; the following signatures would then match the presented use cases:(the type of X is irrelevant)
Disadvantages of current workarounds
As noted, implied storage has been implemented via caller information. For example, here is the current signature of the
Latch
method:CallerLineNumberAttribute
is the closest we get to site-bound implied storage. As noted, this is hackish and error prone (extra steps are taken to avoid collisions but, in fine line numbers to do not uniquify function calls); unfortunately there are more shortages to this:Notes
Note 1
Throughout I have used
implicit ref x
to articulate the fact that the called function is responsible for initializing the value of an anonymous field. It may turn out that every use case requiresref
- if so theref
keyword might be omitted.Note 2
In those cases where the user may wish to rely on implicit storage allocation, or explicitly supply a reference, a separate keyword may be needed.
optional
came to mind but this looks headed towards a confusion with optional parameters. Meanwhile, actual optional parameter semantics may not be a good match for this.For completeness, I note that, in my primary use cases, implicit storage allocation is always optional. This is because the programmer may wish to manually reset or configure the underlying services (latches, cooldowns, and others).
Note 3
Having no handle at all on anonymous fields is not desirable. At a minimum, I am assuming that these fields can still be addressed via reflection.
Beta Was this translation helpful? Give feedback.
All reactions