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

Comments

@gafter
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

This comment has been minimized.

Show comment
Hide comment
@alanfo

alanfo 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.

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

This comment has been minimized.

Show comment
Hide comment
@AlgorithmsAreCool

AlgorithmsAreCool Feb 6, 2015

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.

AlgorithmsAreCool commented Feb 6, 2015

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

This comment has been minimized.

Show comment
Hide comment
@alanfo

alanfo 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.

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

This comment has been minimized.

Show comment
Hide comment
@mirhagk

mirhagk 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.

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

This comment has been minimized.

Show comment
Hide comment
@axel-habermaier

axel-habermaier Feb 6, 2015

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.

Contributor

axel-habermaier commented Feb 6, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@alanfo

alanfo 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 :)

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

This comment has been minimized.

Show comment
Hide comment
@mirhagk

mirhagk 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).

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

This comment has been minimized.

Show comment
Hide comment
@AlgorithmsAreCool

AlgorithmsAreCool Feb 6, 2015

@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.

AlgorithmsAreCool commented Feb 6, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@mirhagk

mirhagk 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.

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

This comment has been minimized.

Show comment
Hide comment
@mkosieradzki

mkosieradzki Feb 12, 2015

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.

Contributor

mkosieradzki commented Feb 12, 2015

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

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Feb 12, 2015

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 ?

HaloFour commented Feb 12, 2015

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

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Feb 12, 2015

Member

@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.

Member

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

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Feb 12, 2015

@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.

HaloFour commented Feb 12, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@mkosieradzki

mkosieradzki Feb 13, 2015

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.

Contributor

mkosieradzki commented Feb 13, 2015

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

This comment has been minimized.

Show comment
Hide comment
@aluanhaddad

aluanhaddad Feb 19, 2015

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.

aluanhaddad commented Feb 19, 2015

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

Merge pull request #673 from mavasani/AnalyzerSpecificDiagnostics
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 referenced this issue May 20, 2015

Closed

Discussion: Local Functions #2930

19 of 30 tasks complete

@gafter gafter self-assigned this Jul 24, 2015

@jmagaram

This comment has been minimized.

Show comment
Hide comment
@jmagaram

jmagaram Oct 24, 2015

I had a use for this recently. I was writing a function to do some kind of recursive processing on a tree. That recursive function was only necessary inside that method, so I did not want to make a private method on the class and "pollute the namespace." I started doing something like...

Action<TreeNode> processTreeNode = (TreeNode t) => { do stuff and also processTreeNode }

...but the compiler objected to the recursive reference. I had to first insert a...

Action<TreeNode> processTreeNode = null;

This seemed a bit ugly. And then I read blog post somewhere about the dangers of recursive delegates and figured it was safer to just make a private method in the class.

jmagaram commented Oct 24, 2015

I had a use for this recently. I was writing a function to do some kind of recursive processing on a tree. That recursive function was only necessary inside that method, so I did not want to make a private method on the class and "pollute the namespace." I started doing something like...

Action<TreeNode> processTreeNode = (TreeNode t) => { do stuff and also processTreeNode }

...but the compiler objected to the recursive reference. I had to first insert a...

Action<TreeNode> processTreeNode = null;

This seemed a bit ugly. And then I read blog post somewhere about the dangers of recursive delegates and figured it was safer to just make a private method in the class.

@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Oct 24, 2015

Contributor

Another thing to be aware of: nested local functions inside a constructor should be able to set readonly fields.

I ran into this when assigning to a discriminator field. There was a long piece of logic that I extracted from the constructor into a private method, but this of course failed.

class Foo
{
    private readonly FooType type;
    private Bar[] data;

    Foo(Bar[] data) {
        this.data = data;
        AssignType();
    }

    void AssignType()
    {
         //use this.data to find out what the type is
    }
}

Of course, there's a workaround of turning AssignType a pure static function DetermineType and calling it this.type = DetermineType(data);, but this feels less OOPish and more functional.

Contributor

orthoxerox commented Oct 24, 2015

Another thing to be aware of: nested local functions inside a constructor should be able to set readonly fields.

I ran into this when assigning to a discriminator field. There was a long piece of logic that I extracted from the constructor into a private method, but this of course failed.

class Foo
{
    private readonly FooType type;
    private Bar[] data;

