Is there a good reason, other than expectations brought from other langs, why 0 should be falsey?
Is there a good reason, other than expectations brought from other langs, why 0 should be falsey?
Still I prefer a distinct Boolean type, too.
Boolean mu = null;You should get a type error if you try to use an int in boolean logic. I hate auto type-conversion. Autoboxing in Java is kinda nice, but really the base types should have been objects to begin with, and operator overloading should have been allowed.
(Although I do admit operators in Scala get confusing as hell. I like one of the recent RFCs that would require every Scala operator to have an actual "word" function alias to go with it so you can tell what the hell it does).
Booleans are the same kind of thing. We don't normally do arithmetic on them; at most we might treat `bool` like Z-mod-2. But Z-mod-2 behaves very differently from `int`.
We very often do arithmetic on booleans, the a canonical homomorphism (Iverson bracket) with 1 ~ True and 0 ~ False where && and || are multiplication and addition. But I guess you knew that.
This is just as bad as the existence of truthy or falsy values.
So define:
def __bool__(self):
raise TypeError("I don't like non-bool booleans")
on your classes, and you're good to go. Python isn't doing type conversion, it's doing operator overloading and letting you define how your class should work with the 'if' statement. It won't help you with the already-defined behavior of built-in and third-party classes, but you can have all your own code be unusable in boolean operations if you like.If, in addition, you want implicit conversion from integers to booleans, you have to pick some patterns as meaning ‘false’.
The first is fairly normal in low-level languages. So, the question is why one would want implicit conversion from integer to boolean. The main reasons I can think of are
- because assembly doesn’t have boolean.
- to keep source code shorter.
For C, I think it was a combination of both. Testing for null pointers using if(p) is nicely short. From there, given that there’s little or no difference between pointers and integers at register level, it is a short step to if(i) for integer i.
What is flags register file for then? Zero flag, carry, overflow, etc? They sure look booleans to me. Sometimes assembler routines pass booleans in carry flag.
Zero is an absence, and it has a good analogy in the real world: If you have 0 apples, you do not have any apples.
unless I owe some apples - in which case, by your "real-world" test, I do have some?
Do you owe anything if you have 0 apples?
This phrasing shows the error even more starkly. No, you don't owe anything if you have 0 apples. If you use this as a test, however, then you also either "don't owe" anything if you have -5 apples, or you do owe something if you have 5 apples. You simply can't use this as the test.
To put this into code directly based on the logic you say it's an analogy for:
numapples = 0;
// Do you owe anything if you have 0 apples?
if (!numapples) {
printf("I don't owe any apples.\n");
}
else {
printf("I owe some apples.\n");
}
This simply doesn't work. (Gives wrong result at 5 for example.)The correct tests are, respectively:
numapples = 5;
// have no apples
if (numapples == 0) {
printf("I don't have or owe any apples.\n");
}
if (numapples < 0) {
printf("I owe some apples.\n");
}
if (numapples > 0) {
printf("I have some apples.\n");
}
There is another, subtler, reason for this. What is special about the value "0" here is that it's the default value - you're born without any apples, or something.But by making someone write the test like this:
(currentapples - startingapples) == 0
you can test whether there is any offset from a different starting value. The "0 == false" version of the test completely breaks that: it applies when you're testing an offset at 0, but breaks anywhere else. In other words as an offset it's equivalent to: ( currentapples == zeroapples ) && (zeroapples == 0)
don't get me wrong, I use it in code. But I'm not under the misapprehension that there is a real-world analogy. There isn't one. It's simply the definition of C/C++.Whenever I see == 0 in an if test, I feel free to remove those 4 characters, don't get me wrong. Because it's just the definition of C. Not because I think there's any analogy with anything.
That said, which version of this code is cleaner and less likely to allow a programmer to make a logical mistake:
1.
if ((currentapples - startingapples) != 0) {
2. if (currentapples - startingapples) {
to me, the intent of 1 is super-clear, as for the intent of 2, who knows, but of course after half of a second I can parse what its effect is, by running through the definition of C in my head. That's not great for reading and debugging programs. :)That's why many databases allow for (the option of) types to include a NULL value that is handled specially.
When you are rolling your own data design it is a legitimate question to ask: "Will an incomplete version of this object exist?" If the answer is yes, then the design should accommodate making that distinction.
Most likely this is a result of one of the mother of most bracket based languages specifically C and it's children. BCPL did not have multiple data types which I would guess was an artifact of it's time. I am not certain if this is still the case, but a language that was built in the 60's clearly had limits that are no longer valid. With no types it would be natural to define a boolean as a 0 and 1 in binary which would give you your 0 is falsey.
I was not born yet, and I expect you probably where not as well, when these pivotal decisions where made. It is still interesting to look back and see how what we do today was the result and the why of our forefathers in computer science. For example at the time I can see why Ken Thompson would make the choice of = as assignment instead of := to save bytes and use == for comparison. Assignment being a more often operator would indeed save space, but the amount of bugs it's created since then he probably would not have made that decision.
I'm probably wrong though.
if(myfunc(...)) { ...It's a design decision, so it should be informed by the audience of the language.
More complicated semantics save more words. Ergo we should make the semantics as complicated as possible.
(This specific example is probably a false dichotomy--"the concept of truthiness" might be neither as simple nor as complicated as possible.)