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

Proposal: Nested local functions and type declarations #259

Closed
gafter opened this issue Feb 5, 2015 · 44 comments
Closed

Proposal: Nested local functions and type declarations #259

gafter opened this issue Feb 5, 2015 · 44 comments
Assignees
Labels
Area-Language Design Feature Request Language-C# New Language Feature - Local Functions Local Functions Resolution-Fixed The bug has been fixed and/or the requested behavior has been implemented

Comments

@gafter
Copy link
Member

gafter commented Feb 5, 2015

Extend the languages to support the declaration of functions and types in block scope. Local functions would be capable of using captured variables from the enclosing scope.

@alanfo
Copy link

alanfo commented Feb 6, 2015

There have certainly been occasions in the past where I've wished that C# supported nested methods which had automatic access to the variables declared in the outer scope without the need to pass them as parameters.

Even in old BASIC programming one could do something similar with the crude but effective GOSUB command.

As we already have anonymous methods and lambda expressions, it doesn't seem much of a conceptual jump to support 'named' nested methods as well so I'd certainly be in favor of that part of the proposal

However, I don't remember ever wishing that I could declare whole types within a method which, unless they were restricted in some way, would add significant extra complexity to the language.

Are there any particular use cases you think such a feature would address?

Incidentally, I know that Java supports something called 'local classes' but, on the rare occasions I've needed to program in that language, I've never found a use for them.

@AlgorithmsAreCool
Copy link

I think the local function problem is pretty well solved by C#'s closures

void Foo()
{
   int myNumber = 42;
   Predicate localFunction = (int num) =>{
       return num > myNumber
   };
...
    myArray.Where(localFunction);
}

The local class problem isn't one i encounter often, but sometimes i need a narrowly scoped class to fulfill an interface contract. Aside from interface shims i can't think of any good use cases for local classes that are not solved by C#'s anonymous classes already.

@alanfo
Copy link

alanfo commented Feb 6, 2015

Named nested methods would have one advantage over anonymous methods or lambda expressions in that you wouldn't need to assign them to a compatible delegate before you could invoke them.

As you know, if there's no suitable predefined delegate type in the .NET framework, you have to come up with your own.

On the other hand, you'd need to specify all parameter types because there would be no way that the compiler could infer them.

Anonymous methods also have some restrictions - you can't use params in the parameter list and you can't capture ref or out parameters of the enclosing method. However, it's possible that these same restrictions would also apply to nested methods if the underlying implementation were similar.

@mirhagk
Copy link

mirhagk commented Feb 6, 2015

you wouldn't need to assign them to a compatible delegate before you could invoke them.

Action and Func cover any delegates you'll need (unless you have more than 16 parameters which would be giant code smell). And the syntax for doing it really isn't much different:

Func<int,int,int> foo = ((a,b) => 
{
    return a+b;
});
int foo(int a, int b)
{
    return a+b;
}

I don't see a benefit here that justifies the complexity.

I do see somewhat of the point of having the local class (and more importantly struct) simply because anonymous classes have readonly fields, but I don't think there are very many algorithms that actually need them. Most of those algorithms would probably be better off with LINQ and immutable types.

@axel-habermaier
Copy link
Contributor

@mirhagk: No, unfortunately they don't. Consider ref, out, pointer, or params parameters. Those are not valid generic arguments and therefore Action and Func can't be used.

@alanfo
Copy link

alanfo commented Feb 6, 2015

Although Action and Func can deal with most things, they can't deal with ref or out parameters as @axel-habermaier just said:

using System;

delegate void MyDelegate(ref int a);

class Program
{
   static int a = 2;

   static void Main()
   {
       MyMethod();
   }

   static void MyMethod()
   {
       MyDelegate d = (ref int b) => ++b;
       d(ref a);
       Console.WriteLine(a); // 3           
   }
}

If you tried to replace MyDelegate with Func<int, int>, then it wouldn't compile.

If nested methods were introduced, I imagine that the above program would look like this if one uses C# 6.0's 'expression bodied' function feature:

using System;

class Program
{
   static int a = 2;

   static void Main()
   {
       MyMethod();
   }

   static void MyMethod()
   {
       void Nested(ref int b) => ++b;
       Nested(ref a);       
       Console.WriteLine(a); // 3           
   }
}

A bit cleaner, perhaps :)

@mirhagk
Copy link

mirhagk commented Feb 6, 2015

Okay that makes sense.

