www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Casting an expression to bool means testing for 0 or !=0 for arithmetic types

reply Pluto <pluto planets.not> writes:
This part has always bothered me. Could somebody please explain to me the
rationale behind limiting functions to one usable error code?

if(function())
~~
:Pluto
Jul 31 2010
next sibling parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 31.07.2010 23:44, Pluto wrote:
 This part has always bothered me. Could somebody please explain to me the
 rationale behind limiting functions to one usable error code?

 if(function())
 ~~
    
Inherently if is for testing _condition_ which is true/false. Going futher you'd just reinvent switch statement. which if perfectly OK for it, here it goes: switch(function()){ case ERR_CODE1: // --- break; case ERR_CODE2: // --- break; // --- default: // --- break; } Honestly, I'd suggest using exceptions instead of error codes. Usage of error codes scales poorly and itself is very error-prone, also killing the return value of functions just for error code leads to very awkward design.
 :Pluto
    
-- Dmitry Olshansky
Jul 31 2010
next sibling parent reply Pluto <pluto planets.not> writes:
== Quote from Dmitry Olshansky (dmitry.olsh gmail.com)'s article
 On 31.07.2010 23:44, Pluto wrote:
 This part has always bothered me. Could somebody please explain to me the
 rationale behind limiting functions to one usable error code?

 if(function())
 ~~
Inherently if is for testing _condition_ which is true/false. Going futher you'd just reinvent switch statement. which if perfectly OK for it, here it goes: switch(function()){ case ERR_CODE1: // --- break; case ERR_CODE2: // --- break; // --- default: // --- break; }
But these aren't compatible for the same function. Defining false as <1 would fix this.
 Honestly, I'd suggest using exceptions instead of error codes.
 Usage of error codes scales poorly and itself is very error-prone, also
 killing the return value of functions just for error code leads to very
 awkward design.
I was asking purely out of design interest. ~~ :Pluto
Jul 31 2010
parent Jonathan M Davis <jmdavisprog gmail.com> writes:
On Saturday 31 July 2010 16:22:46 Pluto wrote:
 But these aren't compatible for the same function.
 Defining false as <1 would fix this.
Historically, false has always been 0 and true non-zero - probably because it's then easy to use the assembly instruction to check whether something is 0 (which I believe is cheaper than checking for a specific value). In any case, changing that would likely break both people's expectations and their code. And regardless of that, it _isn't_ an error code to have a function return whether it succeeded or not. If you want to check error codes, then check for specific values or actually use the returned value rather than just throughing it in a condition by itself. _Every_ type converts to bool when put in a condition statement. It's entirely consistent that way. And having 0 be false and 1 true for numeric types is entirely consistent with how things have historically worked and pretty much everyone will expect them to work. Changing that would cause far more problems than it would ever solve. If you want error codes, you can just check the return value against 0 like has historically been done and you're fine, but error codes are generally a poor choice anyway. - Jonathan M Davis
Jul 31 2010
prev sibling parent reply Jason Spencer <spencer8 sbcglobal.net> writes:
== Quote from Dmitry Olshansky (dmitry.olsh gmail.com)'s article
 Honestly, I'd suggest using exceptions instead of error codes.
This seems to be an increasingly common but still expensive suggestion for error handling. Unless D has made great strides in exception performance (which is entirely possible), I still believe that, where performance is anything like an issue anyway, exceptions should be used only for truly exceptional condition--not alternate flow that could/should be expected. But I'm not just being a curmudgeon; I also have a suggestion. If you really want to take different paths based on a function return, make that return an object. This is what exceptions do, but it need not be so expensive. You could have a generic return class that has a string, a value, and casts to bool to give you most of what exceptions and bool return types give. But you can also sub-class that class and add polymorphic behavior to avoid the switch on value. Or make it a singleton and not pass it back. Or have one for each important call path, to which every called function can add delegates to run onError() or run deferred at some root level. Some of this would be a lot of work to setup, but I find it pretty generic. I should probably make a library and post it. For the stuff that most folks use exceptions for (i.e. to hold a string,) this class is easy to write, and costs must less at run-time. I will freely allow the critique that if do you this, then you'd have to check the return object after each call to see if there's error handling required. But I see this as a chief benefit for the maintainer (usually me in 6 months after I've forgotten everything about the code.) It spells out that there is another code path that's important to consider when changing things. Letting exceptions go by tends to obfuscate that. For out of memory, sure throw and don't catch. But for input string didn't parse, you want to handle that safely, informatively, gracefully, or whatever your robustness requirements are. So make it clear to someone that it needs to be handled. I see a lot of exception-unsafe code. I think programmers often don't appreciate that your function may suddenly return between any two arbitrary function calls, and almost between any two arbitrary statements. You have to really design carefully for exceptions. So it turns out this problem is a tough one and requires effort no matter what. Just a thought. Jason
Jul 31 2010
next sibling parent reply Jonathan M Davis <jmdavisprog gmail.com> writes:
On Saturday 31 July 2010 20:15:58 Jason Spencer wrote:
 == Quote from Dmitry Olshansky (dmitry.olsh gmail.com)'s article
 
