Logo




Subscribe:
RSS 2.0 | Atom 1.0
Categories:

Sign In


[Giagnocavo]Michael::Write()

# Wednesday, August 27, 2008
Extension Methods Suck
Been meaning to write this for a while, and I think I touched on it here, but I'd like to expand a bit. C# Extension Methods are just a hack to compensate for C#'s poor handling of functions in general. As far as I can tell, they were added solely so you can do this type of thing in LINQ:

  var squares = myInts.Select(i => i * i)

Instead of:

  var squares = Enumerable.Select(myInts, i => i * i)

In other words, they wanted to provide some simple infix syntax for functions. Well, that's a poor approach to the problem.

    - First off, it only works on function specially declared to be "extensions", which means your composition options are limited to whatever the library has built in. I can't send arguments to arbitrary static methods, like, say, "someVar.Console.WriteLine".

    - Second, since extension methods are defined solely by their method name, ambiguity is quite easy to come across. There's no way to qualify Foo.Function versus Bar.Function.

Pipeline                                                       
Other languages approach this with two simple things (which actually simplify the entire language/type system overall). First off, we need to be able to define function operators. I'll demonstrate with F#'s pipeline operator:

    let (|>) x f = f x

[As a side note, any language that lets you toss around operators and functions will allow this kind of syntax – it isn’t that F# had to have compiler support for this particular operator.]

In fake C#, it’d be something like (for fun, notice the lack of type inference):

B operator|> <A, B>(A x, Func<A, B> f) { return f(x); }

This means that the |> operator will take x on the left and apply it to f on the right side. If this existed in C#, you'd be able to write something like:

    "Hello" |> Console.WriteLine
or
    if (myInts |> Enumerable.Any) { .... }

Now we can pass in a parameter to static methods. But, hey, whaddya know? With this, Extension Methods are solved for all single-argument static methods! That was easy. But what about Select – it takes two parameters, so this won’t work.

Enter the Lambda
What if ALL functions took one parameter and output one parameter? If that were the case, then we’d be set. But how do allow more than one parameter? Well, what if, every time you declared a method with more than one parameter, it actually returned a method that took the next parameter? For example, we could write “Add” as:

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

This is known as the curried form of Add. We’d now call it as: Add(5)(6). We can do cute stuff like “var inc = Add(1)”. But, as the Add declaration shows, in C# this is too unwieldy (and this is a simple example!). The compiler should actually do all this for us, so we can just write our functions normally but use them as if they were written in curried form.

Now, if we simply swap the order of arguments for Enumerable.Select, we have our extension method ready to roll: Enumerable.Select(Func, IEnumerable) can be used so:

var squares = myInts |> Enumerable.Select(i => i * i)

Now the call to Select takes the lambda (i * i) and returns a function that takes an Enumerable. It is then given the myInts, and everyone is happy. This is just a quick, crap, explanation. Google can lead you to many more interesting resources about partial application, currying and so on.

At any rate, I think if C# had taken this approach, we'd all be much better off. To top it off, more functions would take their arguments in a proper style. As it is, uncurried versions of Extension Methods are incompatible with normal function pipelining. Oh well.


Code | FSharp
Wednesday, August 27, 2008 12:17:53 AM UTC  #    Comments [15]  |  Trackback

Wednesday, August 27, 2008 2:18:23 AM UTC
"Second, since extension methods are defined solely by their method name, ambiguity is quite easy to come across. There's no way to qualify Foo.Function versus Bar.Function."

Can't you resolve the conflict by renaming the methods with the "using" statement?
Greg
Wednesday, August 27, 2008 2:56:24 AM UTC
C#'s using will only alias namespaces or specific types (you can't alias List<T>, but you can alias List<int>).

Funny though, if you do try to alias a method, IntelliSense sorta works (it displays the method) - but of course it doesn't compile.
Wednesday, August 27, 2008 2:56:39 AM UTC
I do agree with you on the negative side of Extension Methods, but also notice that in a few cases it is a useful feature (refer to Paint.NET author)

http://blog.getpaint.net/2008/07/03/c-extension-methods-portability-aid/

In my own open source project, I also plan to move extra overloading functions out of the main assembly. I think Extension Methods makes it possible to place overloading methods into a separate Extension assembly. (Yes, I find it usually hard to provide enough overloading methods.)
Wednesday, August 27, 2008 3:08:56 AM UTC
Extension methods don't enable what the Paint.NET guy mentions. Any selective namespace opening will enable that. Extension methods simply inherit namespace opening. For instance:

namespace Foo { public static class Util { public static int GetCurrent() { } }
namespace Bar { public static class Util { public static long GetCurrent() { } }

Depending on if I open Foo or Bar, a line that says "Util.GetCurrent()" will return either an int or a long. The fact that I could turn them into extension methods is irrelevant.

If you're creating your own library, then why wouldn't you include those functions right in the same assembly? Extending a type (even as simply as Extension Methods) should be generally frowned on.

And if C#/VB really wanted to expose type extensions, they would have done a real job (properties, static methods) instead of just enough to make their LINQ stuff work.
Wednesday, August 27, 2008 4:14:34 AM UTC
I think there's one real answer for why C# went with extension methods and not a |> operator:

http://www.charlespetzold.com/etc/DoesVisualStudioRotTheMind.html

And the answer is Yes, Visual Studio (and similar tools like IntelliJ IDEA, Eclipse, etc.) do rot the mind by increasing reliance on Intellisense/autocomplete/whatever-they-choose-to-call-it.

The "problem" with |> is that you need to know, in advance, the class+method that you want to be invoking. It makes "discoverability" harder, as you won't know the class+method exists unless you (gasp!) read documentation or see it in pre-existing code.

With Extension Methods, as long as you have the right `using' clauses in place (and I'm sure the IDE will be helpful enough to add some like System.Linq for you), as soon as you hit '.' all relevant extension methods will be shown. The developer won't know which method is an extension method and which is from the object itself, and they won't need to care. Discoverability is increased (it's shown in intellisense! How could it be easier to find!).

And we all grow slightly dumber as a result.

(The other, likely "real real" reason, is that extension methods allow for a "pattern" to be used for query comprehension syntax, so that `from e in c where e > 10 select e` can be ~directly mapped to extension methods e.g. c.Where(e => e > 10).Select(e)`, *regardless* of type, and things will Just Work. Such "Just Work"ing requires ~global method names for a given set of types, otherwise query comprehensions wouldn't work in a reasonable way. Of course, macros would solve this in a more extensible fashion, but macros are "evil" and thus seemingly forever off the table...)
Wednesday, August 27, 2008 4:57:33 AM UTC
Hmm, I thought it was the whole functional paradigm being too difficult for C# (for whatever reason).

But it's just like using existing utility libraries - you can poke around and run into them. Otherwise you'd never find, say, System.Math.

As far as comprehensions, I suppose that's how Queryable/Enumerable are implemented - are there any other pattern providers? But there's gotta be a better way... like what if you just prefixed a query with the pattern provider to use? It'd be extensible (for those wanting to create a pattern, not just because a type happened to have a "Where" on it) and unambiguous. As a bonus, code that's going to queryable/expression<T> would be different than compiled code. At any rate, that cant be the "real real" reason... can it?
Wednesday, August 27, 2008 10:24:06 AM UTC
The point is just to extent the original class with additional features, and make them accessible as if they were available from the beginning.
It also makes the syntax more concise since I don't have to type the name of the extension class.

With your concept, you have to write "if (myInts |> Enumerable.Any) { .... }" instead of "if (myInts.Any) { .... }"
I just don't see the point instead of introducing yet another syntax, and having to type the name of the extension class I'd prefer "if (Enumerable.Any(myInts)) { .... }".

The only problem I could think of, is that in the end, you would tend to forget if a particular method is an extension or not.

Now if you don't like a feature, don't use it, and let the developers who like it enjoy their new toy.
Laurent Debacker
Wednesday, August 27, 2008 12:30:08 PM UTC
I think the "real real real" reason is the whole "macros are teh 3vil!" mentality in C#. Query comprehensions can be done with Scheme macros (http://xacc.wordpress.com/2008/05/19/linq-for-r6rs-scheme-final/, http://xacc.wordpress.com/2008/04/20/linq-for-r6rs-scheme-take-9/) and Nemerle (http://osdir.com/ml/lang.nemerle.devel/2006-01/msg00100.html, etc.).

So the "traditional functional" solution for LINQ query comprehension-like behavior would be...macros, which (so far) Hejlsberg and co. refuse to add to the language.

Lacking a decent macro system ("decent" == "Lisp/Scheme/Nemerle/Boo-like, NOT C/C++ like"), compiler support is required for all language constructs (depending on your view on whether higher-ordered functions add language constructs).

So I don't think any one factor "requires" the current extension method design, but the combination of (1) improved IntelliSense support, (2) simplified LINQ query comprehension support, and (3) other languages like Ruby and (iirc) Objective-C support ~similar concepts (of adding instance members to existing types), making this an occasionally requested feature, resulted in the design of extension methods that we currently have.

Finally, are there any other LINQ pattern providers? Yes: LINQ-to-SQL (which builds upon Queryable, but due to the LINQ design is also ~distinct). Once NHibernate gets in gear it will also support LINQ-to-SQL, and thus would also be a LINQ pattern provider. LINQ-to-XML may also qualify.

Plus much of the point to LINQ query comprehensions is that it is extensible. We may not have macros, but at least we can ~trivially extend the existing syntax to support new problem domains in the future.
Wednesday, August 27, 2008 8:23:11 PM UTC
@Laurent

Yes, throwing away the extension class and just using a period _is_ shorter. But you could make the claim for any static functions - bring them all in scope!

As far as calling it normal style, it doesn't compose well at all. You end up with: Enumerable.Any(Enumerable.Where(Enumerable.Select.....) which is crap to read. By being able to manipulate your functions, you can accomplish the same thing, but build up to it.

As far as |> being more syntax, the point I didn't fully make is that it's actually _less_ syntax. By treating functions as first class, supporting auto-curry, automatic generalization, etc. the language becomes simpler. Constructs such as |> are defined _in the language_. It's all one simple syntax. It seems to me to have a simple base syntax that can compose into higher level features is better than having a bunch of specific high level features.

Finally, I'm not opposed to type extensions per se. Just that C# decided to implement only a bit of type extensions, and only for the explicit purpose of enabling their LINQ syntax rather than for the language as a whole.
Wednesday, August 27, 2008 8:54:57 PM UTC
@Jonathan

Yes, my first thought when I saw LINQ query comprehensions was "oh sweet, how is that defined". Slowly it dawned on me that no, it's done in the compiler. Sigh.

Type extensions may be a feature to add. But if so, then add type extensions, don't go cherry picking. I think that sums up what I don't like about C# 3's approach.

It's as if they started with query comprehensions, then said "what is the most specific set of things we can do to VB and C# to enable this syntax" and just went from there. So we get weird things like type extensions _only for methods_, variable type inference _for locals only_, lack of imperative constructs (ForEach), or really much of anything that isn't SQL related in the standard Enumerable library and so on.

As far as the LINQ providers, I tried some LINQ-to-SQL queries and found nothing but Queryable. Using reflector I searched for Where, Join, SelectMany, GroupBy, etc. and only found Enumerable and Queryable. Where are the other providers -- or what am I missing?
Wednesday, August 27, 2008 10:26:00 PM UTC
Pervasive currying kind of gets in the way of function overloading.

void Print(int indent, string v);
void Print(int indent, double v);

What type does the expression "Print(3)" have?

Maybe there's a way to resolve this issue. Or maybe the benefit of curried methods outweighs the benefit of function overloading. Either way, it's something you have to address before claiming a clear win.

Also, I'm surprised that C#'s extension methods don't distinguish between Foo.Function and Bar.Function -- it's relatively straightforward to do so. If this is true, I'm sure it was a conscious decision by the C# 3.0 designers -- possibly to reduce the potential for misleading code. At first glance, however, this seems like a bad decision.
Wednesday, August 27, 2008 11:53:14 PM UTC
Overloading in general is a bitch :).At any rate, the way F# deals with this issue is that first, you have to specify which function you want if you end up with a totally ambiguous overload. Second, since there are named and optional parameters, overloading is a lot less interesting from the start. (Since C# doesn't support named parameters, and has the idiotic null class/non-null struct system, it's hard to avoid overloading.)

Additionally, the benefit of being able to declare operators like |> is that you can flow type info:

type test =
[<OverloadID("xi")>]
static member foo x (a:int) = printfn "%s%d " x a
[<OverloadID("xs")>]
static member foo x (a:string) = printfn "%s%s " x a
do 2 |> test.foo "hi int"
do "wat" |> test.foo "hi string"

Alternatively you can specify which overload you want:
let f = (test.foo : string -> int -> unit) "hi" 2

Note this isn't a problem if there's no ambiguity in the list of arguments for the different overloads (if the second case didn't take a string as its first argument, it'd be fine).

But AFAIK this syntax won't work, because of the reasons you mentioned:
let f = test.foo "hi" 2

I simply don't know enough about type systems to see if it would be possible to look forward more to gather type info or if it's just extremely difficult.

On another note, the C# team seems to be against doing anything that wouldn't work for overloaded methods. So even where you could come up with C# syntax for non-overloaded methods, they'd reject it (from what I've seen). The other other reason involved here is that they are against function types for some reason. Automatic currying would force them to exalt Func<..> delegate over other delegates, and .NET likes thinking delegates are cool and special or something.
Thursday, August 28, 2008 2:03:23 PM UTC
with proper namespaces included in current scope extension methods are very easy to discover unlike static methods of any other class. considering that you cannot remeber everything, intellisense + extension methods are much better than a set of classes and their static methods.
Thursday, August 28, 2008 3:55:44 PM UTC
Sure, it's easier in IntelliSense. But then you could argue the same way for _any_ static method, like all the System.Math ones, for example.

I'm not against type extensions, but their use should be much more limited. Enumerable doesn't seem to warrant being added as an extension method, if other suitable syntax were available.
Friday, August 29, 2008 10:45:52 AM UTC
I agree with Laurent's comments about excessive verbosity and Jon's comment about discoverability (IntelliSense).

However, I also agree with a lot of points from Michael, and also this one from Laurent:

>The only problem I could think of, is that in the end, you would tend to forget if a particular method is an extension or not.

Then, if I were in the C# team, I would have chosen a different qualifier than "." to access an extension method, why not simply ".."?

Anyway, these semantics can be discovered at design time by your IDE, so maybe in the future we come out with different colouring for extension methods in order to differenciate them easily. CC yourselves to https://bugzilla.novell.com/show_bug.cgi?id=323385 and https://bugzilla.novell.com/show_bug.cgi?id=363499.

BTW, I don't agree with this comment:
>Now if you don't like a feature, don't use it, and let the developers who like it enjoy their new toy.

I think that publishing a new version of a so much used language is a serious thing. And we must be critic. This is the only downside of Mono: we couldn't decide on this :(
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