If this is to happen, I'd like to see it in tandem with allowing statements at the top level, ie that class/method definitions are basically treated on the same level as statements. That would allow you to define functions outside of a class, allow for this, and allow statements at the top level (which makes it possible to treat C# as a scripting language, mentioned in #98).

@AlgorithmsAreCool
Copy link

@mirhagk I am very wary of allowing top level statements and classless functions in C#. I can see endless abuse in codebases with little benefit to normal programs. I love C# but i don't think scriptifying it makes sense at all, we have many scripting languages that are better suited to the task.

@mirhagk
Copy link

mirhagk commented Feb 6, 2015

@AlgorithmsAreCool The thing is C# is already used as a scripting language. Scriptcs and LinqPad both offer scripting capabilities, but with some hacks to allow top level statements (linqpad makes you choose expression vs statements vs program and wraps them as appropriate, scriptcs basically allows bare statements).

Classless functions already essentially exist with static classes. The difference between a static class and a namespace with top level functions is basically nothing.

EDIT: Perhaps the C# grammar would support them, but it would be disallowed for most project types. You could only allow it in a "scripting" context.

@mkosieradzki
Copy link
Contributor

On my side I would like to add that the local functions are extremely useful in functional programming as recursive functions. We use this approach very often. Actually the underlying construct is more like:

Func<int, int> Fib = null;
Fib = n => n > 2 ? Fib(n-1) + Fib(n-2) : 1;

To allow recursive calls.

int Fib(int n) => n > 2 ? Fib(n-1) + Fib(n-2) : 1;
or
rec int Fib(int n) => n > 2 ? Fib(n-1) + Fib(n-2) : 1;
looks definitely nicer :).

+1 for this feature - worth implementing even as a simple syntactic sugar.

@HaloFour
Copy link

The one big advantage that local functions would have over delegates is that delegate invocation is much more expensive than a direct method call. Aside the closure capabilities there really isn't a great deal of benefit of that over a regular private static method. In the case of closures would a local function also have the notion of a capture list, like with #117 ?

@gafter
Copy link
Member Author

gafter commented Feb 12, 2015

@HaloFour If we do capture lists like #117 and also local functions #259 we would have to decide if local functions can specify capture lists. That seems messy.

@HaloFour
Copy link

@gafter I don't disagree. Given that local functions will be much more limited in how they can be used as opposed to delegates it's probably not necessary, either. Instead of promoting the variables to fields in a closure state machine they could silently be ref parameters which the compiler would fill in.

public void Foo() {
    int x = 10;
    int IncrementBy(int value) {
        x += value;
        return x;
    }

    int result = IncrementBy(1);
    Debug.Assert(result == x);
}

could be translated into:

private static void Foo_IncrementBy(int value, ref int x) {
    x += value;
    return x;
}

public void Foo() {
    int x = 10;
    int result = Foo_IncrementBy(1, ref x);
    Debug.Assert(result == x);
}

If the local function would reference members of the containing type then this could be passed as another hidden parameter.

Additionally, for perf purposes, I'd like to add that invoking the local function always be done with call instead of callvirt. It would also be nice if the compiler would detect if the local function would return with the result from itself and automatically perform a tail call.

@mkosieradzki
Copy link
Contributor

Capture lists do not sound like a good idea for recursive local functions unless tail calls are aplicable, IMO non-tail-recursion should disable capture lists (if implemented) to avoid wasting stack memory.

@aluanhaddad
Copy link

I think this would be a very useful feature. This kind of coding can improve organization and reduce namespace pollution. Additionally, If these local functions were permitted to be generic, it would open up some nice scenarios that are not covered by the System.Func and System.Action delegate families.

mavasani added a commit that referenced this issue Feb 20, 2015
This change addresses #259: below issues related to diagnostics generated for analyzer exceptions from third party analyzers.

1.Suppression of duplicate exception diagnostics: Current mechanism did the suppression in SuppressMessageState based on unique reported messages. This is obviously incorrect as an exception diagnostic will be reported non-suppressed and suppressed on subsequent queries to SuppressMessageState.IsDiagnosticSuppressed.


2.The IDE diagnostic service has multiple layers where document/project diagnostics are filtered and these analyzer exception diagnostics were getting dropped at various places.


So this change moves the exception diagnostics generation + reporting out of the regular analyzer diagnostic pipeline and in line with analyzer load failure diagnostics reporting in VS:

1.Add an event handler to AnalyzerDriverHelper to report analyzer exception diagnostics to interested clients.


2.Listen to these diagnostic events in IDE diagnostic service and wrap them with relevant workspace/project argument and generate updated events.


