Partager via


Standard Generic Delegate Types

Hey all, I'm back from my vacation. Two weeks of reading, sailing, kayaking and visiting with old friends has left me a lot more relaxed and sunburnt than when I left. I could use another week, but it's also good to be back.

We're introducing a lot of new features in C# 3.0 which, when combined to form LINQ are really interesting and powerful, but, like the component parts of Voltron, are pretty interesting and powerful just on their own. Lambda expressions, for example, are not just useful for making query comprehensions work. They make functional-style programming in C# 3.0 much more elegant than the somewhat clunky anonymous method syntax from C# 2.0.

We're also introducing a new standard generic delegate type in the LINQ libraries to make delegate declaration easier. In the old days, to create a function that takes an int and returns a function from int to int, you'd have to do something like this:

delegate int D1(int y);
delegate D1 D2(int x);
//...
D2 makeAdder = delegate(int x){
return delegate(int y){
return x + y;
};
};
D1 addTen = makeAdder(10);
Console.WriteLine(addTen(20));

In the new world we'll have these definitions in a standard library:

delegate R Func<R>();
delegate R Func<A1, R>(A1 a1);
delegate R Func<A1, A2, R>(A1 a1, A2 a2);
delegate R Func<A1, A2, A3, R>(A1 a1, A2 a2, A3 a3);
//...etc, up to some reasonable number of arguments

so that you can use them plus lambdas to make the code above far more concise:

Func<int, Func<int, int>> makeAdder = x=>y=>x+y;
Func<int, int> addTen = makeAdder(10);
Console.WriteLine(addTen(20));

Here's an interesting fact: there are delegate types which can be defined using the old-fashioned syntax but cannot be defined using the newfangled generic syntax. Void delegates, generic delegates and delegates with out or ref parameters are obvious examples. Can you think of any other examples? Next time on FAIC, I'll post an interesting one.

