Logo




Subscribe:
RSS 2.0 | Atom 1.0
Categories:

Sign In


[Giagnocavo]Michael::Write()

# Thursday, August 28, 2008
Why are there still delegates?
Can someone explain to me the point of having delegate types in C#/.NET? They made sense in the non-generic .NET 1 world, but with generics they should have no reason to exist (except maybe purely as a type alias). All the delegates in common use are representable with a generic definition (think Action/Func<...>). But for some reason, you cannot automatically use an equivalent delegate in C#.

This manifests a bit more when you consider a local such as:

var inc = (int x) => i + 1;

As Expression<T> syntax takes the same form, the compiler cannot tell if it's an expression or a delegate. Additionally, since Func<int,int> is "just as good" as any other generic type, even if the compiler wanted to create a delegate, it wouldn't know to use Func<int,int>. Thus, you must specify it, and inner functions are too clunky to be used in C#.

The answer I got from Microsoft (not an official answer, just a response to a Connect item) was that C# might allow auto-conversion in version 4, but also that delegates aren't totally useless because recursive delegate types can't be declared in that fashion. But for the relatively few uses (fixed-point combinators?) of such types, built-in, specific support would be a small sacrifice, wouldn't it?

This is just something I've been wondering about.

Update: Thanks for Jonathan Pryor for pointing out in the comments what my limited functional mind missed: ref/out parameters and of course, and high-arity delegates. While these should be easy to work around, they are accurate technical reasons. Thanks.

Code
Thursday, August 28, 2008 12:18:44 AM UTC  #    Comments [11]  |  Trackback

Thursday, August 28, 2008 1:45:11 AM UTC
There are two other scenarios where "generic" delegate support is required:

1. `ref`/`out` parameters, as generics doesn't support the use of `ref` or `out` within type parameters. Action<ref int> isn't valid.

2. The lack of variadic generic parameters. Action and Func are overloaded to support up to 4 parameters. If you need 5, you're out of luck, and need to declare your own delegate type. (A case could be made that if you need more than 4 you need a redesign, but let's not go there....)

I would love variadic generic parameters -- it would allow a sensible Tuple type -- but I doubt that (1) will ever be supported, so custom delegate types are unlikely to go away. They'll be less used, but still necessary.

However, that doesn't prevent the ability for e.g. Action/Func to be implicitly convertable to any other generic type within assignment context, which would allow for `var v = ((int x) => x*x)(5);` to be valid meaningful code. Such support would also (gratefully) obsolete the need for Mono.Rocks.Lambda.Func(), the sole purpose of which is to return a Func for a lambda expression, thus permitting `var v = Lambda.Func((int x) => x*x)(5)`.

One other issue worth mentioning is expression trees (`Expression<Func<int,int>> f = x => x*x;`), which use the same lambda syntax (by design, and used in LINQ-to-SQL).

So the overriding "problem" is that a given lambda expression (`x => x*x`) has a meaning which is context-dependant (Action vs. Func vs. another delegate type vs. expression tree of previous), and thus doesn't easily lend itself to "assume a Func is requested if the context doesn't otherwise control the behavior," as a large point to C# is to remove such magical assumptions from the language. This isn't Boo, you know. :-)
Thursday, August 28, 2008 1:56:20 AM UTC
Jonathan,

Right again you are. I keep dreaming in a nicer world. Out should be done via tuples, and ref can be handled via a reference types (i.e., Reference<T>). As far as variadic generics, yes, I'd assume there would be predefined types and that the compiler could generate necessary types when need be. In other words, have a first class function type.

The Expression<T> stuff is annoying because it does overshadow the entire problem. As explicit as C# is, I'm not sure why there's no denotation for expression versus code. Won't this become an even larger problem if Expression<T> syntax starts allowing multi-statements?

Thursday, August 28, 2008 3:10:14 AM UTC
I realize you want a fully functional language, but C# isn't, and likely never will be. It's trying (likely it's best) to combine multiple different paradigms, but it can't fulfill all of them perfectly.