3.Add an AbstractHostDiagnosticUpdateSource in Features layer to listen and report analyzer exception diagnostic events from diagnostic service. Additionally, removal of an analyzer reference in workspace will clean up the diagnostics for the analyzers belonging to that analyzer reference.


4.Listen to exception diagnostic events in command line compiler and report as regular diagnostics.


Added typw AbstractHostDiagnosticUpdateSource can be extended in future to report other kind of host diagnostics which are not related to a project/document/analyzer.
@gafter gafter mentioned this issue May 20, 2015
30 tasks
@gafter gafter self-assigned this Jul 24, 2015
@bbarry
Copy link

bbarry commented Nov 14, 2015

public void Foo() 
{
    var Bar() => Baz();
    var Baz() => Bar();
}
  1. That code (if it compiled) creates a method Foo() that returns. Bar() and Baz() are never called. Thus no stack overflow.
  2. The trouble is "What type does the compiler use here for var?"
  3. If Foo() did call Bar(), that code might not be a stack overflow anyway because it is mutual tail recursion.

I think it would be nice if that code warned twice: "Unreachable local method declared." for both Bar() and Baz() (indeed the latest sources do in similar cases that are not recursive according to the tests).

It should also complain about var being ambiguous ("error CS7019: Type of 'Bar()' cannot be inferred since its initializer directly or indirectly refers to the definition." according to tests in the latest sources).


As to knowing whether the defined method is a local function or member method, yes that is admittedly a potential source of confusion for users. It is even worse if local functions are nested inside each other (var doesn't help at all). Here's that same gist, now with no forward references.

@gafter gafter added 4 - In Review A fix for the issue is submitted for review. and removed 1 - Planning labels Nov 20, 2015
@gafter gafter modified the milestone: C# 7 and VB 15 Nov 21, 2015
@gafter gafter added this to the 2.0 milestone Dec 14, 2015
@gulshan
Copy link

gulshan commented Jan 7, 2016

Can we also have Local expression bodied Properties like-

float m;
float c;
var E => m*c*c;
//assign values for m and c later and E will be evaluated to mc^2 when called/used

@paulomorgado
Copy link

@gulshan,

You could use an anonymous delegate or lambda for that purpose:

Func<float> E = () => m*c*c;

That would create a closure over m and c, though.

I think the real question here is: do you have a compelling use case for local properties (regardless of how they are expressed)? What could you do with them that you couldn't do with local functions?

@gafter gafter modified the milestones: 2.0 (RTM), 2.0 (Preview) Mar 7, 2016
@gafter
Copy link
Member Author

gafter commented Mar 7, 2016

We have local functions lined up for C# 7 already done in the future branch, so I'm going to close this as done. If someone wants local type declarations, please open a separate issue for that.

@gafter gafter closed this as completed Mar 7, 2016
@gafter gafter added Resolution-Fixed The bug has been fixed and/or the requested behavior has been implemented and removed 4 - In Review A fix for the issue is submitted for review. Language-VB labels Mar 7, 2016
@gafter gafter modified the milestones: 2.0 (Preview), 2.0 (RTM) Mar 7, 2016
@KarthikElumalai
Copy link

Good one...

@giggio
Copy link

giggio commented Apr 9, 2016

This should be tagged with New Language Feature - Local Functions.

@qwertie
Copy link

qwertie commented Apr 12, 2016

This issue is so weird because there's no actual proposal here, just a "hey, let's do local functions!" - What's the impetus for the proposal? What would the semantics be? What would the advantage(s) be over the alternatives, such as simply inferring that var square = (int x) => x * x has type Func<int, int>? And then bam, @gafter who opened the issue announces it's done.

It's good to notify the community you're going to do a feature, but this isn't enough information for an intelligent discussion.

@alrz
Copy link
Member

alrz commented Apr 12, 2016

Note: This was opened a year ago. And closed a month ago. So it's probably too late for any further discussions. In fact, you're just saying "why didn't anybody tell me?"

@qwertie
Copy link

qwertie commented Apr 12, 2016

In case it will help anyone, I found slightly more information in "local-functions.md". See also #2930

Edit: Aha! found the real proposal at #3911.

@ghost
Copy link

ghost commented Feb 18, 2018

I suggest another syntax to local functions to be unnested dotnet/csharplang#1329

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Language Design Feature Request Language-C# New Language Feature - Local Functions Local Functions Resolution-Fixed The bug has been fixed and/or the requested behavior has been implemented
Projects
None yet
Development

No branches or pull requests