Comments

  • Anonymous
    June 21, 2006
    .. and somehow VoidFunc<> just sounds like some trendy techno band ;)

  • Anonymous
    June 21, 2006
    I think this Func<> delegates should be somheow extended to allow currying or some other functional features. F#, for example, has a powerful set of delegates allowing these operations (I think they are called FastFunc<>). So, why not enlarge the Base Class Library to allow real interoperation for any language.
    In my point of view, the .NET Framework is now getting more tied to C# and VB advancements, whereas some other extensions could behave well on other languages. C#, on the other hand, is becoming a starnge mix of functional and OOP features (why support lambda expressions and now allow an easy way to take functions as parameters). Some other languages that target .NET, such as Boo and Nemerle, are making it easier.

  • Anonymous
    June 21, 2006
    I think you cannot declare the "I don't care for my parameters" delegates with the new mechanism. Example:

    string [] arr = ...;
    int Count = 0;
    Array.ForEach<string>(arr,delegate
    {
      Count++;
    });

    Normally you would declate it this way:
    Array.ForEach<string>(arr,delegate(string str)
    {
      Count++;
    });


    By the way I did make some comparison how C# and F# do perform in functional programming. F# does win of course but you can get already quite functional with generators and currying in .NET 2.0

    http://geekswithblogs.net/akraus1/articles/79880.aspx

    Yours,
     Alois Kraus

  • Anonymous
    June 21, 2006
    Delegates do allow a form of currying in .NET 2.0, but it is pretty gross.  Basically it is a mechanism introduced for the convenience of compiler writers writing compilers for functional languages, not for C# programmers.

    For example, suppose you've got public class C { public static int M(T t, int x) { whatever } } and you want to curry M with a T.  You can say:

       Func<int, int> d = Delegate.CreateDelegate( typeof(Func<int, int>), t,
         typeof(C).GetMethod("M", BindingFlags.Static | BindingFlags.Public));

    and you'll get a delegate that takes an int and calls M(t, x).

    There's no mechanism that I know of for taking an existing delegate object and currying that.  I agree that this would be pretty cool.

    However, we don't add features because they're cool, or because they enable functional programming, or any other such highfalutin concerns.  We add features because they enable our customers to get work done.  We're not adding lambdas because we're a bunch of functional language wonks who love lambdas -- I mean, we are that, but that's not why we're adding lambdas.  We're adding lambdas because lambdas will help massively with query comprehensions, and we're adding query comprehensions because our research shows that pro devs could really use a language-integrated approach to querying disparate datasets without losing the benefits of static typing.  If some day there's a highly pragmantic language feature that needs currying, that's the day we'll figure out how to do elegant currying in C#.

    I'm not sure I quite undersand your point about the framework being tied to language advancements.  By the "framework" do you mean the common language runtime, or the base class library?  I agree that we are making significant additions to the base class library in order to make LINQ practical for real-world query problems.  But C# 3.0 features are driving exactly zero enhancements into the CLR itself.  If somehow you could avoid using any of the LINQ libraries that will ship with the next version of the BCL, then there's no reason I know of why you couldn't run the output of the C# 3.0 compiler in the 2.0 .NET CLR.

  • Anonymous
    June 21, 2006
    Alois is correct; lambdas do not support the "I am assignable to any delegate type which has no out parameters" form of anonymous methods.

    However, that's not what I'm getting at.  I'm asking whether there is a delegate type declaration which can be written in the classic form:

    delegate R D(A a);
    ...
    D x = something;

    which cannot be written using the generic form

    delegate R Func<A, R>(A a);
    ...
    Func<A, R> x = something;

    What can you choose for A or R that makes this possible in the first form but not in the second?

  • Anonymous
    June 21, 2006
    A couple of questions/remarks

    1) Any chance you'll also define standard Method delegates for void functions:

    delegate void Method();
    delegate void Method<TArg1>(TArg1 arg1);
    ...

    2) As for currying, if there's nothing builtin in the framework, it's dead easy to write the following class yourself (although a bit tedious, because of all the overloads...)

    public static class Curry
    {
       public static Function<TReturn> Function<TReturn, TArg1> (Function<TReturn, TArg1> f, TArg1 arg1)
       {
           return delegate { f(arg1); };
       }

       ...
    }

    3) Finally, is there any chance there could be structural equivalence for delegates.  It would allow us to use the new generic standard delegates in old api's as well.  What I mean is, could the following ever be legal:

    delegate void MyMethod(int i);

    private void DoSomething(MyMethod m)
    {
       m(10);
    }

    private void PrintInt(int n);
    {
       Console.Writeline(n.ToString());
    }

    public void Example()
    {
       Method<int> m = new Method<int>(PrintInt);
       DoSomething(m);  // Although DoSomething expects a MyMethod, we can give it a Method<int>
    }

  • Anonymous
    June 22, 2006
    Excellent questions Geert.  

    Before I answer them, let me give the caveat that none of this has been decided and written in stone yet.  Things are still in a state of flux here.

    1) Probably, yes.  And they'll probably be called "Action", which I'm not thrilled with, but it does emphasize that we've got a side effect going on here.

    2) As a functional-style-loving wonk, I'd love to see currying operators built into the framework like this.  But as you note, they're pretty easy to roll yourself, so odds are good we probably won't be adding them.

    3) We have no plans to have structural equivalence for delegates.  One of the points of standardizing on Func<A, R> is so that there is one clear choice for interoperable delegate typing.  If everyone uses Func<A,R> as their type then you don't need structural equivalence because you already have type equivalence -- that's how we want to solve this issue.

    Thanks for your feedback!

  • Anonymous
    June 22, 2006
    Eric,
    This is very important, yes.
    I often want to use those in cases where it is just a chore to create my own delegate.
    You can see how I solved it at the moment here:

    http://www.ayende.com/Blog/2005/10/29/StaticReflection.aspx

    I got:

    R Func<R>();
    R Func<R, A1>(A1 a);

    void Proc();
    void Proc<A1> (A1 a);

    it wroks very nicely.

  • Anonymous
    June 22, 2006
    Hm,

    I could think of
          delegate R  D(params A1 [] a);
          delegate R  Func<A1 [],R>(A1 [] a);
    since you would introduce ambiguities if you let params into the game. Your questions are always quite tricky ;-)

    Yours,
      Alois Kraus


  • Anonymous
    June 22, 2006
    Good one!  

    Also, delegate declarations where the formal parameter list contains a parameter with an attribute cannot be declared using the generic syntax.

    Any others you can come up with?

    Big hint:  for any "old fashioned" delegate declaration, there is a type symbol which is valid as an argument or return type in that declaration, but which actually has no equivalent at all in the generic form.

  • Anonymous
    June 22, 2006
    Another thing could be unsafe code and the * symbol. I do not think that I am able to do this with generics. This is getting really dirty now but it is legal C# code.

           unsafe int func(int a)
           {
               return (int) a;
           }

           unsafe delegate int D(int
    a);
           Program()
           {
               unsafe
               {
                   D x = func;
               }

    Ahh the good old C++ times shine through here ;-).

    Yours,

     Alois Kraus

  • Anonymous
    June 22, 2006
    You're finding all kinds of great corner cases here.  I am definitely re-reading this post before I write the check-in tests for lambda type inference!

    :-)

  • Anonymous
    June 22, 2006
    Hi Eric,

    I think I know what type symbol you had in mind:

    int func(Type a) // This will become interesting with generics
    {
         return 0;
    }

    delegate int D(Type a); // perfectly legal
    delegate int Func<Type,int>(Type a); // This is definetely not possible with generics

    This was too easy to think first of. ;-)

    Yours,
     Alois Kraus

  • Anonymous
    June 23, 2006
    If you're not going to do structural equivalence for delegates, any chance of at least putting implicit conversion operators bidirectionally between Func<T, bool> and Predicate<T>?

    Another interesting approach to this would be to provide a general way to "cast" or convert between two structurally equivalent delegate types. Currently the closest I can figure is:

    Func<T, bool> f = whatever;
    Predicate<T> p = x => f(x);

    but that gets clumsy when there are a lot of parameters and it seems like it would have more overhead, creating a method to perform the identity function rather than just using the existing method which is known to fit the delegate type.

    Thoughts?
    Stuart.

  • Anonymous
    June 23, 2006

    Last time I asked whether there were examples of delegate types which could be declared with the traditional...

  • Anonymous
    November 13, 2006
    I'd prefer to reorder type arguments like this: delegate R Func<R, A1>(A1 a1); delegate R Func<R, A1, A2>(A1 a1, A2 a2); delegate R Func<R, A1, A2, A3>(A1 a1, A2 a2, A3 a3); It seems more natural.

  • Anonymous
    November 13, 2006
    It woild be nice to have System.Action with more type arguments in BCL delegate void Action< A1>(A1 a1); delegate void Action< A1, A2>(A1 a1, A2 a2); delegate void Action< A1, A2, A3>(A1 a1, A2 a2, A3 a3); like Func family.

  • Anonymous
    November 13, 2006
    Putting the return type first makes it harder to read.  I read "Func<int, double, string>" as "a function from int and double to string". Also, it certainly does NOT feel more natural to VB programmers, who are used to the return type coming at the end of a function declaration.  

  • Anonymous
    February 25, 2007
    I often want a generic type where the parameter is a value or an object rather than a type: for example I want a class of integers modulo p for some prime number p. The simplest approach is to construct a class IntegerModulo with p as an instance variable along with the particular value k (so the constructor IntegerModulo(k,p) constructs an object representing k modulo p). There are two problems with this: first I may have a lot of IntegerModulo objects with the same p, which is a bit wasteful; and second and more importantly I cannot catch attempts to add two IntegerModulo objects with different values of p at compile time. What I would like is to be able to define a generic type, IntegerModulo<7> for example, but this is not easy to do. I could construct a class to represent the value 7 using reflection.emit, but this is very cumbersome. Do you have any suggestions?

  • Anonymous
    September 06, 2007
    Last time I mentioned that one of the subtleties of programming language design is weighing the benefit

  • Anonymous
    September 06, 2007
    Last time I asked whether there were examples of delegate types which could be declared with the traditional