For example, using Tuples for `out` and Reference<T> for `ref` _sounds_ good, but misses an important feature of `out` and `ref`: execution performance. I don't care how good your JIT is, I don't see any way for the semantics of a tuple (likely GC object creation unless you want value-type Tuples) to ever match the runtime performance of `out`, and similar for Reference<T> vs. `ref`. The GC is fast, but it's not _that_ fast, not compared to by-ref parameter passing. I don't see how it ever could be, either.

(Perhaps if we had a vastly more intelligent JIT that could take the IL of Reference<T> and Tuples and convert it into pass-by-reference when possible, but I can't see that happening anytime soon, and such optimizations may be ~impossible if your Tuple has a finalizer, or if your Tuple is not sealed and a subclass adds a finalizer, etc.)

Now that I've killed Tuples and Reference<T>, we hit "predefined types [...] that the compiler could generate." This is also an impossibility, and is one of the issues with anonymous types.

C#, as currently designed, is statically typed, which means all types must be known at compile time. OK, fine, so what's wrong with cmpiler-generated types? They're certainly known at compile time. The problem is that this goes counter to another C# design goal: versioning. Once the compiler is generating types for you, you no longer control those types, and thus can't do such "essential" things as modify them without breaking backward compatibility, provide a GUID for interop scenarios, or even something as fundamental as keep the same name. (Look at the IL for anonymous types; the IL gmcs generates has such "wonderful" names as "<>__AnonType0`2", which can't actually be typed in any language I know of, and if you e.g. introduce a new anonymous type usage before an existing usage, say by adding a file to your solution, you suddenly have a different type after you recompile. So much for a consistently versioned ABI!)

This is why C# iterators use non-compiler generated types like IEnumerable<T> as the return value: the type that the compiler generates will have any name, and a name that may change at any time, and thus *cannot* be used in a public API.

Action/Func/Tuple would have the same restrictions. You either have true variadic generics at the IL level, and have all languages support it, or you go with the current approach and have only a predefined number included with the runtime, and beyond that number you're out of luck.

(For example, "true variadic generics" would, to paraphrase C++-0x syntax, be `class Tuple<T...> { public Tuple (T... values) {/*...*/} }`, and I have _no_ idea how you would would introduce e.g. properties on a per-type basis, e.g. to introduce _1, _2, etc. properties for each type. C++-0x tuples "bypass" this issue by using a template function to "index" the tuple, e.g. `auto v = make_tuple (1, 2); int a = get<1>(v);`.)

So I don't think variadic generics are ever likely to happen. Macros are more probable, given that you'd likely need macros in order to generate properties in the desired fashion, and we've seen their stance toward macros. Plus, variadic generics would be terribly complicated, and I can't see them ever being added to the CLS because of this complexity, which means a "standard" Tuple type would be ~useless.

Which brings me to another comment of yours: "have a first class function type." Delegates *are* the first-class function type. The issue is that a method is implicitly convertible to potentially more than one delegate type. Consider object.Finalize(), which would implicitly convertable to System.Action, System.CrossAppDomainDelegate, and System.Threading.ThreadStart.

So delegates are first-class (as they're types), but by the curious C# design methods themselves are pseudo first-class. They're implicitly convertible to a delegate, so it LOOKS LIKE you can pass them to methods/events/etc. by name, but the compiler needs to know _which_ delegate type to convert to. For example, if Console.WriteLine(int) *were* an Action<int>, then you could call Mono.Rocks.CurryRocks.Curry() on it, e.g. Console.WriteLine.Curry(4) (to return an Action that, when invoked, would invoke Console.WriteLine(4)). However, since methods aren't by themselves first-class, but just implicitly convertible to something that is first class (making it 1.5 class?), this fails, and you instead need a helper to tell the compiler which delegate type to use, e.g. Lambda.Func(Console.WriteLine).Curry(5), which still fails (which WriteLine overload to use? plus issues with type inferencing in the C# 3 standard which will hopefully be fixedin C# 4), eventually resulting in a messy ((Action<int>) Console.WriteLine).Curry(5).

And after all that, we finally hit Expression<T>. There *is* a denotation for code vs. data; unfortunately, it's on the lhs of the = (the fact that you're assigning to an Expressoin<T>), which is why lambda expressions have no type in and of themselves, they need to be of a type which is implicitly convertible to *both* code (delegates) AND data (Expression<T>). I usually consider this to be a good thing as it increases abstraction: you don't need to worry about whether the method you're calling takes a Func<T,T> or an Expression<Func<T,T>>, as the code *you* write is identical either way; you don't need to care (as long as said code is provided within the call site, e.g. foo.Select(x => x*2)).

I don't see how Expression<T> allowing multiple statements changes this at all (and all signs point to Expression<T> permitting statements in .NET 4, as part of the DLR effort).

To take a giant side step, delegates vs. Expression<T> in C# is rather like quoting in Lisp: you can't tell, in the middle of an expression, if you're code or data. You need the larger context. Consider (eq a b); this is data if contained within a larger quoted expression, e.g. (quote (setf *cmp* (eq a b))), but is code if not otherwise quoted. I don't see this as a bad thing.
Thursday, August 28, 2008 3:20:02 AM UTC
Also, with some helper methods you can get your `inc' to work with just an added method call:

var inc = Lambda.Func((int x) => x+1);

This works, and is really quite trivial:

static class Lambda {
public static Func<T,T> Func<T>(Func<T,T> f) { return f; }
}

Mono.Rocks already provides Lambda for all builtin overloads of System.Func and System.Action (though I think I need to fix the naming, as both Func and Action use the "Func" method name, which can result in ambiguity due to inadequate type inferencing in the compiler, e.g. Lambda.Func<int>(Console.WriteLine) is currently ambiguous between the Action<int> and Func<int> overloads, which is incredibly stupid as Func<int> doesn't match Console.WriteLine(int)...).

http://anonsvn.mono-project.com/source/branches/rocks-playground/Mono.Rocks/Lambdas.cs

You wouldn't happen to know of a consistent naming scheme for Action<T>, Func<T>, Expression<Action<T>>, and Expression<Func<T>>, would you? Currently Lambda.Func() is used for the delegates and Lambda.Expression() is used for Expression<T>, but (as above) I think this needs to change. Lambda.Action() makes sense for the Action<T> delegates, but what to do for Expression<Action<T>>? ActionExpression?
Thursday, August 28, 2008 5:23:01 AM UTC
First, wow, and thanks for spending so much time educating me.

>I realize you want a fully functional language, but C# isn't, and likely never will be. It's trying (likely it's best) >to combine multiple different paradigms, but it can't fulfill all of them perfectly.

Yes, you're correct. I'm not necessarily looking for a fully functional language that’s cross-paradigm, but having F# as a measurement is handy.

>for Reference<T> vs. `ref`. The GC is fast, but it's not _that_ fast, not compared to by-ref parameter passing. I >don't see how it ever could be, either.

For tuples (and now I just assumed decomposition too):
(wt,iot) = ThreadPool.GetMinThreads();
I don't see why that would be slower than using out parameters. On a 64-bit platform, it should shove the entire thing in a register, no? [Not to mention I've seen few "good" uses of out.]

But yes, I'm assuming on blind faith that everything I've read re: OCaml/F# performance is accurate, and performance is pretty much on par with their imperative counterparts.

>Action/Func/Tuple would have the same restrictions. You either have true variadic generics at the IL level, and have >all languages support it, or you go with the current approach and have only a predefined number included with the >runtime, and beyond that number you're out of luck.

This is where the auto-conversion language support comes in. For the times you use a tuple/action/func with over, say, 8 arguments, if your language supports it, then it can automatically convert. I think C# is actually going to do this (for delegates), effectively killing my complaint, actually. Ditto for tuples. But

>values) {/*...*/} }`, and I have _no_ idea how you would would introduce e.g. properties on a per-type basis, e.g. to

Well, that's what decomposition is for. (a,b,c,d,e,f) = myTuple.

>Which brings me to another comment of yours: "have a first class function type." Delegates *are* the first-class >function type. The issue is that a method is implicitly convertible to potentially more than one delegate type.

Yes, but this is somewhat the basis of my overall complaint. "Function" should be the first class type, not the myriad of delegate types. If other delegate types existed, they'd only be handy type aliases to the canonical "function" type. The delegate type differences as they are today prevent this.

>but just implicitly convertible to something that is first class (making it 1.5 class?), this fails, and you instead >need a helper to tell the compiler which delegate type to use, e.g. Lambda.Func(Console.WriteLine).Curry(5), which still >fails (which WriteLine overload to use? plus issues with type inferencing in the C# 3 standard which will hopefully be

Ah yes, I see. The issues of method[group] conversion to delegate is another problem. With an "intrinsic function type", then we could _always_ know what the canonical representation of the method should be. Again, I'm hoping C# 4 might address this at least by making "Func" the default and allowing auto-conversion.

Overloading _is_ a problem, and probably one of, if not the, largest functional issues that C# faces. I'm too ignorant to know if fixing this is even remotely feasible.

>I don't see how Expression<T> allowing multiple statements changes this at all (and all signs point to Expression<T> >permitting statements in .NET 4, as part of the DLR effort).

Well, my quick thinking was that there will STILL be an overshadowing "data versus code" disallowing local methods. This is predicated on C# 4 "exalting" the Action/Func types. If they did that, then maybe:
var f = () => {return 1+1;}
Just maybe, that would have a chance of compiling. But if that's a valid Expression<T>, then there's still no chance without some other marker.

Basically, I want the lightest-weight way of declaring functions. The helper methods like Func() are "ok", but I dunno, somehow it just doesn't seem to work. Of course, this may also be the lack of type inference (since you have to specify your arguments' types often anyways).

>contained within a larger quoted expression, e.g. (quote (setf *cmp* (eq a b))), but is code if not otherwise quoted. I

Isn't "quote" being there what we're talking about?
Thursday, August 28, 2008 5:40:35 AM UTC
With respects to the Lambda.Action,etc. names, I did try doing that for a bit. Two things:

First, much of the benefit is in having type inference. Since the parameters still need to be explicitly typed, you aren't gaining _much_ over just doing "Func<int,int> inc = (i)=> i + a;". True, the return type is not there, but for any practical use, where the argument types are complicated, say, a tuple->expression dictionary, writing the type down makes it unusable.

Second, and this is a lame reason since I cannot quantify it well, it just didn't _feel_ good, even with heavy aliasing, to have to do F.F((int i) => ....). I don't even care too much for F#'s (fun ... ->) syntax, but "F.F" (the shortest you can do in C#), just didn't "work" for me. Again, this is probably some personal issue I have. I used to be much more enthusiastic about this, but after actually trying to use these concepts in practise, the futility of it all came crashing down on me. FFC talks about it here: http://flyingfrogblog.blogspot.com/2008/04/who-will-use-f.html - I want to follow up on this in a separate post.

Less subjectively, I think having to write anything nearly as long as Lambda.Func is unusable. If a "functional" library was standardized as F and had really small methods (F.F, F.A, etc.) maybe it'd work? But to save typing the return type but have to type, say, Lambda.ActionExpression... I dunno. If I had to do it, I'd probably use F and A, then something like XF or XA for expressions.
Thursday, August 28, 2008 2:33:37 PM UTC
> For tuples (and now I just assumed decomposition too):
> (wt,iot) = ThreadPool.GetMinThreads();
> I don't see why that would be slower than using out parameters. On a 64-bit platform, it should shove the entire thing in a register, no? [Not to mention I've seen few "good" uses of out.]

