views:

245

answers:

2

The .NET Framework is adding an ISet<T> interface with the 4.0 release. In the same release, F# is being added as a first-class language. F# provides an immutable Set<'T> class.

It would seem logical to me that the immutable set provided would implement the ISet<T> interface, but it doesn't. Does anyone know why?

My guess is that they didn't want to implement an interface intended to be mutable, but I don't think this explanation holds up. After all, their Map<'Key, 'Value> class implements IDictionary, which is mutable. And there are examples elsewhere in the framework of classes implementing interfaces that are only partially appropriate.

My other thought is that ISet<T> is new, so maybe they didn't get around to it. But that seems kind of thin.

Does the fact that ISet<T> is generic (v. IDictionary, which is not) have anything to do with it?

Any thoughts on the matter would be appreciated.

+5  A: 

Since no one else is jumping in, I'll add my speculation. First of all, the majority of the IDictionary<'k,'v> interface still makes sense for an immutable dictionary; it's really only adding or removing individual elements that won't work. Secondly, there's a decent amount of code that has already been written which relies on IDictionary, so being able to use F# values with that code is a nice plus.

On the other hand, several of ISet<'t>'s methods require mutation, including not just adding individual elements, but also several set mutating operators such as union and intersection. Additionally, many set use cases are already subsumed by the IEnumerable<'t> interface - I think that it will be relatively rare for people to rely on ISet<'t> implementations for immutable set based code.

I don't think that genericity has anything to do with it - note that despite the MSDN documentation, F# maps implement the generic IDictionary interface, not the non-generic one.

kvb
This sounds plausible. Still, it's frustrating if true. It seems like a mistake to me to include all those interface methods that imply the mutability of the implementing class. (Likewise, I think it would be a mistake to include methods implying the class should be _immutable_.) It would be nice if `ISet<T>` included only "read" members. I think the interface as-is is too opinionated, and now I _still_ don't really have a way to communicate simply that "this is a collection of unique items." (At least so far as F# interop is concerned.)
Sean Devlin
Looks like not quite the majority: by my count, only five of the 11 proper members of `ISet<T>` require mutation. If you include inherited members from `ICollection<T>`, it becomes eight of 18. (And of these, at least `void Add(T item)` will probably be implemented explicitly.)
Sean Devlin
I was thinking about writing a similar answer, but then I looked at the `ISet<T>` interface and found out that it has quite a few operations that can still be implemented (namely `SetEquals`, `IsSubsetOf`, `IsSupersetOf`, `Overlaps`, `IsProperSubsetOf`, `IsProperSupersetOf`), maybe it's fewer than `IDictionary`, but still quite a few... Another reasoning may be that there are no .NET methods that would take `ISet<T>`, so there is no application-driven reason for implementing it (I guess there may be some good use case for `IDictionary`).
Tomas Petricek
@Tomas That's true, but it seems to me that _not_ implementing it introduces some friction for anyone writing new code. For example, I want to define some interfaces that pass around sets (not to modify, just to query against). This adds friction to F# consumers of my code, so now it almost makes more sense just to fall back to `IEnumerable<T>` for my interface. Kind of a bummer, I was really excited about the addition of this interface to the framework. It seems like this will inhibit adoption a little bit.
Sean Devlin
@Sean: I agree, I guess that .NET BCL should really include a better set of interfaces to represent collections, sets, dictionaries and their immutable versions, etc. This is probably hard to do, given the .NET 1 legacy that we have to live with.
Tomas Petricek
@Sean - you're right, not quite a majority, I'll update my post.
kvb
+4  A: 

Offhand I don't think I was previously aware of ISet. (Actually I looked back through old email, and found one mention of BCL plans for it--from 2008--but that was it. So I think it wasn't on our radar.)

That said, F# strives to be source-compatible between its .NET 2.0 and .NET 4.0 bits, to the point of back-porting .NET 4.0 entities into FSharp.Core.dll version 2.0. For example, The 2.0 FSharp.Core contains System.Tuple, System.BigInteger, System.Threading.CancelationTokenSource (part of the async programming model), etc., and ISet would potentially be another bit of work to back-port (it's unclear to me if it would be 'necessary' to port it, though).

I'll file an issue to have a look, though it may be moot at this point in time.

Brian
That makes sense. I didn't realize there was a drive to keep F# source-compatible across frameworks. Just curious, but what is the reasoning behind this? Is the official stance that .NET 4.0 does not contain a "new" version of F#, but rather an official, first-class packaging of F# as it exists today?
Sean Devlin