API's that Suck

November 30, 2009

AddRange with Code Contracts

Filed under: Uncategorized — Grauenwolf @ 7:40 am

Lets start with our AddRange function from last time.

  1. <Extension()>
  2. Public Sub AddRange(Of T)(ByVal target As ICollection(Of T), ByVal source As IEnumerable(Of T))
  3.     For Each item In source
  4.         target.Add(item)
  5.     Next
  6. End Sub

Functionally this works, but what happens if target or source is null? You get a NullReferenceException without any clue as to which argument is messed up.

  1. If target Is Nothing Then Throw New ArgumentNullException("target")
  2. If source Is Nothing Then Throw New ArgumentNullException("source")

And of course you don’t want to try adding items to a read-only collection.

  1. If target.IsReadOnly Then Throw New ArgumentException("target cannot be a read-only collection")

So far so good. But those rules are a little soft and there is nothing to enforce them at compile time. To fix that, we can add some Code Contracts support. By enabling Code Contracts, developers using Team System will get compiler warnings when they try to break a rule. (Developers not using Team System will just get the usual runtime exceptions.) So now we have this:

  1. <Extension()>
  2. Public Sub AddRange(Of T)(ByVal target As ICollection(Of T), ByVal source As IEnumerable(Of T))
  3.     If target Is Nothing Then Throw New ArgumentNullException("target")
  4.     If source Is Nothing Then Throw New ArgumentNullException("source")
  5.     If target.IsReadOnly Then Throw New ArgumentException("target cannot be a read-only collection")
  6.     Contract.EndContractBlock()
  7.  
  8.     For Each item In source
  9.         target.Add(item)
  10.     Next
  11. End Sub

Right now we are making three demands: Source cannot be null, target cannot be null, and target cannot be read-only. But we aren’t making any promises, this code could literally do anything and still obey the contract. Wouldn’t it be nice if we could promise something, like say the target.Count would be incremented correctly? Such as example might look like this.

  1. <Extension()>
  2. Public Sub AddRange(Of T)(ByVal target As ICollection(Of T), ByVal source As ICollection(Of T))
  3.     If target Is Nothing Then Throw New ArgumentNullException("target")
  4.     If source Is Nothing Then Throw New ArgumentNullException("source")
  5.     If target.IsReadOnly Then Throw New ArgumentException("target cannot be a read-only collection")
  6.     Contract.Ensures(target.Count = Contract.OldValue(target.Count) + source.Count)
  7.  
  8.     For Each item In source
  9.         target.Add(item)
  10.     Next
  11. End Sub

In order to make this promise the source cannot just be an enumerable, it must be a collection with a pre-defined size. Looks good, too bad it doesn’t work. According to the compiler, line 6 cannot be proven. It appears, though I’m not certain, that we currently don’t have a contract that says “For all ICollection<T>, calling Add will increment Count by 1.” And without that contract, our code can’t make any promises.

Design by contract is hard. Retrofitting design by contract onto a system that never before had it is downright painful.

Advertisements

Why do I have to loops just to add to a collection?

Filed under: Uncategorized — Grauenwolf @ 6:55 am

Lets says you have a collection of strings representing email addresses in a MailAddressCollection. You want to add them to another MailAddressCollection you have to loop through them one item at a time.

  1. Dim source As New MailAddressCollection From {{"a@test.com"}, {"b@test.com"}, {"c@test.com"}}
  2. Dim target As New MailAddressCollection From {{"x@test.com"}, {"y@test.com"}}
  3. For Each item In source
  4.     target.Add(item)
  5. Next

Why doesn’t MailAddressCollection have an AddRange function like a sensible collection? Well, probably because there is nothing in the collection contract that says that it should. ICollection<T> only requires that an Add method be defined. With extension methods, that should be enough. Just hook into a generic AddRange method defined as such:

  1. Public Sub AddRange(Of T)(ByVal target As ICollection(Of T), ByVal source As IEnumerable(Of T))
  2.     For Each item In source
  3.         target.Add(item)
  4.     Next
  5. End Sub

Now when you call it, you get a nice and clean one-liner. And not just for MailAddressCollection, but for every class that implements ICollection<T>. Alas it doesn’t work on older collections that only implement ICollection, as that version doesn’t have an Add method.

  1. Dim source As New MailAddressCollection From {{"a@test.com"}, {"b@test.com"}, {"c@test.com"}}
  2. Dim target As New MailAddressCollection From {{"x@test.com"}, {"y@test.com"}}
  3. target.AddRange(source)

Ok, so what’s all this belly-aching about? Somehow this obvious little timesaver is missing from the numerous extension methods we got when LINQ was released.

November 29, 2009

Functional Purity, IEnumerators, and IEnumerables

Filed under: Uncategorized — Grauenwolf @ 3:05 am

I’ve been playing with Code Contracts and something about purity keeps coming up. In terms of side effects, what exactly is the guarantee offered by IEnumerable and IEnumerator?

IEnumerators are, by the very nature, impure objects.  Being more like the index variable in a loop than real objects, they should be used once and discarded. ILists, on the other hand, are generally durable objects. You can iterate through them as often as you like with no ill effects. So our first two rules are clear. Any function that takes an IEnumerator is impure while any that takes an IList is, assuming read-only access, pure. IEnumerable is the tricky case.  There is nothing in the contract that says GetEnumerator has to be free from side-effects, but off hand I can’t think of anything where it would be a problem.

This is important for API design because it changes what signatures need to be exposed. If you can’t trust GetEnumerator to be pure, then any classes that accept IEnumerator should also accept arguments of type IList.

Blog at WordPress.com.