You need to think like a CPU. :-)

First, is a tuple a class type or a value type?

If it's a value type, then returning a tuple could be as fast as by-ref parameters, as the value type would be returned on the stack in CPU registers, as you imply.

However, I've seldom seen Tuple as a struct; it's usually a reference type, as this allows using inheritance as part of it's implementation (done in Mono.Rocks to minimize code duplication), using it as a base class, and helps keeps the stack footprint from being potentially ~huge.

So if Tuple is a reference type, things can *never* be as fast: you have GC object creation overhead, ThreadPool.GetMinTheads() would return a reference, and so you'd need to follow the reference to obtain your fields. All this might be inlinable by a really smart JIT, but it ~effectively becomes as difficult as allocating reference types on the stack, which is a non-trivial (and potentially unsolvable) problem if you want a *fast* JIT.

Add in non-sealed Tuples, and subclasses could add a finalizer, in which case any such "JIT optimizations" are null and void, as you need to invoke the finalizer, which means you need a GC-allocated object...

We could revisit the struct vs. class decision in Mono.Rocks, but it would involve having ~42+N members (where N is the number of items held in the tuple), at least if we continue having `struct Tuple<T1> : IList<object> {...}` (for late-bound access).

Having 4 (or more) types around with > 40 unique members isn't an ideal solution to me...

