March 07, 2005
Regan Heath wrote:
> int[char[]] array;
> int value;
> 
> array["bob"] = 5;
> if ("fred" in array) {
>    value = array["fred"];
> }
> 
> but, now we're doing a double lookup.

Can't compiler handle this right? It does many nice tricks already ... .

-- 
Dawid Ciężarkiewicz | arael
jid: arael@fov.pl
March 07, 2005
Doesn't seem like a hard thing to do to me.  The compiler just needs to amend my code to:
import std.stdio;

class C {
  void foo(C c) {
    writefln("C");
  }
}

class B: C {
  void foo(C c) {
    if (cast(B)c) {
      this.foo(cast(B)c);
    } else {
      super.foo(c);
    }
  }

  void foo(B b) {
    writefln("B");
  }
}

void main() {
        C b = new B();
        b.foo(b);
}

I'm not familiar about this argument.  What advantages are there to *not* doing this?  I can't think of any, whereas the advantages I see (and have used in Java) are many.

John Demme

Ben Hinkle wrote:
> "John Demme" <me@teqdruid.com> wrote in message news:d0i1rd$2t1f$1@digitaldaemon.com...
> 
>>I thought that one needed to manually override the method in any situation where the child class's method's parameter was a child of the parent's method's parameter... ie:
>>import std.stdio;
>>
>>class C {
>>  void foo(C c) {
>>    writefln("C");
>>  }
>>}
>>
>>class B: C {
>>  void foo(B b) {
>>    writefln("B");
>>  }
>>}
>>
>>void main() {
>>        C b = new B();
>>        b.foo(b);
>>}
>>
>>Prints "C", when I think it should print "B".  Is this something that D will do eventually, or no?
> 
> 
> It would be very hard to do with the standard vtable mechanism (I think). The call b.foo(b) compiles into basically "take the vtable for b and call the first function in the C section" since the type of b is C and the first function of C is foo. To implement what you suggest using that same mechanism the function B.foo would have to be stored in the slot for C.foo - which is illegal since users can pass *any* object of type C to C.foo but not B.foo (if I have that written straight). So I would imagine some sort of double dispatching would be needed to implement what you suggest.
> 
> 
>>John
>>
>>Walter wrote:
>>
>>>"Matthew" <admin.hat@stlsoft.dot.org> wrote in message
>>>news:d0hbh4$250h$1@digitaldaemon.com...
>>>
>>>
>>>>IMO, 3. is the only sane solution. It's the same problem with the stinky
>>>
>>>crap of opCmp. Look at the rubbish I've had to
>>>
>>>
>>>>write to get comparable Fields:
>>>
>>>
>>>
>>>You only need opCmp(Object) if you are using builtin operations like .sort.
>>>If you're going to use < or > operators with Fields, then opCmp(Field) is
>>>all you need.
>>>
> 
> 
March 08, 2005
> I'm not familiar about this argument.  What advantages are there to *not* doing this?  I can't think of any, whereas the advantages I see (and have used in Java) are many.

This Java code also outputs "C":

class C
{
  void foo(C c)
  {
    System.out.println("C");
  }
}

class B extends C
{
  void foo(B b)
  {
    System.out.println("B");
  }
}

public class Test
{
  public static void main(String[] args)
  {
    C b = new B();
    b.foo(b);
  }
}

Or am I missing something?


xs0
March 08, 2005
On Mon, 07 Mar 2005 18:54:05 -0500, John Demme wrote:

> Doesn't seem like a hard thing to do to me.  The compiler just needs to
> amend my code to:
> import std.stdio;
> 
> class C {
>    void foo(C c) {
>      writefln("C");
>    }
> }
> 
> class B: C {
>    void foo(C c) {
>      if (cast(B)c) {
>        this.foo(cast(B)c);
>      } else {
>        super.foo(c);
>      }
>    }
> 
>    void foo(B b) {
>      writefln("B");
>    }
> }
> 
> void main() {
>          C b = new B();
>          b.foo(b);
> }
> 
> I'm not familiar about this argument.  What advantages are there to *not* doing this?  I can't think of any, whereas the advantages I see (and have used in Java) are many.

What I don't get is, if you know at the time you are writing the code that you want to create an instance of B, why are you declaring it to be a C?

The way I read (note: *read*) the code in the main routine, is that you are declaring that 'b' is an instance of a C class, but that you are creating a new B class instead. Then you are saying 'b.foo(b)' which reads that you are calling the 'foo' method in whatever class 'b' is (ie. a C), passing itself as a parameter.

In this reading, why would one expect the foo method in class B to be called? What is it I'm not understanding? Can you give a real example where this makes sense to do?

