9/10/07

C# 3.0 Collection Initializers, Duck Typing and ISupportsAdd

Ilya Ryzhenkov from the ReSharper team has an interesting post: C# 3.0 Collection Initializers - Incomplete Feature? The problem is:

... restrictions are too strong - type being constructed should implement IEnumerable and have instance method "Add". IEnumerable is not of a big deal, but inability to use extension methods for Add is deal breaker.
I think that Ilya makes a very good point and that this feature indeed could be made more universally applicable. However, it is important to clearly understand the semantics of this feature to use it correctly - developers should know precisely what happens behind the curtains, otherwise unexpected side-effects can occur.

Now let's brainstorm a little bit. Recently I wrote about more fine-granular interfaces. I exaggerated a little, but the idea was that an interface should define a minimal contract. Specifically, I expressed regret that there is no ISupportsAdd interface, because adding stuff is a widely used ability of many entities, not necessarily collections.

I think it would help a lot if we had an ISupportsAdd/ISupportsAdd<T> interface with the only void Add(T item) method (you could also name it IFillable, IAllowsAdd, ICanAdd, you name it). For places in the framework where Add methods are named differently (e.g. AddPermission), this interface could be implemented explicitly.

This leads us to another problem: there are already lots of shipped types that do not implement this "ISupportsAdd" interface. How to add an implementation of an interface to a type without changing the type (and its declaring assembly)? We'd need something like extension methods ("extension interfaces", anyone?). Orion Edwards makes (in my opinion) a terrific suggestion:

Define an alternative to an interface called "requirement". It would work and behave exactly the same as an interface EXCEPT that it would use duck typing instead of static typing. For example:
public requirement Closeable
{
void Close();
}

public void TestMethod( Closeable c )
{
c.Close();
}
TestMethod( new System.Windows.Forms.Form() );

Collection initializers are another case where duck typing is sneaking into the C# language. Guess what the first case is? Right, the foreach statement. I was surprised, too, when I read a post by Krzysztof Cwalina about Duck Notation. It turns out, foreach doesn't necessarily require IEnumerable - it can also use duck typing to recognize iterable entities.

However, I'm not a professional language designer and I have no idea how duck typing would behave in a statically typed language. The known problem with duck-typing is that it orients to spelling of members, and not the semantics. There could be cases where members are named equally but have totally different semantics, so duck-typing would destroy the benefits of static type checking by allowing semantically incompatible assignments. But with this explicit "requirement" duck typing, who knows, maybe it's a good idea.

I'm just saying that I already had a lot of cases where I regretted that a shipped type doesn't implement some interface - and I couldn't add this interface to the type declaration because I'm the consumer, and not the producer of the type. I believe that "Extension interfaces" or "explicit duck typing" would really help here.

P.S. Oh, and can anyone explain why do we need to implement IEnumerable to be able to use collection initializers? I'm probably overseeing something obvious, but I thought I'd take the risk of sounding stupid and ask anyway :)

Links:
On duck typing in .NET see also:

11 comments:

Anonymous said...

I don't think this is a very good idea. An interface is not just a couple of methods with the right signature - it's a contract, it specifies usage patterns and expected behavior that clients can rely on - a couple of methods that happen to have the right names would be a lot more fragile.

However, there is a (IMO) more elegant option: type classes, as in Haskell type classes. The idea is similar: you specify a "type class", in your example "Closeable":
// purely fictional C# syntax
typeclass Closeable
{
void Close();
}

And, in a addition to that, you provide an implementation, that can be in a different module than the typeclass and the class declaration
implementation System.Windows.Forms.Form : Closeable
{
void Close()
{
DialogResult = DialogResult.Ok;
}
}

This way, clients could rely on a correct implemntation if a type class is implemented. It also helps you get rid of lots of Adapter-classes and parallel hierarchies in traditional OO designs.

Kirill Osenkov said...

So you're sceptical about duck typing in C#? Because one might inadvertently make random classes implement your interface? I agree.

Instead, you propose explicit mapping "I want class A to implement interface B and here's how". This mapping leaves both class A and interface B untouched, just like Haskell's type classes leave existing types untouched - they only specify the mapping: how a type implements an interface. Did I get it correctly?

Sounds like a good idea to me. I'm also a little bit cautious about duck typing in strongly typed languages - you might shoot yourself in the foot by accidentally allowing types to make false promises about their semantics.

One more thing: you mention parallel hierarchies. I'm very interested in designing parallel class hierarchies and you seem to have some experience in that area. I'd love to hear anything about it and the Bridge pattern...

Anyway, thanks for the great comment!

Kirill Osenkov said...

But then again - how would such a mapping differ from the plain vanilla Adapter pattern? Apart from the syntax, the intent would still be the same: we want an existing class (which we can't change) to implement an additional interface.

A problem with Adapter is that it is not referentially transparent - an adapter and its target object are different objects, which makes the Adapter in many cases useless.

Anonymous said...

But then again - how would such a mapping differ from the plain vanilla Adapter pattern? Apart from the syntax, the intent would still be the same: we want an existing class (which we can't change) to implement an additional interface.

Yes, you do have a point: Haskell-Like typeclasses aren't "orthogonal" to the C# type system; They're elegant in Haskell, because they're the only way to implement an "interface" there, but in the C# type system they would just add a second way to do something that can already be done. (Unless of course one would remove "classic" interface implementations from the language in favor of a type-class like system, but I don't think that's likely...) On the other hand, the same thing can be said about many language elements (e.g. events vs. observer pattern), so the question probably is how intuitive people would find such a feature.

To parallel hierarchies: the idea is really quite simple, you can concentrate more on what an object is supposed to do and less on where it fits in a hierarchy, because the hierarchy can be added later, and different clients can use different type class hierarchies to interface with the same objects. Of course that can be acchieved with adapters too, to some extent, but in my experience that's not common - adapters are usually seen more as last-resort hacks to bridge incompatibilities, I've never seen anyone build a clean design from scratch with many adapters bridging between different hierarchies in it.

Jacob said...

This post talks about the motivation behind requiring IEnumerable: http://blogs.msdn.com/madst/archive/2006/10/10/What-is-a-collection_3F00_.aspx

Spoiler: they determined that Add without IEnumerable is usually mathematical.

John Rusk said...

You comment about "extension interfaces" is interesting. I've also blogged about that idea, suggesting two other reasons why they might be useful: http://dotnet.agilekiwi.com/blog/2006/04/extension-interfaces.html

Kirill Osenkov said...

Thanks John. Very good considerations about Extension Interfaces. I'll make sure this feedback reaches the C# team.

Anonymous said...

The IENumerable is required to make the Duck Typing assumptions a little more exact.

Research turned out, that Add methods do not only mean Collection Add and the idea was to initialize collections...

Anonymous said...

I also posted on duck typing and collection initializers and why making semantic assumptions based on identifiers in a statically typed language is a bad idea (whoever decided that foreach should use GetEnumerator without the IEnumerable interface should be shot). I like that there are people who are considering possible solutions, instead of just ranting about the problem like I did :) I really hope that something like this makes it into the language, and soon.

http://commongenius.com/variable_irony/archive/2006/12/09/Collection-Initializers-and-Duck-Typing.aspx

Владимир said...

I recently found about using Structural Types in Scala. Although it's not directly related to your post, I think it is related, is a beautiful language feature, and is interesting to know.

Kirill Osenkov said...

Definitely!