So for the foreseeable future, I don't see Tuples as replacing `out' parameters.

(I also like the style that `out' parameters enable, in particular the TryParse()-style methods on int/etc., for when you don't want to deal with the overhead of exception handling, which can be a significant overhead in some situations.)

> But yes, I'm assuming on blind faith that everything I've read re: OCaml/F# performance is accurate, and performance is pretty much on par with their imperative counterparts.

OCaml doesn't run on the CLR, and thus can use any internal optimization it deems useful. F# does run on the CLR, and thus needs to live with some of its limitations.

In particular, it seems that _to .NET_ F# tuples are limited to 7 elements; see: http://research.microsoft.com/fsharp/manual/FSharp.Core/Microsoft.FSharp.Core.type_Tuple.html

Yet the language manual has no such limitation, so I wonder how a F#-created tuple with 10 elements is exported to C#. Would it use "tuple chaining", e.g. Tuple<int,Tuple<int,Tuple<int<...>>>>?

Also, I'm not entirely sure but it seems that the F# tuple is a reference type. (At least, it doesn't follow the F# manual's syntax for value types...)

>Action/Func/Tuple would have the same restrictions. You either have true variadic generics at the IL level, and have all languages support it, or you go with the current approach and have only a predefined number included with the runtime, and beyond that number you're out of luck.
>
> This is where the auto-conversion language support comes in. For the times you use a tuple/action/func with over, say, 8 arguments, if your language supports it, then it can automatically convert. I think C# is actually going to do this (for delegates), effectively killing my complaint, actually. Ditto for tuples. But


But an auto conversion to _what_? There needs to be a conversion to some specific type, something that can handle ~all potential methods.

There's also another issue: Action & Func are nearly identical except for the return type. So if you're reading code and see `var a = x => Foo(x);`, is that an Action or a Func? You'd have to look at Foo to be sure.

So a given expression doesn't have just one potential type, but one among a set of types, and that's before we even introduce Expression<T> (particularly since we should be able to convert any Func into an Action by simply ignoring its return value).

> Yes, but this is somewhat the basis of my overall complaint. "Function" should be the first class type, not the myriad of delegate types. If other delegate types existed, they'd only be handy type aliases to the canonical "function" type. The delegate type differences as they are today prevent this.


Action vs. Func will also prevent this. And even if we didn't have Action and Func but just Func (with an allowance for `void` as a generic parameter, e.g. Func<int,void>), we'd still have the issue with method overloading. (Which is probably why most functional languages don't permit overloading, as overloading introduces lots of ambiguities like this.)

>but just implicitly convertible to something that is first class (making it 1.5 class?), this fails, and you instead >need a helper to tell the compiler which delegate type to use, e.g. Lambda.Func(Console.WriteLine).Curry(5), which still >fails (which WriteLine overload to use? plus issues with type inferencing in the C# 3 standard which will hopefully be
>
> Ah yes, I see. The issues of method[group] conversion to delegate is another problem. With an "intrinsic function type", then we could _always_ know what the canonical representation of the method should be. Again, I'm hoping C# 4 might address this at least by making "Func" the default and allowing auto-conversion.

You could only have a "canonical Function representation" if you had *both* variadic generics (which I don't think will happen soon) *and* the ability for `void' to be used as a type parameter, in which case Func<T..., TResult> would be the canonical representation.

And you'd *still* have the issue of method overloading; if you want the Func for Console.WriteLine, _which_ method is that? And thus what's the type of the method you're getting?

> Overloading _is_ a problem, and probably one of, if not the, largest functional issues that C# faces. I'm too ignorant to know if fixing this is even remotely feasible.

I suspect it isn't feasible, which is why we have method groups with implicit conversions to a specific type. You need to tell the compiler which type you're after, not let the compiler figure it out (as it will deduce which method to use based on what you tell it/request; something needs to bootstrap the process).

> Well, my quick thinking was that there will STILL be an overshadowing "data versus code" disallowing local methods. This is predicated on C# 4 "exalting" the Action/Func types. If they did that, then maybe:
> var f = () => {return 1+1;}
> Just maybe, that would have a chance of compiling. But if that's a valid Expression<T>, then there's still no chance without some other marker.

I can't see that not also being a valid Expression<T>. Now, your example could still work by having it "default" to Func<...> unless assigning to Expression<T>, but this has the downside of inserting "magic", which may be good, but may not be.

> Basically, I want the lightest-weight way of declaring functions. The helper methods like Func() are "ok", but I dunno, somehow it just doesn't seem to work. Of course, this may also be the lack of type inference (since you have to specify your arguments' types often anyways).