I would have thought that ...


 class TwoDImage {
    void foo(TwoDImage c) {
      writefln("C");
    }
 }

 class Circle: TwoDImage {

    void foo(Circle b) {
      writefln("B");
    }
 }

 void main() {
          Circle b = new TwoDImage();
          b.foo(b);
 }


would make more sense to do.

-- 
Derek
Melbourne, Australia
8/03/2005 11:06:45 AM
March 08, 2005
You are correct... please let me amend my previous statement to I have done *similar* things in Java... I was writing a Tree-based map, and used overriding and overloading... If may have been that an interface was being used, and the structure was different.  I don't recall the details.

John

xs0 wrote:
> 
>> I'm not familiar about this argument.  What advantages are there to *not* doing this?  I can't think of any, whereas the advantages I see (and have used in Java) are many.
> 
> 
> This Java code also outputs "C":
> 
> class C
> {
>   void foo(C c)
>   {
>     System.out.println("C");
>   }
> }
> 
> class B extends C
> {
>   void foo(B b)
>   {
>     System.out.println("B");
>   }
> }
> 
> public class Test
> {
>   public static void main(String[] args)
>   {
>     C b = new B();
>     b.foo(b);
>   }
> }
> 
> Or am I missing something?
> 
> 
> xs0
March 08, 2005
Here's a good example:
import std.stdio;

class TwoDImage {
  TwoDImage overlap(TwoDImage i) {
    //Returns the overlap of the two images.
    writefln("Potentially long calculation");
    return null;
  }
}

class Circle: TwoDImage {
  TwoDImage overlap(Circle c) {
    //Does the same thing as super.overlap,
    //But is able to calculate the overlap much more effeciently
    writefln("Trivial calculation");
    return null;
  }
}

void main() {
  Circle a = new Circle();
  Circle b = new Circle();
  PrintOverlap(a, b);
}

void PrintOverlap(TwoDImage a, TwoDImage b) {
  TwoDImage i = a.overlap(b);
  //Graphically print the overlap
}

The above prints "Potentially long calculation".
Since the PrintOverlap function cannot and should not know anything about Circle, letting Circle override and overload TwoDImage, is the only way to optimize this calculation.  Currently the only way to do is using a hack:
import std.stdio;

class TwoDImage {
  TwoDImage overlap(TwoDImage i) {
    //Returns the overlap of the two images.
    writefln("Potentially long calculation");
    return null;
  }
}

class Circle: TwoDImage {
  //HACK:
  override TwoDImage overlap(TwoDImage i) {
    if (cast(Circle)i)
      return this.overlap(cast(Circle)i);
    else
      return super.overlap(i);
  }
  TwoDImage overlap(Circle c) {
    //Does the same thing as super.overlap,
    //But is able to calculate the overlap much more effeciently
    writefln("Trivial calculation");
    return null;
  }
}

void main() {
  Circle a = new Circle();
  Circle b = new Circle();
  PrintOverlap(a, b);
}

void PrintOverlap(TwoDImage a, TwoDImage b) {
  TwoDImage i = a.overlap(b);
  //Graphically print the overlap
}

This makes a lot of sense to me.  I don't suppose I have everyone convinced? or more importantly, have Walter convinced?  It doesn't seem like a hard thing for the compiler to do, as all it needs to do is either add the method that directly overrides, or add a small bit to the beginning of an already existing one. ie, if Circle.overlap(TwoDImage i) was
  override TwoDImage overlap(TwoDImage i) {
	//Do some other, but not as optimized calculation
      return null;
  }
The other Circle.overlap would be called as well.

Comments? Questions? Suggestions? Anecdotes? Potions?

John Demme

Derek Parnell wrote:
> On Mon, 07 Mar 2005 18:54:05 -0500, John Demme wrote:
> 
> 
>>Doesn't seem like a hard thing to do to me.  The compiler just needs to amend my code to:
>>import std.stdio;
>>
>>class C {
>>   void foo(C c) {
>>     writefln("C");
>>   }
>>}
>>
>>class B: C {
>>   void foo(C c) {
>>     if (cast(B)c) {
>>       this.foo(cast(B)c);
>>     } else {
>>       super.foo(c);
>>     }
>>   }
>>
>>   void foo(B b) {
>>     writefln("B");
>>   }
>>}
>>
>>void main() {
>>         C b = new B();
>>         b.foo(b);
>>}
>>
>>I'm not familiar about this argument.  What advantages are there to *not* doing this?  I can't think of any, whereas the advantages I see (and have used in Java) are many.
> 
> 
> What I don't get is, if you know at the time you are writing the code that
> you want to create an instance of B, why are you declaring it to be a C?
> 
> The way I read (note: *read*) the code in the main routine, is that you are
> declaring that 'b' is an instance of a C class, but that you are creating a
> new B class instead. Then you are saying 'b.foo(b)' which reads that you
> are calling the 'foo' method in whatever class 'b' is (ie. a C), passing
> itself as a parameter.
> 
> In this reading, why would one expect the foo method in class B to be
> called? What is it I'm not understanding? Can you give a real example where
> this makes sense to do?
> 
> I would have thought that ...
> 
> 
>  class TwoDImage {
>     void foo(TwoDImage c) {
>       writefln("C");
>     }
>  }
>   class Circle: TwoDImage {
> 
>     void foo(Circle b) {
>       writefln("B");
>     }
>  }
>   void main() {
>           Circle b = new TwoDImage();
>           b.foo(b);
>  }
> 
> 
> would make more sense to do.
> 
March 08, 2005
Ben Hinkle wrote:

> My vote would be for 3 since looking up a key that isn't in the array is the AA
> equivalent to indexing a dynamic array outside of its bounds. Since indexing out
> of bounds throws an exception (in debug mode) so should invalid key lookup. 

I disagree. An out of bounds array index is an exceptional case because the size of the array is, usually, a known quantity - i.e. all members of the set of numbers from 0...n are valid, and n *must* be known during the allocation of the array. Unassigned keys in a hashmap are not an exceptional case because for hashmaps there is no valid set of keys (in the general case) that is guaranteed to be known at runtime. Besides which, hasmaps are quite often used as a form of registry where the case of unassigned keys is common.

I'm in the camp that advocates throwing exceptions only in exceptional circumstances, and not as a general catch-all failure signal. If you imagine a world where exceptions are never caught, what are some failure cases which, the majority of the time, would be an acceptable reason to terminate the program? Any answers you come up with are good candidated to define as 'exceptional'. In my book, hashmap lookup failures do not pass that test.

Just to push my point further, there is not one case where an out of bounds array index is acceptable. And in every case the developer would certainly want to know if an attempt to index an out of bounds array were made. In our imaginary world of uncaught exceptions, this is a great time to terminate the program. Yet there are a large number of hashmap use cases where you would not want your application to terminate because a particular key wasn't assigned. Imagine a plugin system that loads plugins on demand. Of course the user should be notified that a particular plugin isn't available to load - but does that pass the test as an acceptable reason to terminate the application? Not in the genral case, it is in the application specific domain and therefore it is not exceptional. And when a failure case is not exceptional, then a testable value (a boolean or null, for example) should be returned instead so that the application can decide whether or not to terminate.

True, you can choose to catch an exception and do nothing with it, but in my book that is poor design, particularly in a language like D which has no mechanism that requires you to catch exceptions, or even declare that a particular method or operation throws one. It's too easy to overlook an exception here or there. If my production software terminates, it had better be due to an exceptional case.
March 08, 2005
On Tue, 08 Mar 2005 11:09:05 +0900, Mike Parker <aldacron71@yahoo.com> wrote:
> Ben Hinkle wrote:
>
>> My vote would be for 3 since looking up a key that isn't in the array is the AA
>> equivalent to indexing a dynamic array outside of its bounds. Since indexing out
>> of bounds throws an exception (in debug mode) so should invalid key lookup.
>
> I disagree. An out of bounds array index is an exceptional case because the size of the array is, usually, a known quantity - i.e. all members of the set of numbers from 0...n are valid, and n *must* be known during the allocation of the array. Unassigned keys in a hashmap are not an exceptional case because for hashmaps there is no valid set of keys (in the general case) that is guaranteed to be known at runtime. Besides which, hasmaps are quite often used as a form of registry where the case of unassigned keys is common.
>
> I'm in the camp that advocates throwing exceptions only in exceptional circumstances, and not as a general catch-all failure signal. If you imagine a world where exceptions are never caught, what are some failure cases which, the majority of the time, would be an acceptable reason to terminate the program? Any answers you come up with are good candidated to define as 'exceptional'. In my book, hashmap lookup failures do not pass that test.
>
> Just to push my point further, there is not one case where an out of bounds array index is acceptable. And in every case the developer would certainly want to know if an attempt to index an out of bounds array were made. In our imaginary world of uncaught exceptions, this is a great time to terminate the program. Yet there are a large number of hashmap use cases where you would not want your application to terminate because a particular key wasn't assigned. Imagine a plugin system that loads plugins on demand. Of course the user should be notified that a particular plugin isn't available to load - but does that pass the test as an acceptable reason to terminate the application? Not in the genral case, it is in the application specific domain and therefore it is not exceptional. And when a failure case is not exceptional, then a testable value (a boolean or null, for example) should be returned instead so that the application can decide whether or not to terminate.
>
> True, you can choose to catch an exception and do nothing with it, but in my book that is poor design, particularly in a language like D which has no mechanism that requires you to catch exceptions, or even declare that a particular method or operation throws one. It's too easy to overlook an exception here or there. If my production software terminates, it had better be due to an exceptional case.

While I tend to agree with you here.. I think that if there is a way to query and retrieve the value i.e.
  bool contains(KeyType key, out ValueType value);

Then it is acceptable to throw an exception on:
  array["key"]

because, given the former, the latter makes an assumption that the value exists. So, when it doesn't exist that assumption is false, and that is an exceptional case.

Regan
March 08, 2005
Hear Hear!

It just doesn't make sense to wrap a try/catch around hashmap lookups, where a missing entry is perfectly legitimate. The performance hit would be even less attractive.



In article <d0j1fc$ueh$1@digitaldaemon.com>, Mike Parker says...
>
>Ben Hinkle wrote:
>
>> My vote would be for 3 since looking up a key that isn't in the array is the AA equivalent to indexing a dynamic array outside of its bounds. Since indexing out of bounds throws an exception (in debug mode) so should invalid key lookup.
>
>I disagree. An out of bounds array index is an exceptional case because the size of the array is, usually, a known quantity - i.e. all members of the set of numbers from 0...n are valid, and n *must* be known during the allocation of the array. Unassigned keys in a hashmap are not an exceptional case because for hashmaps there is no valid set of keys (in the general case) that is guaranteed to be known at runtime. Besides which, hasmaps are quite often used as a form of registry where the case of unassigned keys is common.
>
>I'm in the camp that advocates throwing exceptions only in exceptional circumstances, and not as a general catch-all failure signal. If you imagine a world where exceptions are never caught, what are some failure cases which, the majority of the time, would be an acceptable reason to terminate the program? Any answers you come up with are good candidated to define as 'exceptional'. In my book, hashmap lookup failures do not pass that test.
>
>Just to push my point further, there is not one case where an out of bounds array index is acceptable. And in every case the developer would certainly want to know if an attempt to index an out of bounds array were made. In our imaginary world of uncaught exceptions, this is a great time to terminate the program. Yet there are a large number of hashmap use cases where you would not want your application to terminate because a particular key wasn't assigned. Imagine a plugin system that loads plugins on demand. Of course the user should be notified that a particular plugin isn't available to load - but does that pass the test as an acceptable reason to terminate the application? Not in the genral case, it is in the application specific domain and therefore it is not exceptional. And when a failure case is not exceptional, then a testable value (a boolean or null, for example) should be returned instead so that the application can decide whether or not to terminate.
>
>True, you can choose to catch an exception and do nothing with it, but in my book that is poor design, particularly in a language like D which has no mechanism that requires you to catch exceptions, or even declare that a particular method or operation throws one. It's too easy to overlook an exception here or there. If my production software terminates, it had better be due to an exceptional case.



March 08, 2005
"Mike Parker" <aldacron71@yahoo.com> wrote in message news:d0j1fc$ueh$1@digitaldaemon.com...
> Ben Hinkle wrote:
>
>> My vote would be for 3 since looking up a key that isn't in the array
>> is the AA
>> equivalent to indexing a dynamic array outside of its bounds. Since
>> indexing out
>> of bounds throws an exception (in debug mode) so should invalid key
>> lookup.
>
> I disagree. An out of bounds array index is an exceptional case because the size of the array is, usually, a known quantity - i.e. all members of the set of numbers from 0...n are valid, and n *must* be known during the allocation of the array. Unassigned keys in a hashmap are not an exceptional case because for hashmaps there is no valid set of keys (in the general case) that is guaranteed to be known at runtime. Besides which, hasmaps are quite often used as a form of registry where the case of unassigned keys is common.

Well, we must use associative containers in much different ways. What you've described simply does not tally with my experience and use patterns.

> I'm in the camp that advocates throwing exceptions only in exceptional circumstances, and not as a general catch-all failure signal.

Same here. And trying to get a value out of an associative container that does not exist is exactly that, exceptional. (Or it certainly should be.)

> If you imagine a world where exceptions are never caught, what are some failure cases which, the majority of the time, would be an acceptable reason to terminate the program?

Anything that violates a program's design should result in its termination.

> Any answers you come up with are good candidated to define as 'exceptional'.

Absolute nonsense.

I'm bugging out now, because I fear we're so far apart that there's no point wasting our breaths. :-(