Issue with forward ranges which are reference types

Jonathan M Davis jmdavisProg at gmx.com
Tue Aug 16 23:41:03 PDT 2011


On Tuesday, August 16, 2011 23:20:14 Mehrdad wrote:
> On 8/16/2011 9:37 PM, Jesse Phillips wrote:
> > On Tue, 16 Aug 2011 21:17:31 -0700, Mehrdad wrote:
> >> On 8/16/2011 9:05 PM, Jonathan M Davis wrote:
> >>> Sorry that this is long, but it's very important IMHO, and I don't
> >>> know
> >>> how to make it much shorter and cover what it's supposed to cover.
> >>> 
> >>> Okay. Your typical forward range is either an array a struct which
> >>> is a
> >>> value type (that is, copying it creates an independent range which
> >>> points to the same elements and is not altered if the original range
> >>> is
> >>> altered - the elements that it points to aren't copied of
> >>> course).<snip>  Thoughts?
> >>> 
> >>> - Jonathan M Davis
> >> 
> >> Funny, I was also thinking about this recently.
> >> 
> >> The trouble is that that's not the only issue. There's also the issue
> >> with polymorphism -- i.e., InputRangeObject is pretty much *useless*
> >> right now because no function ever checks for it (AFAIK... am I
> >> wrong?).
> >> So if you pass a random-access range object as an InputRange, the
> >> callee
> >> will just assume it's an InputRange and would reject it. So you're
> >> forced to downcast every time, which is really tedious. Things don't
> >> "just work" anymore.
> > 
> > All of the range functions check for functionality, so if your random-
> > access range object contains, popFront, front, empty (which it is
> > required to to be random-access range) then it will be accepted as an
> > InputRange.
> 
> Right, but the problem is that none of this template business (e.g.
> isInputRange!T, hasLength!T, etc.) works if the input is an Object that
> implements InputRange.
> 
> For example, consider this:
> 
>      static Object getItems()
>      { return inputRangeObject([1, 2]); }
> 
>      Object collection = getItems();
>      if (collection.empty)  //Whoops...
>      {
>          ...
>      }
> 
> The caller has no idea what kind of range is returned by getItems(), but
> he still needs to be able to check whether it's empty.
> 
> How can he figure this out? He would be forced to cast (which is by
> itself a pretty bad option), but what can he cast the object to?
> InputRange!Object doesn't work because it could be an InputRange!string
> or something. There's really NO way (that I know of) for the caller to
> test and see if the collection is an input range, unless he knows the
> type -- which runs counter to what I've seen in other OOP languages (C#,
> Java).
> 
> Hope that makes sense...

If you're dealing with a reference for a type that implements the functions 
for input range or forward range or whatever, then it's not an issue. It'll 
work with functions that require those range types. If you're dealing with a 
reference that doesn't implement the appropriate functions, then it isn't even 
if the actual type does. You could cast to the actual type and use that, but 
that pretty much assumes that you know the actual type - or if you don't you 
end up having to do something like

auto ir = cast(InputRange)obj;

if(ir)
    //call range func
else
   //do whatever you do if you can't call the range func

However, that OO _normally_ works is that you use a reference which is the 
type that you want to treat the object as. So, all of the code using that 
reference only deals with functionality that that reference type has. You 
don't usually cast it to other types to try and do other stuff. So, if a 
function needs the InputRange class/interface, then that's the reference that 
you use for that particular variable, and then whatever you assign it to (from 
a function parameter or a return function or whatever) is a type which derives 
from or implements InputRange. And it all works just fine.

It's usually _bad_ OO to be casting between object types. In particular, 
actually using the base Object class is usually a _bad_ idea. Some languages 
do that for their containers because they lack proper templates, but then you 
have to worry about casting the objects to the correct type when you get them 
out. It can work, but it's not a great idea. Java and C# used be like that, 
but when they added generics, they made it so that only the internal 
implementation works that way. The generics take care of keeping track of the 
actual type of the objects in the container and you don't have to cast anymore 
(though the casts still occur underneath the hood). So, that improves the 
situation considerably.

But regardless, good OO design does not usually require casting from a base 
class or interface to a derived class. So, the issue that you're describing 
just doesn't happen in good OO code.

- Jonathan M Davis


More information about the Digitalmars-d mailing list