Type inference can't help you. Consider the expression "x+1": what types can x be? byte, short, int, long, and the unsigned counterparts, and then there are any user-defined types which provide an operator+(T,int) method.

So `var inc = x => x+1;` will likely never be supported, but an explicitly typed variant could be: var inc = (int x) => x+1;

>contained within a larger quoted expression, e.g. (quote (setf *cmp* (eq a b))), but is code if not otherwise quoted. I
>
> Isn't "quote" being there what we're talking about?

Precisely, (quote) is Expression<T>, so (just as with QUOTE) you need to look at the context of an expression to determine it's actual type. In C#, the context is provided by the left-hand-side, what you're assigning to (or the method parameter type).

Since `var inc = x => x+1;` doesn't provide any context, requiring the compiler to deduce ~everything, I can't see this working unless you provide some form of "default" rules, e.g. "a lambda expression is a delegate type unless within an Expression<T> context," etc.
Friday, August 29, 2008 12:42:57 AM UTC
>First, is a tuple a class type or a value type?

I never found any reasons for Tuple to be a reference type. It's just 2 or more values together. A triple is NOT compatible with a double, so there's no reason for inheritance. As far as more code... seems easy enough to codegen a tuple struct.

>it's implementation (done in Mono.Rocks to minimize code duplication), using it as a base class, and helps keeps the >stack footprint from being potentially ~huge.