[...snip]
 
 Just a thought.
 Jason
I honestly don't think that exception handling is a particularly expensive solution in most cases. When dealing with an error, it's likely to either be related to I/O (be it an actual I/O error or input from a user or file) or a logic error in the code. For logic errors, efficiency isn't really an issue, since they shouldn't be happening. If they are, go fix your code and it won't be an issue. For I/O-related errors, I/O is already slow. You can't expect that to be efficient anyway. And if you're get an I/O error, you have a problem that has to be dealt with, and efficiency probably isn't an issue at that point anyway. The main issue with exceptions is whether code handles them correctly, and that's just as true of error codes. Particularly when you add RAII and scope into the mix, it's really not all that bad. There's always the issue over checked exceptions with there being pros and cons both ways, but overall, I'm definitely pro-exceptions and anti-error handling. I really do think that it makes for better code and that efficiency really isn't an issue in most cases. Maybe if you're dealing with embedded code, it might matter. But beyond something like that, exceptions will be better. As for how to use exceptions, I really think that Java does it quite well and is a good role model overall. It's just the checked exceptions that are particularly debatable. Your suggestion isn't necessarily bad, but I really don't think that there's anything wrong with using exceptions and that having to deal with errors explicitly like you would with error codes or your suggestion just clutters code and makes it less maintainable. - Jonathan M Davis
Jul 31 2010
next sibling parent Pluto <pluto planets.not> writes:
== Quote from Jonathan M Davis (jmdavisprog gmail.com)'s article
 On Saturday 31 July 2010 20:15:58 Jason Spencer wrote:
 == Quote from Dmitry Olshansky (dmitry.olsh gmail.com)'s article
[...snip]
 Just a thought.
 Jason
I honestly don't think that exception handling is a particularly expensive solution in most cases. When dealing with an error, it's likely to either be related to I/O (be it an actual I/O error or input from a user or file) or a logic error in the code.
I have seen code which tries toInt and then does toFloat in the catch. Not sure whether it was just bad code (looks like it though) but there are probably some examples where people really don't know what kind of data they are handling and use std.conv to try. Not really sure about that now that I think about it. ~~ :Pluto
Aug 01 2010
prev sibling parent reply Jason Spencer <spencer8 sbcglobal.net> writes:
== Quote from Jonathan M Davis (jmdavisprog gmail.com)'s article
 For logic errors, efficiency isn't really an issue, since they
 shouldn't be happening. If they are, go fix your code and it won't
 be an issue.
That gets less true as the cost of a try block goes up. Even if logic errors never occur, the infrastructure to check for them costs you something. Ever compared the performance of a program w/ and w/o trys? So, would you advocate for exceptions as the sole error reporting mechanism? Return values are just for valid result values and everything else is an exception? Where do you personally draw the line? Jason
Aug 01 2010
parent Jonathan M Davis <jmdavisprog gmail.com> writes:
On Sunday 01 August 2010 07:36:27 Jason Spencer wrote:
 == Quote from Jonathan M Davis (jmdavisprog gmail.com)'s article
 
 For logic errors, efficiency isn't really an issue, since they
 shouldn't be happening. If they are, go fix your code and it won't
 be an issue.
That gets less true as the cost of a try block goes up. Even if logic errors never occur, the infrastructure to check for them costs you something. Ever compared the performance of a program w/ and w/o trys? So, would you advocate for exceptions as the sole error reporting mechanism? Return values are just for valid result values and everything else is an exception? Where do you personally draw the line? Jason
It really depends on what you're doing. I do think that exceptions are the better way to go, but it tends to be dangerous to say things like "always" and "never." Generally, however, I would use return for valid results only and use exceptions for errors. However, if error cases become very likely, then exceptions do tend to become a poorer solution since you don't want to be throwing lots of exceptions. Also, there are cases where you can take an error and simply turn it into a default value (particularly for things like parsing and conversion functions) if that's acceptable. Still, I think that it generally makes for better code when you assume that the returned data is correct and then let exceptions deal with the error cases - particularly when dealing with user input and file I/O. And in such cases, efficiency is not generally the greatest concern. If you had cases where efficency was a great concern, then it might be worth looking at whether exceptions were the best way to handle things, but in the general case, I believe that they are. As to where exactly I draw the line, I'd really have to look at the code in question to make any kind of judgement call. However, I'm generally going to go with exceptions unless I have a good reason not to, and I've found such reasons to be fairly rare. - Jonathan M Davis
Aug 01 2010
prev sibling parent bearophile <bearophileHUGS lycos.com> writes:
Jason Spencer Wrote:
 Unless D has made great strides in exception
 performance (which is entirely possible),
