last day (15 days later) » 

11:55 PM
0
A: When is type testing OK?

Telastyn When is type checking OK? Never. By having the behavior associated with that type in some other function, you're violating the Open Closed Principle, because you're free to modify the existing behavior of the type by changing the is clause, or (in some languages, or depending on the scena...

 
There is one minor caveat: implementing algebraic data types in languages that don't support them. However, the use of inheritance and typechecking is purely a hacky implementation detail there; the intent isn't to introduce subtypes, but to categorize values. I bring it up only because ADTs are useful and never is a very strong qualifier, but otherwise I fully agree; instanceof leaks implementation details and breaks abstraction.
 
"Never" is a word I don't really like in such a context, especially when it contradicts what you write below.
 
If by "never" you actually mean "sometimes," then you're right.
 
@DocBrown & Caleb - It's always a code smell, and sometimes we need to write stinky code. That doesn't make it okay.
 
OK means allowable, acceptable, etc., but not necessarily ideal or optimal. Contrast with not OK: if something is not OK then you shouldn't do it at all. As you indicate, the need to check the type of something can be an indication of deeper problems in the code, but there are times when it's the most expedient, least bad option, and in such situations it's obviously OK to use the tools at your disposal. (If it were easy to avoid them in all situations, they probably wouldn't be there in the first place.) The question boils down identifying those situations, and never doesn't help.
 
11:55 PM
How else would you model the concept of "All X's can use a general-purpose method Y to do some operation, but some X's include a special-purpose method Z that can do it better". Either all X's have to implement an interface that includes Z (even if some don't actually support that method), or else one has to test which objects do support it. Personally, from a conceptual standpoint, I prefer the former approach, but SOLID principles and .NET's lack of support for default interface implementations argue against it.
 
I'm tempted to give you a +1 here for "most controversial answer." (+5/-4 at the time of this comment)
 
@Telastyn: How should one write a method to efficiently retrieve the last item from an IEnumerable<T>, if not by testing the type? Some types allow ways of retrieving the last item which are orders of magnitude faster than enumeration; should one forgo such speedups in the name of "code purity"?
 
@supercat - By virtual dispatch in an ideal world. That rather is the entire point of subtyping - have some variable implementation of a common behavior. Doing type testing yourself is circumventing the built in tools (and optimizations) (and safety checks) that the language and compiler provide for you.
 
@Telastyn: Using virtual dispatch to answer such queries would require that every implementation of IEnumerable<T> provide support for all such queries, whether or not they could do anything better than a static method which accessed them only as IEnumerable<T>. Personally, I would tend to favor such a design, Interface-Segregation Principle notwithstanding, but for the fact that .NET would make such a thing really painful to implement. Is there any way to use virtual dispatch without either using run-time type checking or having "kitchen-sink" base-level interfaces?
 
@supercat: One could argue that if you're trying to get the "last" element of an IEnumerable<T>, you're Doing It Wrong. The sequence might not even be finite. And you shouldn't care. If you do, then use a more specific type from the start.
 
11:55 PM
@supercat - you can do trickery with dynamic, but you can't get a nice .Last implementation then. In .NET, you're likely stuck with hackery since there's no default virtual implementation that does the naive thing and since there's no dynamic dispatch on extension methods.
An excellent point @cHao. C++ has RandomAccessIterator and the such to differentiate the capabilities of collections, but then usually uses templates to fake out dynamic dispatch on the capabilities.
 
@cHao: As a general principle, methods should demand the least specific type possible; given that collections whose last item can be found efficiently have no common ancestral type, what type could a method demand, other than IEnumerable<T>, without needlessly restricting the circumstances where it may be employed?
 
@supercat: A methods should demand the least specific type possible that satisfies its requirements. IEnumerable<T> doesn't promise a "last" element exists. If your method needs such an element, it should be requiring a type that guarantees the existence of one. And that type's subtypes can then provide efficient implementations of the "Last" method.
 
@cHao: Is there any type to which it is possible to cast every IEnumerable<T> instance that has a last item which can be found efficiently, that guarantees the existence of such a last item? I would posit that it's more important that a method accept every object instance that has a last item, than that its type guarantee the existence of such an item.
 
@supercat: How about ICollection<T> or IList<T>?
 
@cHao: A List<Cat> would satisfy IEnumerable<Animal>, and could efficiently return its last item, but would not satisfy ICollection<Animal>. A iterator which yields four instances of Cat and exits would also satisfy IEnumerable<Animal> and could also quickly find the last item, but it doesn't implement ICollection<Animal> either. Incidentally, some instances of collection types that implement IList<T> won't have a last item, so implementation of IList<T> is neither necessary nor sufficient to guarantee that it will be possible to efficiently return the last item.
 
11:55 PM
@supercat: ICollection<T> defines a Count property, which means there must be a count -- ie: some knowable number of items. And IList<T> is a subtype of ICollection<T>, and ICollection<T> is a subtype of IEnumerable<T>.
 
@cHao: A List<Cat> implements ICollection<Cat>, and a List<Dog> implements ICollection<Dog>, but while both implement IEnumerable<Animal>, neither implements ICollection<Animal>.
 
@supercat: Neither directly implements IEnumerable<Animal> either; they work due to the covariance of the type parameter. And that covariance extends through IList and ICollection all the way back to IEnumerable. I don't see it skipping a generation.
@supercat: Ahh...just noticed that the other interfaces don't say out T. :P However, IReadOnlyCollection and IReadOnlyList, though, do. (And both are probably a better fit anyway, since you're not modifying the list.)
 
@cHao: There are a lot of types which semantically could implement IReadOnlyCollection<T>, but don't [including all legacy IList<T> implementations that predate IReadOnlyCollection]. If a method would be usable with such types, why shouldn't it be?
 
@supercat: Because if you say you take an IEnumerable<T>, and are not more specific, you're saying you can take any IEnumerable<T>. That is clearly not the case. And the second someone starts getting fancy with your method and it gets into an infinite loop trying to find the last element of an infinite sequence, your code will be entirely to blame.
 
@cHao: The fact that a method takes a parameter of a particular type does not imply that it will accept all instances of that type and its derivatives. It is entirely right and proper for methods to specify that object instances must abide by other criteria as well. For example, if one has a List<string> and passes its Sort an IComparable<T>, it is necessary not only that that comparator return integer values when given pairs of strings, but also that it does so in such a fashion as to yield a consistent ranking, at least for the particular strings in the list.
 

  last day (15 days later) »