Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I know of atleast one instance in C# where something like this happens. The IEnumberable<T>.Count() extension sees if the input is actually a ICollection and uses the count on that to get the count directly, rather than iterating over it.

I remember writing extension methods in C# for IEnumerable<T>, which would of course take in an IEnumerable<T>, but saw if the actual input was a ICollection<T>, IList<T> etc to optimize the operations.



This is not the same thing. The problem with the Go implementation is essentially that the method is semantically different from the expected method.

Closing a stream is semantically different from not closing a stream. It just is. The Go method was just wrong.

The problem was not the type introspection. There's nothing wrong with us attempting to do optimization for ICollection`1 or the rest of the collection interfaces because we have exactly the same semantic effect.

If you use different methods to achieve the same semantics, that's an implementation detail. If you use any methods to produce different semantics, that's a bug.


> This is not the same thing. The problem with the Go implementation is essentially that the method is semantically different from the expected method.

No-one is arguing about the Close() case. However, the io.Copy() case is comparable to the IEnumerable<T>.Count() implementation.


Unless your WriterTo method does something different than the caller assumes it's going to do.

Unless the special functions are predefined by the language (e.g. Python's __str__, __len__, __iter__, etc.) or specified in an interface that the class explicitly inherits from, making assumptions that the language developers and I meant the same thing is careless at best and dangerous at worst.


That's morally wrong, but at least the language provides an alternative: you could provide overloads for the function, and so it would be clear that the Count() on an ICollection was a different method from the Count() on an IEnumerable (at least, I assume you can - if C# extension methods make overloading impossible then this is a Bad Thing and should be fixed. (But even then, a problem with extension methods is much less significant than a problem with all functions)). In Java we see this with e.g. Guava's ImmutableList.copyOf(...), which has several overloads taking different subinterfaces (iterable, collection, list, ...) so you can reasonably tell that the method will use all the details of its input.


The semantics of ICollection<T>.Count and IList<T>.Count are well defined and known to you at the time your write your extension method.

You don't know the semantics of a method of an arbitrary type you've been passed in just because it has the same name. Go plays fast and loose with this idea, but at least the interface type of the argument should document "I'm expecting these methods to have a particular semantics". If they don't, that's then your fault.


The Go dev has acknowledged that this is a bug, but also pointed out why something like this was done elsewhere ( io.Copy ), and the Count example was pointed at that.


I'm not a heavy C# dev, but I don't understand why your first paragraph would be so: isn't that the point of implementing an interface like IEnumerable<T> -- so that you can implement Count() in an optimized, specialized way that "gets the count directly"?


Some implementations of IEnumerable do have a Count property on them. He's specifically talking about the Count() extension method. Since this extension method applies to all IEnumerable<T> implementations, there may or may not be an optimized way of accessing the count, and this method would need to know about them. The code looks a little something like this (decompiled from the .NET framework, so the variable names are probably not true to what they're actually named in the real source)

https://gist.github.com/anonymous/7c2c65268feafa66fb02


Count() is an extension method for IEnumerable<T>. This means that Count() is a static method which takes an single argument of type IEnumerable<T> and ostensibly only depends on members defined by IEnumerable<T> to work (but manojlds points out that this isn't really the case). C# provides syntactic sugar to make calling a static extension method look like calling an instance method.

If you wanted to implement your own "Count" you would implement ICollection<T> which the IEnumerable<T> extension method would call into.


(I'm not a c# expert and I may be wrong) IEnumerable really only requires a

IEnumerator GetEnumerator()

method most of the real "meat" of ienumerable is in the static System.Linq.Enumerable class. You see c# doesn't have multiple inheritence or scala like traits or type classes but it does have a cool compiler hack called Extension methods(http://msdn.microsoft.com/en-us/library/vstudio/bb383977.asp...). You take a static class with static helper functions like you might make in java and you add a this keyword to the first argument then you can call it with a syntax that makes it look like it was a method on that class. For example something like

    public static class Enumerable
    {
        public static int Count<TSource>(this IEnumerable<TSource> source
        {
            int i=0;
            for(TSource t in source)
            {
                i++;
            }
            return i;
        )
    }
And if you use Extensions methods on an interface type you get all of the extension methods for free once you implement the minimal interface. One of the downsides is that you can only really define it in one place and can't override it and if you want even somewhat efficient type specialized versions you need to cast.

I believe in scala you can use traits in a similar manner for default implementation and specialize them for collections but traits are more complicated/powerfull feature.


This is not a compiler hack, there are quite a few languages out there that support this concept.

As for your Scala remark, extension methods in Scala are done via implicits.


I didn't mean hack in a derogatory way, I think its a fairly elegant solution in many ways just that to my knowledge it doesn't really change the static/dynamic type and is just a simple compiler transformation.

I should have mentioned that I know even less about scala then c#. I/m not quite sure how implicits are implemented/optimized but I'm pretty sure you can have inheritence/overiding traits as implicits.




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: