www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Templates: generic "return null;"

reply "Chris" <wendlec tcd.ie> writes:
Is there a way I can make the return type in getAttribute 
generic? null does not work with numbers.

MyStruct(T) {
   T[T] attributes;
   // ....
   public auto getAttribute(T attr) {
       if (!(attr in attributes)) {
         return null; // Doesn't work for numbers!
       }
       return attributes[attr];
     }
}

void main() {
   auto myStr = MyStruct!int(0); // Error
}
Feb 03 2014
next sibling parent reply "Stanislav Blinov" <stanislav.blinov gmail.com> writes:
On Monday, 3 February 2014 at 10:25:19 UTC, Chris wrote:

   T[T] attributes;
   // ....
   public auto getAttribute(T attr) {
       if (!(attr in attributes)) {
         return null; // Doesn't work for numbers!
       }
       return attributes[attr];
     }
 }
One way would be to use std.typecons.Nullable(T) as a return type. Another would be to retink your design :) Note that for numbers, there's no distinct "does not exist" value (well, ok, we have NaN for floating point). Neither there is for structs. Generally such methods as your getAttribute either throw, or get a second optional parameter as hint on what to return when the element is not found in the dictionoary.
Feb 03 2014
parent reply "Chris" <wendlec tcd.ie> writes:
On Monday, 3 February 2014 at 10:32:58 UTC, Stanislav Blinov 
wrote:
 On Monday, 3 February 2014 at 10:25:19 UTC, Chris wrote:

  T[T] attributes;
  // ....
  public auto getAttribute(T attr) {
      if (!(attr in attributes)) {
        return null; // Doesn't work for numbers!
      }
      return attributes[attr];
    }
 }
One way would be to use std.typecons.Nullable(T) as a return type.
Thanks. I'll try this one.
 Another would be to retink your design :) Note that for 
 numbers, there's no distinct "does not exist" value (well, ok, 
 we have NaN for floating point). Neither there is for structs. 
 Generally such methods as your getAttribute either throw, or 
 get a second optional parameter as hint on what to return when 
 the element is not found in the dictionoary.
I'm reluctant to (over)use throw, because I think that throw should be the last resort when you cannot easily predict all the things that can go wrong. Simple requests should give simple answers. If the key doesn't exist it returns nothing. The problem above only exists because of generic types, else it would be either 'null' or for numbers -1 or something. But if you have a good point I've overlooked, you can convince me of throw. No ideology :)
Feb 03 2014
parent "Stanislav Blinov" <stanislav.blinov gmail.com> writes:
On Monday, 3 February 2014 at 10:55:23 UTC, Chris wrote:

 I'm reluctant to (over)use throw, because I think that throw 
 should be the last resort when you cannot easily predict all 
 the things that can go wrong. Simple requests should give 
 simple answers. If the key doesn't exist it returns nothing.
D has no concept of "nothing" in the language. What if your dictionary contains objects, and for some key K it *does* contain a null reference? How would you distinguish absence of value from some "invalid" value? Nullable can help with that, of course. But then again, what if your dictionary does contain other Nullables? :) Generally, inability to return value is an exceptional situation.
 The problem above only exists because of generic types, else it 
 would be either 'null' or for numbers -1 or something.
-1 is a number, just like 0 or 10 or 13142313. Same as above, at the call site you won't be able to tell if -1 indicates that there's no such key in the attributes dictionary or just that attribute happens to have a value of -1.
Feb 03 2014
prev sibling next sibling parent reply "Dicebot" <public dicebot.lv> writes:
You have forgot to mention what behavior you are actually trying 
to achieve ;) Common not-so-meaningful value is simply T.init , 
but there can be no such thing as generic sentinel.

If you need cheap and simple way to figure out that attribute was 
missing, change API to return value by out parameter and turn 
normal return value into boolean success flag.
Feb 03 2014
next sibling parent "Chris" <wendlec tcd.ie> writes:
On Monday, 3 February 2014 at 12:25:16 UTC, Dicebot wrote:
 You have forgot to mention what behavior you are actually 
 trying to achieve ;) Common not-so-meaningful value is simply 
 T.init , but there can be no such thing as generic sentinel.

 If you need cheap and simple way to figure out that attribute 
 was missing, change API to return value by out parameter and 
 turn normal return value into boolean success flag.
Thanks. T.init actually does the trick. The behavior: auto name = myStruct.getAttribute("name"); if (name == "bla") { // do something } else { // do something else } or (theoretically): auto second = myStruct.getAttribute(1.0); if (second > 1.5) { // do something } else { // do something else. } I haven't got a use case for the second example, but it might be handy for data analysis and I wanted to test how far you can go with templates. The reasoning behind it is that string name; try { name = myStruct.getAttribute("name"); } // ... is a bit awkward an OTT. I'd prefer to introduce hasAttribute("name") instead, if I want to be sure it exists. if (myStruct.hasAttribute("name)) name = myStruct.getAttribute("name");
Feb 03 2014
prev sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Dicebot:

 If you need cheap and simple way to figure out that attribute 
 was missing, change API to return value by out parameter and 
 turn normal return value into boolean success flag.
It's probably better to start using Nullable. Bye, bearophile
Feb 03 2014
next sibling parent reply "Chris" <wendlec tcd.ie> writes:
On Monday, 3 February 2014 at 14:10:35 UTC, bearophile wrote:
 Dicebot:

 If you need cheap and simple way to figure out that attribute 
 was missing, change API to return value by out parameter and 
 turn normal return value into boolean success flag.
It's probably better to start using Nullable. Bye, bearophile
Probably. I tried using Nullable, but it caused some problems when the attribute wasn't defined: core.exception.AssertError /usr/include/dmd/phobos/std/typecons.d(1233): Called `get' on null Nullable!int.
Feb 03 2014
parent reply "Dicebot" <public dicebot.lv> writes:
On Monday, 3 February 2014 at 14:17:11 UTC, Chris wrote:
 Probably. I tried using Nullable, but it caused some problems 
 when the attribute wasn't defined:

 core.exception.AssertError /usr/include/dmd/phobos/std/typecons.d(1233): 
 Called `get' on null Nullable!int.
This is intended. The very point of Nullable is to force you to handle `null` state before accessing actual payload.
Feb 03 2014
next sibling parent "Chris" <wendlec tcd.ie> writes:
On Monday, 3 February 2014 at 14:21:29 UTC, Dicebot wrote:
 On Monday, 3 February 2014 at 14:17:11 UTC, Chris wrote:
 Probably. I tried using Nullable, but it caused some problems 
 when the attribute wasn't defined:

 core.exception.AssertError /usr/include/dmd/phobos/std/typecons.d(1233): 
 Called `get' on null Nullable!int.
This is intended. The very point of Nullable is to force you to handle `null` state before accessing actual payload.
Aha, I see. So in my scenario it is useless.
Feb 03 2014
prev sibling parent "Dicebot" <public dicebot.lv> writes:
On Monday, 3 February 2014 at 14:21:29 UTC, Dicebot wrote:
 This is intended. The very point of Nullable is to force you to 
 handle `null` state before accessing actual payload.
P.S. for this very reason in my own implementation of Optional I provide only delegate access method: value.get( () => { /* handle 'empty' case */ }, value => { /* 'value' is legit data */ } );
Feb 03 2014
prev sibling parent "Dicebot" <public dicebot.lv> writes:
On Monday, 3 February 2014 at 14:10:35 UTC, bearophile wrote:
 Dicebot:

 If you need cheap and simple way to figure out that attribute 
 was missing, change API to return value by out parameter and 
 turn normal return value into boolean success flag.
It's probably better to start using Nullable. Bye, bearophile
Depends. If you control system as a whole, yes, it is better to build it on top of Nullable. If interfacing with external code is intended, it will just add more boilerplate. Also I hate name "Nullable" and use it under name "Optional" :)
Feb 03 2014
prev sibling next sibling parent reply "TheFlyingFiddle" <theflyingfiddle gmail.com> writes:
On Monday, 3 February 2014 at 10:25:19 UTC, Chris wrote:
 Is there a way I can make the return type in getAttribute 
 generic? null does not work with numbers.

 MyStruct(T) {
   T[T] attributes;
   // ....
   public auto getAttribute(T attr) {
       if (!(attr in attributes)) {
         return null; // Doesn't work for numbers!
       }
       return attributes[attr];
     }
 }

 void main() {
   auto myStr = MyStruct!int(0); // Error
 }
Whenever i am faced with this situation i do one (or more then one) of the following things. struct MyStruct(T) { T[T] attributes; //(1) Forward the underlying access method Eg: auto opBinaryRight(string s : "in")(T attrib) { return attrib in attributes; } //(2) make a try method. bool tryAttrib(T attrib, out T outAttrib) { auto p = attrib in attributes; if(p) outAttrib = *p; return p !is null; } //(3) Give user option to set default value. T attribOrDefault(T attrib, T default) { auto p = attrib im attributes; return p is null ? default : attrib; } //(4) Use Nullable!T (I prefer #5 over this one) Nullable!T attribOrNull(T attrib) { Nullable!T result; auto p = attrib ib attributes; if(p) result = *p; return result; } //(5) Use a pointer but not forward in operator. T* attribPtr(T attrib) { return attrib in attributes; } //(6) Throw exception (I only do this in combination with one of the above) T attribEx(T attrib) { return *enforce!AttribNotFoundEx(attrib in attributes); } } My personal preference using #2 and #3 in combination. #2 covers the basic case "Is this thing avalible?" and #3 covers the case "Give it to me if it is avalible or use this default value" I think it gives a clear image of what your code is doing at the callsite. Only using #2 or #3 limits you in this sence. For #1, #4 and #5 i personally stay away from them. They force the caller to either use an if or potentially trigger a null pointer derecerence. (Btw what is the benefit of #4? I have never used it since it seems pointless) I very rarly use attribEx. I don't think code shuld just spew exceptions all over the place. They should be reserved for really bad stuff, like bounds checks. One exception i make to this rule is if i'm dealing with ranges. Since the other methods don't lend themselfs for UFCS-chaing.
Feb 03 2014
parent "Chris" <wendlec tcd.ie> writes:
On Tuesday, 4 February 2014 at 00:43:54 UTC, TheFlyingFiddle 
wrote:
 On Monday, 3 February 2014 at 10:25:19 UTC, Chris wrote:
 Is there a way I can make the return type in getAttribute 
 generic? null does not work with numbers.

 MyStruct(T) {
  T[T] attributes;
  // ....
  public auto getAttribute(T attr) {
      if (!(attr in attributes)) {
        return null; // Doesn't work for numbers!
      }
      return attributes[attr];
    }
 }

 void main() {
  auto myStr = MyStruct!int(0); // Error
 }
Whenever i am faced with this situation i do one (or more then one) of the following things. struct MyStruct(T) { T[T] attributes; //(1) Forward the underlying access method Eg: auto opBinaryRight(string s : "in")(T attrib) { return attrib in attributes; } //(2) make a try method. bool tryAttrib(T attrib, out T outAttrib) { auto p = attrib in attributes; if(p) outAttrib = *p; return p !is null; } //(3) Give user option to set default value. T attribOrDefault(T attrib, T default) { auto p = attrib im attributes; return p is null ? default : attrib; } //(4) Use Nullable!T (I prefer #5 over this one) Nullable!T attribOrNull(T attrib) { Nullable!T result; auto p = attrib ib attributes; if(p) result = *p; return result; } //(5) Use a pointer but not forward in operator. T* attribPtr(T attrib) { return attrib in attributes; } //(6) Throw exception (I only do this in combination with one of the above) T attribEx(T attrib) { return *enforce!AttribNotFoundEx(attrib in attributes); } }
Thanks for this brief outline.
 My personal preference using #2 and #3 in combination. #2 
 covers the basic case "Is this thing avalible?" and #3 covers 
 the case "Give it to me if it is avalible or use this default 
 value" I think it gives a clear image of what your code is 
 doing at the callsite. Only using #2 or #3 limits you in this 
 sence.
Personally I don't like the idea of passing a default value on the user side in this particular case. If the attribute has not been set, there is a reason, and I don't want to operate with a return value of something that has not been set at all. I introduced a check similar to #2: bool hasAttribute(T attr) { ... } Of course, the user has to use if. Experimentally, I introduced auto getAttribute(T attr) { if (!(attr in attributes)) { return T.init; } return attributes[attr]; } to avoid the if statement and just gently move along, if the attribute has not been set, which again leads to the problem of #3, i.e. potentially operating with a value of something that does not exist in the first place.
 For #1, #4 and #5 i personally stay away from them. They force 
 the caller to either use an if or potentially trigger a null 
 pointer derecerence. (Btw what is the benefit of #4? I have 
 never used it since it seems pointless)
#4 is weird, but that's because I don't fully understand the concept behind it.
 I very rarly use attribEx. I don't think code shuld just spew 
 exceptions all over the place. They should be reserved for 
 really bad stuff, like bounds checks. One exception i make to 
 this rule is if i'm dealing with ranges. Since the other 
 methods don't lend themselfs for UFCS-chaing.
I agree. Exceptions should be reserved for serious cases or cases where you simply cannot predict all cases (reading random input from the internet, for example).
Feb 04 2014
prev sibling parent Marco Leise <Marco.Leise gmx.de> writes:
Am Mon, 03 Feb 2014 10:25:17 +0000
schrieb "Chris" <wendlec tcd.ie>:

 MyStruct(T) {
    T[T] attributes;
    // ....
    public auto getAttribute(T attr) {
        if (!(attr in attributes)) {
          return null; // Doesn't work for numbers!
        }
        return attributes[attr];
      }
 }
 
 void main() {
    auto myStr = MyStruct!int(0); // Error
 }
MyStruct(T) { T[T] attributes; // .... public auto getAttribute(T attr) { return attr in attributes; } } There you go. -- Marco
Feb 06 2014