    Foo(Bar[] data) {
        this.data = data;
        AssignType();
    }

    void AssignType()
    {
         //use this.data to find out what the type is
    }
}

Of course, there's a workaround of turning AssignType a pure static function DetermineType and calling it this.type = DetermineType(data);, but this feels less OOPish and more functional.

@vladd

This comment has been minimized.

Show comment
Hide comment
@vladd

vladd Oct 24, 2015

@orthoxerox Not sure about your last proposal. Should the constructor be able to store the function which changes a readonly field in a delegate and allow calling it afterwards?

vladd commented Oct 24, 2015

@orthoxerox Not sure about your last proposal. Should the constructor be able to store the function which changes a readonly field in a delegate and allow calling it afterwards?

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Oct 24, 2015

@orthoxerox The only way the compiler could do that is if it could emit the IL so that the local function was flattened into the constructor. The CLR does not permit readonly fields to be set anywhere outside of the local constructor, doing so produces an unverifiable assembly.

HaloFour commented Oct 24, 2015

@orthoxerox The only way the compiler could do that is if it could emit the IL so that the local function was flattened into the constructor. The CLR does not permit readonly fields to be set anywhere outside of the local constructor, doing so produces an unverifiable assembly.

@AdamSpeight2008

This comment has been minimized.

Show comment
Hide comment
@AdamSpeight2008

AdamSpeight2008 Nov 13, 2015

Contributor

If the outer function is an iterator and the inner one isn't, would the inner one be able yield values?

Iterator Function Example() As IEnumerable(Of Integer)
  Function InnerFoo()
     Yield 1
  End Function

  Yield 0
  InnerFoo()
End Function
Contributor

AdamSpeight2008 commented Nov 13, 2015

If the outer function is an iterator and the inner one isn't, would the inner one be able yield values?

Iterator Function Example() As IEnumerable(Of Integer)
  Function InnerFoo()
     Yield 1
  End Function

  Yield 0
  InnerFoo()
End Function
@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Nov 13, 2015

Member

The inner method cannot interact in any control-flow way with the enclosing method. It cannot return from it, goto any of its labels, break from its loops, yield from its, etc. In this way nested local (named) functions to not differ from anonymous functions.

Member

gafter commented Nov 13, 2015

The inner method cannot interact in any control-flow way with the enclosing method. It cannot return from it, goto any of its labels, break from its loops, yield from its, etc. In this way nested local (named) functions to not differ from anonymous functions.

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Nov 13, 2015

Member
        Dim Fib1, Fib2 As Func(Of Integer, Integer)
        Fib1 = Function(arg As Integer) As Integer
' Warning BC42104: Variable 'Fib2' is used before it has been assigned a value. A null reference exception could result at runtime.
                   Return If(arg <= 1, 1, Fib2(arg - 1) + Fib2(arg - 2))
               End Function
        Fib2 = Function(arg As Integer) As Integer
                   Return If(arg <= 1, 1, Fib1(arg - 1) + Fib1(arg - 2))
               End Function
Member

gafter commented Nov 13, 2015

        Dim Fib1, Fib2 As Func(Of Integer, Integer)
        Fib1 = Function(arg As Integer) As Integer
' Warning BC42104: Variable 'Fib2' is used before it has been assigned a value. A null reference exception could result at runtime.
                   Return If(arg <= 1, 1, Fib2(arg - 1) + Fib2(arg - 2))
               End Function
        Fib2 = Function(arg As Integer) As Integer
                   Return If(arg <= 1, 1, Fib1(arg - 1) + Fib1(arg - 2))
               End Function
@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Nov 13, 2015

@gafter

Well you could always do the following, but ew: 😉

Dim Fib2 As Func(Of Integer, Integer) = Nothing
Dim Fib1 = Function(arg As Integer) As Integer
                Return If(arg <= 1, 1, Fib2(arg - 1) + Fib2(arg - 2))
            End Function
Fib2 = Function(arg As Integer) As Integer
            Return If(arg <= 1, 1, Fib1(arg - 1) + Fib1(arg - 2))
        End Function

You mentioned the memory allocations but not the delegate invocation. That should improve performance considerably, no?

HaloFour commented Nov 13, 2015

@gafter

Well you could always do the following, but ew: 😉

