Tuesday, May 6, 2008

Complaining about .NET 3.5

I been doing some pretty serious functional programming with the .NET framework since the LINQ preview was released early last year. After rougly a year of use I feel that I now have the experience to confidently make a list of complaints. This is by no means a rant. The .NET framework is excellent but the following omissions are really needling me. 1. No IRandomAccessCollection<T> interface. We need a way of indicating that a collection is randomly accessible. This means it has an O(1) method to access a given element in an array and an O(1) count property. IList<T> and System.Array should both inherit from it. Often I want to perform a stream operation that requires random access to elements in order to do its job efficiently. Either an Array or a List will do. Copying a stream into an array is potentially expensive. If the stream is already an Array or a List it's worthwhile to attempt a run-time cast because it may save considerable time. As a result I end up doing two casts and writing an adapter. This is essentially what's done in ParallelFX today and I overhead that Joe Duffy was looking for feedback as to whether an interface like this one should be added to the BCL. Yes please. 2. No BigInteger. By this I mean an integer that grows to any size (until it uses up available memory). It was announced that this would be introduced in .NET 3.5 but it was made internal at the last second. Blog posts asking for an explanation were ignored. I believe I understand the reasoning. There is always a way of avoiding using a BigInteger and producing a more efficient algorithm. That said, sometimes I want to trade efficiency for elegance, reliability (no overflows), and speed of development. That's a decision that should be left up to me. Occasionally the lack of a BigInteger is inconvenient enough to make me use IronPython instead of C# for certain programs.* 3. Cost of Concat. Concatenating IEnumerable's together is costly. This is unacceptable because streams are a fundamental C# idiom. Wes Dyer explains more fully in this blog post. This problem has really affected the efficiency and elegance of my functional algorithms. To address this problem the following new C# syntax has been proposed: yield return someValue yield foreach someIEnumerable; I hope this is adopted. This is a big issue. 4. Having to implement IEnumerable.MoveNext() every time I implement IEnumerable<T>. C# already has syntactical support for streams (yield). Would it have been so hard for the compiler to generate this function if it didn't already exist? I know this is probably a non-starter and not that big a deal, but frankly it's an embarrassing amount of cruft to have to write. 5. Having to read/write "IEnumerable<IEnumerable<string>>" is awful. In the same vein as the previous complaint, why not provide syntactical support for declaring streams. C-Omega, the precursor to C#, used the asterisk to indicate a type was a stream in a similar way that [] indicates a type is an array. Streams are so pervasive in C# that I believe they are worthy of the same syntactical support as an array. I know the asterisk is out because C# already uses them for pointer arithmetic, but what about this: T[...][...] GetPermutations<T>(this T[...] stream) { // ... } That's just off the top of my head and there may be a better syntax but you see my point. 6. Lack of a non-null modifier. One of the unfortunate but necessary attributes of C# is that it has the concept of nullness. This is necessary because C# wants to play well with unmanaged code. Nevertheless nullness breaks polymorphism. A great way of mitigating this issue is to provide compiler support to prevent NullReferenceExceptions. C-Omega allowed you to add "!" to the end of a type and the compiler would enforce that a null value couldn't be assigned to it. Customer! cust = null; // compiler error I don't understand why this wasn't added to C#. I assume there's a good reason. Perhaps someone could explain it to me? That's all I can think of at the moment. I'll no doubt update this post as I run across further issues. *I like Python. I do think C#'s query comprehensions and its ubiquitous stream monad make it better suited for functional programming though.

7 comments:

Judah Gabriel Himango said...

I agree with #1, #3, #5. If feel so strongly about #6, I've been telling the C# guys to add the Spec# stuff since C# 3 was being designed. I'm really hoping C# 4 sees non-null concepts, pre and post conditions...I just want something that helps us write less buggy code.

Judah Gabriel Himango said...

Also, if the C# compiler could enforce pre- and post-conditions, including non-null parameter, think of all the unit tests you *wouldn't* have to write!

Anonymous said...

About #2. There was a good reason why Microsoft didn't ship BigInteger in the final version of .NET 3.5. Just read BCL Team blog:
http://blogs.msdn.com/bclteam/archive/2008/01/04/where-did-biginteger-go-melitta-andersen.aspx

I'm not sure I'm following #3. Isn’t the Enumerable.Concat[T] method what you’re looking for? It functions like this:
Enumerable.Concat[T](this IEnumerable[T] first, IEnumerable[T] second)
{
foreach (T t in first) yield return t;
foreach (T t in second) yield return t;
}

About #6. I agree. Having compile time support for something like that would be wonderful. I agree with Judah, think of all the unit tests we wouldn’t have to write!

Anonymous said...

I agree that not having BigInteger in the BCL is annoying but looks like Mono has an implementation available:

http://www.go-mono.com/docs/index.aspx?link=T%3AMono.Math.BigInteger

Jafar Husain said...

Judah: Agreed :-).

Steven: The cost of using concat is not linear. Read the Wes Dyer blog post for an explanation why.

Anonymous: Thanks! I found some BigInteger implementations on CodePlex but I wanted something really robust. Looks like this may be the ticket.

IDisposable said...

#1: Agreed
#2: Import F#'s or J#'s for now
#4: Meh
#5: typedef (as pure symtactic sugar) is what I want here
#6: ABSOLUTE MUST!

#3: Concat written like this:

static IEnumerable<T> Concat<T>(params IEnumerable<T>[] sequences)
{
foreach (var seq in sequences)
foreach (var item in seq)
yield return item;
}

Is linear in performance but loses the extension method goodness.

Anonymous said...

情趣用品|情趣用品|情趣用品|情趣|情趣用品|情趣

About Me

My photo
I'm a software developer who started programming at age 16 and never saw any reason to stop. I'm working on the Presentation Platform Controls team at Microsoft. My primary interests are functional programming, and Rich Internet Applications.