Well, if someone's doing some 8+ tuples of large value types, they're sort of asking for it. In practice, I've rarely seen beyond triples.

>number of items held in the tuple), at least if we continue having `struct Tuple<T1> : IList<object> {...}` (for late->bound access).

Tuple is not a List. I fail to see why the amount of codegen for a Tuple library is important. Save the generation script in the comments for Tuple.cs or something :).

>So for the foreseeable future, I don't see Tuples as replacing `out' parameters.

The allocations in particular are an issue, yes, but I never considered a reference type for tuples. That said, F# uses reference types for everything, and I'm about 20 years too soon to question or understand dsyme, so I'll just plead ignorance here.

>(I also like the style that `out' parameters enable, in particular the
>TryParse()-style methods on int/etc., for when >you don't want to deal
>with the overhead of exception handling, which can be a significant
>overhead in some situations.)

Well, we're getting back into something I've been kicking around, and that is C# needs to go much further to do things right. Further than I've made it look like I'm suggestion. For instance, the right thing for TryParse is to return an Option<T>. Then you could pattern match or otherwise deal with it.

But still, with language-integrated tuples, you could have:

(valid, res) = TryParse("123");
if (valid)...

I can see why that's suboptimal, and without more pattern matching, I don't see a better alternative. I guess what I do is hope that by adding a few features, the teams would add any helping features to make it work right.

But even apart from these, having a well-known tuple type solves the problem of everyone having to create their own. Or worse, people creating pointless miniclasses like "KeyValuePair", or "KeyPair". Example: Starting from a fresh C# project, how do I create a dictionary that has a key of two integers?

>OCaml doesn't run on the CLR, and thus can use any internal optimization it deems useful. F# does run on the CLR, and >thus needs to live with some of its limitations.

But my point was that OCaml takes a similar approach and CAN perform quite well. So it's a matter of making sure the CLR/JIT people are on top of it. If, say, C# said "we're going to really do things functionally correct", I'm sure the CLR people would have a lot more pressure on them than they get from dsyme (unfortunately).

>Would it use "tuple chaining", e.g. Tuple<int,Tuple<int,Tuple<int<...>>>>?

Interesting. Yes, it chains them. Here's (1,2,3,4,5,6,7,8,9): [FSharp.Core]Microsoft.FSharp.Core.Tuple`7<int32,int32,int32,int32,int32,int32,class [FSharp.Core]Microsoft.FSharp.Core.Tuple`3<int32,int32,int32>>

>But an auto conversion to _what_? There needs to be a conversion to some specific type, something that can handle ~all >potential methods.

I suppose the best way is for the CLR itself to knock off this silly delegate distinguishing. Then any delegate with the right signature would be compatible because the CLR would treat it equally (so a compiler generated Func6 is compatible with any other delegate of the same signature). I guess I just don't understand: Why should Predicate<T> be different to the CLR than Func<T, bool>? [Yes I see how the compiler alone would have trouble w.r.t versioning.]

>There's also another issue: Action & Func are nearly identical except for the return type. So if you're reading code and >see `var a = x => Foo(x);`, is that an Action or a Func? You'd have to look at Foo to be sure.

That's no problem - that's just good type inference at work. It's unfortunate that void is treated so differently.

>introduce Expression<T> (particularly since we should be able to convert any Func into an Action by simply ignoring its >return value).

I used to agree with this, but after NOT being able to just disregard return values, I find it very valuable to force consumption of a return value, either explicitly, or implicitly (it becomes the return for the current function). Of course, now I'm assuming C# will have decent syntax for explicitly ignoring values.

>`void` as a generic parameter, e.g. Func<int,void>), we'd still have
>the issue with method overloading. (Which is >probably why most
>functional languages don't permit overloading, as overloading
>introduces lots of ambiguities like

Yea, C#'s style of overloading is a pain, plain and simple. With optional, named, parameters, "difficult" overloading is less necessary, but that doesn't solve today's problem. So I do accept and admit that the common C# overloading, where you have signatures like "A", "A, B", "A, B, C" (really, emulating optional parameters) leaves you screwed. There is still plenty of overloading that is fine (say, same number of arguments, or different types at each level). But yes, that's not too common in .NET.

>You could only have a "canonical Function representation" if you had *both* variadic generics (which I don't think will >happen soon) *and* the ability for `void' to be used as a type parameter, in which case Func<T..., TResult> would be the >canonical representation.

I think what you're getting hung up on is that you're saying function types should be representable _inside_ the type system, rather than as _part_ of the type system.

>> var f = () => {return 1+1;}
>
>I can't see that not also being a valid Expression<T>. Now, your example could still work by having it "default" to >Func<...> unless assigning to Expression<T>, but this has the downside of inserting "magic", which may be good, but may >not be.

Well, I'm not sure why this is "magic", except in light of the design decisions that C# took. It should be vastly more common to use normal lambdas than quoted code. EXCEPT if you think of lambdas as "just for LINQ", in which case it's a whole different story. This leads back to my recurring complaint that C# took LINQ and then implemented features around it, versus features that stand on their own merits.

But yes Func/Action _should_ be the default; that’s my whole point.

>Type inference can't help you. Consider the expression "x+1": what types can x be? byte, short, int, long, and the >unsigned counterparts, and then there are any user-defined types which provide an operator+(T,int) method.
>
>So `var inc = x => x+1;` will likely never be supported, but an
>explicitly typed variant could be: var inc = (int x) => >x+1;

In this particular case, yea, ok. You could assume int like OCaml does, but this is a rather specific case. But inference DOES help tons. In a similar, simple case:
var add = (double a, b) => a + b;
The type of b can be inferred.

But there's much, much more. Many times an argument will be passed into a method explicitly. In all those cases (overloading withstanding), the types can be inferred. In some cases, it's _necessary_ (thanks C# anon types for illustrating something!):

var dic = Enumerable.Range(1, 100).ToDictionary(i=> new { i }); var lookup = x => dic[x];

C# can't handle this at all since it NEEDS the type annotation. And without anonymous types, sure it can WORK, but the typing overhead is so high (for no reason) that it's just not productive to use this style. Hence, C# loses out.

In practise, there is often a lot of context for inner methods, and they rarely end up requiring annotations. A similar example in F#:

let d = dict [(1,1)]
let lookup x = d.Item(x)
lookup 1

No annotation for x, yet it works because it can be inferred by how it's used. C# teams immediate response is "but overloading!" and hence they throw the entire feature out (even though there's no overloading in many cases). Their strange dedication to sanctity of overloading nukes the rest of the language. I don't get it.

>contained within a larger quoted expression, e.g. (quote (setf *cmp*
>(eq a b))), but is code if not otherwise quoted. I Precisely, (quote)
>is Expression<T>, so (just as with QUOTE) you need to look at the
>context of an expression to

You already explained my point: " but is code if not otherwise quoted " -- that's the thing, the COMMON case is that it's code. _Sometimes_ you want quoted code, and you call it out as such. You shouldn't have to call both cases out (as C# requires).

>Since `var inc = x => x+1;` doesn't provide any context, requiring the compiler to deduce ~everything, I can't see this >working unless you provide some form of "default" rules, e.g. "a lambda expression is a delegate type unless within an >Expression<T> context," etc.

Yes - the fact that C# doesn't have that default is what makes it so weird. But again, see my whole "LINQ was the driver at all costs" complaint.

None of the changes I suggest are necessarily "easy" or "simple", but they represent a superior approach to how things _should_ have been handled. I keep hope that every new version of the CLR could possibly allow some breakthrough --

But, I've come to the realization that unless C# has a real change of heart, I can't see it getting significantly better. It seems like they've boxed themselves in pretty tight to the existing CLR concepts and won't break out of that. I get the interop reasons for this, I don't see why this should be an overshadowing thing.

I question the position and reason for C# apart from a "lowest common denominator interop language". F# is superior in nearly everything (what can C# do well that F# can't?). This is a serious question, and apart from some interop scenarios, I'm coming up empty handed. It would pain me to think of C# as "VB.NET's more serious cousin".
Friday, August 29, 2008 1:27:36 AM UTC
>Action/Func/Tuple would have the same restrictions. You either have true variadic generics at the IL level, and have

Oh I also want to point out that you could of course do Func<A,Func<B,Func<C>>> if you wanted to represent this in the type system. Not saying it'd work particularly well to just "drop in" to C#, but it does represent things inside the constraints you were talking about.
Friday, August 29, 2008 1:23:50 PM UTC
Not all delegates can be represented using Action/Func. Take this delegate:
delegate SelfReturning SelfReturning();

SelfReturning A() {
return A;
}
The type would be Func<Func<Func<Func<...>>>> - but the .NET type system does not support infinite generics.
Daniel G.
Friday, August 29, 2008 5:23:16 PM UTC
@Daniel Uh, yea, I did point that out in my post, as well as that these things have specific limited use and could be hard coded.
OpenID
Please login with either your OpenID above, or your details below.
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Live Comment Preview