From my tests dmd exceptions are more than ten times slower than Oracle-Java ones. I have test code. Bye, bearophile
Aug 01 2010
prev sibling parent reply "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Pluto <pluto planets.not> wrote:

 This part has always bothered me. Could somebody please explain to me the
 rationale behind limiting functions to one usable error code?
Well, traditionally it was done because testing for 0/non-0 is a simple and fast operation. Also, boolean logic can be thought of as simple maths, as AND is multiplication and OR is addition. This only makes sense if false == 0. As for having more error codes, why would you use an int for it? Would it not be more logical to use an enum, and to explicitly test for the value upon return? Why not use exceptions? If the problem is that enums are non-extensible, and exceptions are too heavy, many libraries use the convention that 0 means success, and anything else is an error code. -- Simen
Jul 31 2010
parent reply Pluto <pluto planets.not> writes:
== Quote from Simen kjaeraas (simen.kjaras gmail.com)'s article
 Pluto <pluto planets.not> wrote:
 This part has always bothered me. Could somebody please explain to me the
 rationale behind limiting functions to one usable error code?
Well, traditionally it was done because testing for 0/non-0 is a simple and fast operation.
So speed it is. Was <1 really slower back then?
 Also, boolean logic can be thought of as simple maths,
 as AND is multiplication and OR is addition. This only makes sense if
 false == 0.
false < 1, is what I would expect. It even makes it more symmetrical.
 As for having more error codes, why would you use an int for it? Would it
 not be more logical to use an enum, and to explicitly test for the value
 upon return? Why not use exceptions?
 If the problem is that enums are non-extensible, and exceptions are too
 heavy, many libraries use the convention that 0 means success, and
 anything else is an error code.
Don't worry, I won't start using return types like this in D. Just interested.
Jul 31 2010
parent reply "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Pluto <pluto planets.not> wrote:

 == Quote from Simen kjaeraas (simen.kjaras gmail.com)'s article
 Pluto <pluto planets.not> wrote:
 This part has always bothered me. Could somebody please explain to me  
the
 rationale behind limiting functions to one usable error code?
Well, traditionally it was done because testing for 0/non-0 is a simple and fast operation.
So speed it is. Was <1 really slower back then?
Likely not. But the assembly is easier: jnz <label>; vs cmp EAX 0; jg <label>;
 Also, boolean logic can be thought of as simple maths,
 as AND is multiplication and OR is addition. This only makes sense if
 false == 0.
false < 1, is what I would expect. It even makes it more symmetrical.
How? Let's start out with a false value f of -1. Now AND it with itself (f * f), and you get 1, which is true. This does not seem correct. -- Simen
Jul 31 2010
parent Pluto <pluto planets.not> writes:
== Quote from Simen kjaeraas (simen.kjaras gmail.com)'s article
 Pluto <pluto planets.not> wrote:
 == Quote from Simen kjaeraas (simen.kjaras gmail.com)'s article
 Pluto <pluto planets.not> wrote:
 This part has always bothered me. Could somebody please explain to me
the
 rationale behind limiting functions to one usable error code?
Well, traditionally it was done because testing for 0/non-0 is a simple and fast operation.
So speed it is. Was <1 really slower back then?
Likely not. But the assembly is easier: jnz <label>; vs cmp EAX 0; jg <label>;
That might explain things.
 Also, boolean logic can be thought of as simple maths,
 as AND is multiplication and OR is addition. This only makes sense if
 false == 0.
false < 1, is what I would expect. It even makes it more symmetrical.
How? Let's start out with a false value f of -1. Now AND it with itself (f * f), and you get 1, which is true. This does not seem correct.
Not knowing anything about assembler(and ignoring history), this seems sensible to me: f is an integral, not a boolean. For it to work like a boolean it needs a cast, implicit or explicit: cast(bool); >0?true:false if(returnsErrorCodes()); implicit cast from return value to bool f*f; integral operation and should return the multiplication f&f; bitwise operation and should return bitwise AND cast(bool) f & cast(bool) f; bitwise operation on booleans ~~ :Pluto
Aug 01 2010