Dim Fib2 As Func(Of Integer, Integer) = Nothing
Dim Fib1 = Function(arg As Integer) As Integer
                Return If(arg <= 1, 1, Fib2(arg - 1) + Fib2(arg - 2))
            End Function
Fib2 = Function(arg As Integer) As Integer
            Return If(arg <= 1, 1, Fib1(arg - 1) + Fib1(arg - 2))
        End Function

You mentioned the memory allocations but not the delegate invocation. That should improve performance considerably, no?

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Nov 13, 2015

Member

Yes, the direct method invocation is indeed faster than a delegate invocation.

Member

gafter commented Nov 13, 2015

Yes, the direct method invocation is indeed faster than a delegate invocation.

@AdamSpeight2008

This comment has been minimized.

Show comment
Hide comment
@AdamSpeight2008

AdamSpeight2008 Nov 13, 2015

Contributor

I was think more of

Dim Fib  As Func(Of Integer, Integer) = Function(arg As Integer) As Integer
                Return If(arg <= 1, 1, Fib(arg - 1) + Fib(arg - 2))
            End Function
Contributor

AdamSpeight2008 commented Nov 13, 2015

I was think more of

Dim Fib  As Func(Of Integer, Integer) = Function(arg As Integer) As Integer
                Return If(arg <= 1, 1, Fib(arg - 1) + Fib(arg - 2))
            End Function
@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Nov 13, 2015

@AdamSpeight2008 That is more of a benefit for C# where that would result in a compile-time error unless you predefine the delegate variable:

// error: use of unassigned variable fib
Func<int, int> fib = i => (i <= 1 ? 1 : fib(i - 1) + fib(i - 2));

// legal
Func<int, int> fib = null;
fib = i => (i <= 1 ? 1 : fib(i - 1) + fib(i - 2));

The lack of a delegate and the performance overhead that brings to the equation is difference enough for me.

HaloFour commented Nov 13, 2015

@AdamSpeight2008 That is more of a benefit for C# where that would result in a compile-time error unless you predefine the delegate variable:

// error: use of unassigned variable fib
Func<int, int> fib = i => (i <= 1 ? 1 : fib(i - 1) + fib(i - 2));

// legal
Func<int, int> fib = null;
fib = i => (i <= 1 ? 1 : fib(i - 1) + fib(i - 2));

The lack of a delegate and the performance overhead that brings to the equation is difference enough for me.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Nov 13, 2015

Contributor

consistent with methods

to be consistent also with local declarations I'd say var be allowed as return type

let let = e1;
var var = e2;
var f() => e3;

I don't know how "forward reference" would be more useful.

Contributor

alrz commented Nov 13, 2015

consistent with methods

to be consistent also with local declarations I'd say var be allowed as return type

let let = e1;
var var = e2;
var f() => e3;

I don't know how "forward reference" would be more useful.

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Nov 13, 2015

Member

to be consistent also with local declarations I'd say var be allowed as return type

... and restricted to cases where the body does not use itself (recursively) or any later-declared function? And cannot be used before it is declared? Which means mutual recursion would not be supported?

Member

gafter commented Nov 13, 2015

to be consistent also with local declarations I'd say var be allowed as return type

... and restricted to cases where the body does not use itself (recursively) or any later-declared function? And cannot be used before it is declared? Which means mutual recursion would not be supported?

@bbarry

This comment has been minimized.

Show comment
Hide comment
@bbarry

bbarry Nov 14, 2015

@alrz here's a bad example, but this is part of the trouble with having both forward reference local functions and var return type local functions:

public void Foo() 
{
    var Bar() => Baz();
    var Baz() => Bar();
}

As to why forward reference is good, consider this (vaguely copied from a js implementation I have laying around; probably doesn't compile on @gafter's branch or work correctly) implementation of joint binary search via mutual tail recursion. Supposing that method was private (you shouldn't be exposing SortedList<T, V> publicly anyway) on a partial class, you may not want to expose those helpers to even other members of that class. So today you would be stuck with shoving it off as private members of some implementation class. Unfortunately that is one more thing to name (back to the hardest problem for a developer...).

Maybe a restricted allowance of var on some subset of all possible local functions could be desirable. "Forward reference" is almost definitely desirable. Can we decide today what those restrictions are without knowing exactly how forward reference will behave (it is probably at least what @gafter just said)? Is that still a worthwhile subset? Would coming up with a specification today on how you could use var functions limit the specification of how forward reference could be done?

I would much rather see a working solution that provides forward reference over one that allows var in some spot that prevents forward reference from happening somehow.

bbarry commented Nov 14, 2015

@alrz here's a bad example, but this is part of the trouble with having both forward reference local functions and var return type local functions:

public void Foo() 
{
    var Bar() => Baz();
    var Baz() => Bar();
}

As to why forward reference is good, consider this (vaguely copied from a js implementation I have laying around; probably doesn't compile on @gafter's branch or work correctly) implementation of joint binary search via mutual tail recursion. Supposing that method was private (you shouldn't be exposing SortedList<T, V> publicly anyway) on a partial class, you may not want to expose those helpers to even other members of that class. So today you would be stuck with shoving it off as private members of some implementation class. Unfortunately that is one more thing to name (back to the hardest problem for a developer...).

Maybe a restricted allowance of var on some subset of all possible local functions could be desirable. "Forward reference" is almost definitely desirable. Can we decide today what those restrictions are without knowing exactly how forward reference will behave (it is probably at least what @gafter just said)? Is that still a worthwhile subset? Would coming up with a specification today on how you could use var functions limit the specification of how forward reference could be done?

I would much rather see a working solution that provides forward reference over one that allows var in some spot that prevents forward reference from happening somehow.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Nov 14, 2015

Contributor

@gafter

... and restricted to cases where the body does not use itself (recursively) or any later-declared function? And cannot be used before it is declared? Which means mutual recursion would not be supported?

Why should it be restricted? In F# return types can be inferred, although, it only permits recursion with a specific keyword rec and and for mutual recursion. But uses let for both variable and function declarations. Here is my question, don't you think that local functions should be distinguishable from member methods?

@bbarry

public void Foo() 
{
    var Bar() => Baz();
    var Baz() => Bar();
}

Are you trying to stack overflow? Yeah that is one baaad example. otherwise I don't see where the "trouble" is.

I just realized in a code like the gist you've mentioned, it's hard to know that the defined method is a local function or member method! var might help.

Contributor

alrz commented Nov 14, 2015

@gafter

... and restricted to cases where the body does not use itself (recursively) or any later-declared function? And cannot be used before it is declared? Which means mutual recursion would not be supported?

Why should it be restricted? In F# return types can be inferred, although, it only permits recursion with a specific keyword rec and and for mutual recursion. But uses let for both variable and function declarations. Here is my question, don't you think that local functions should be distinguishable from member methods?

@bbarry

public void Foo() 
{
    var Bar() => Baz();
    var Baz() => Bar();
}

Are you trying to stack overflow? Yeah that is one baaad example. otherwise I don't see where the "trouble" is.

I just realized in a code like the gist you've mentioned, it's hard to know that the defined method is a local function or member method! var might help.

@bbarry

This comment has been minimized.

Show comment
Hide comment
@bbarry

bbarry 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.

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 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

This comment has been minimized.

Show comment
Hide comment
@gulshan

gulshan 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

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

This comment has been minimized.

Show comment
Hide comment
@paulomorgado

paulomorgado Jan 7, 2016

@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?

paulomorgado commented Jan 7, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Mar 7, 2016

Member

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.

Member

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 Mar 7, 2016

@gafter gafter modified the milestones: 2.0 (Preview), 2.0 (RTM) Mar 7, 2016

@KarthikElumalai

This comment has been minimized.

Show comment
Hide comment
@KarthikElumalai

KarthikElumalai commented Apr 7, 2016

Good one...

@giggio

This comment has been minimized.

Show comment
Hide comment
@giggio

giggio Apr 9, 2016

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

giggio commented Apr 9, 2016

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

@qwertie

This comment has been minimized.

Show comment
Hide comment
@qwertie

qwertie 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.

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

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Apr 12, 2016

Contributor

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?"

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@qwertie

qwertie 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.

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.

@MohammadHamdyGhanem

This comment has been minimized.

Show comment
Hide comment
@MohammadHamdyGhanem

MohammadHamdyGhanem Feb 18, 2018

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

MohammadHamdyGhanem 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