This can get increasingly hard to reason about the more datatypes you add which interpret 0-ish values as falsey -- as there's always the temptation to do, for 'consistency' with integer truthiness, e.g. https://lwn.net/Articles/590299/ about the python behaviour of dates being falsey in the first second after midnight.
If `null` and `false` are the only falsey values (ala clojure, ruby, elixir etc), that's a rule that's really easy to internalise and reason about, so you never have to worry about things like whether your data type might be falsey in the first second after midnight (and also `if(values)` is actually a correct idiom).
https://github.com/psf/requests/blob/master/requests/models....
https://stackoverflow.com/questions/48347290/why-does-reques...
You can make your types be interpreted as True/False (in Python at lest) as you want. The issue here is that a date should never be "false".
> that's a rule that's really easy to internalise and reason about
Python's rule is simple, it's JS that came up with confusing rules.
The issue here are types that are inconsistently false (and I disagree with the Python module on that)
* None
* False
* zero of any numeric type, for example, 0, 0L, 0.0, 0j.
* any empty sequence, for example, '', (), [].
* any empty mapping, for example, {}.
* instances of user-defined classes, if the class defines a __nonzero__() or __len__() method, when that method returns the integer zero or bool value False."
This really does not strike me as being simple.
I personally don't find these shortcuts to be worthwhile. The best approach is that boolean is a separate type, true is true, false is false, and any attempt to use any other type as the predicate of a control flow statement is a (preferably compile-time) error. Writing `if x != 0` is not a large burden.
They are, because they are the difference between driving with the parking brake on or not. Other languages don't have this so that's why people might not see the value in it first.
> Writing `if x != 0` is not a large burden
If you're comparing what can only be an int value, then it's not a burden.
When your variable can assume multiple values, explicitly comparing it with multiple possibilities is a burden:
Examples:
- Your variable is optional and/or is a sequence that might be empty
- You're using .get() in a dictionary
One of the signs someone is unfamiliar with Python is doing multiple comparisons when only `if x:` would suffice
Regarding your examples, having an optional sequence is probably not the correct move anyway. Is there actually a semantic difference between no sequence and an empty sequence? If not, the variable should be non-optional. If so, then glossing over those differences by writing `if x` to implicitly check both conditions is unclear. Not sure what you're referring to with get() in a dictionary.
I understand that good Python style is considered to be one where you take advantage of the language's notion of truthiness, but I disagree with its whole approach.
Most code doesn't need that and the truthiness nature of various data types is helpful in writing clear code.
It really depends on your function, but you might want to have the function behave differently if given an empty list rather than no list. One very common case for doing that is when you want to facilitate unit testing of a function.
> Not sure what you're referring to with get() in a dictionary.
A very common case, in which you don't care if the element is not present or it is a "False" element.
If you're fetching an element from a dictionary and want not-present to be the same as some false value, fetch with a default value (in Python, pass a second parameter to get()) that you want to see when there's nothing present. Or just extract the value and check for nil or empty separately, nothing wrong with being explicit.
I think Perl was there first: in addition to 0 and "" being falsy in Perl, "0" is also falsy. Then there is this nifty Perl value "0 but true": if you stick it into a condition, it will evaluate to true, but if you do math with it, it behaves like the number zero.
Suppose a fantasy language has an ergonomic syntax to check existence. It returns a boolean. Additionally, implicit conversions to boolean for objects in the language is randomized for each runtime.
Isn't such a weirdo language still preferable to implicit conversions, even when compared to a language where "null" and "false" are the only falsey values? Because even in those languages the new user must internalize the simple rule and learn from context how it works and why it can be used with impunity. Whereas with my weirdo language the syntax explicitly conveys to all classes of user what is happening in the code.
Furthermore, I'd bet that even in your preferred languages there are less readable idioms that ninjas can leverage in the implicit conversions. In my weirdo language the randomized conversions would thwart the ninjas and keep the entire class of users free from their unreadable tyranny.
What "exists" means depends on the language; it can be as loose as checking if the variable has ever been set (PHP), but generally has nothing to do with truthiness/falsiness status.
In many Lisps, nil is the empty list literal, but interestingly not in Clojure, where it just maps to JVM null, and vectors are the most commonly used data type anyway. However, it is idiomatic for APIs to accept (but not return!) nil as if it were an empty sequence or collection, unlike in most non-Lisp languages.
When you have a type system and boolean types, equating 0 and false is not a "math thing," it is more of a "mathematical illiteracy" thing.
false = 0
true = [1] (i.e. the equivalence class of all n > 0)
&& = +
|| = *
spelling it out: || / + (or):
0+0 = 0
0+[1] = [1]+0 = [1]
[1]+[1] = [1]
&& / * (and):
0*0 = 0
0*[1] = [1]*0 = 0
[1]*[1] = [1]
where [1] stands for "any nonzero number". (please let me know if i missed anything!)so natural numbers (quotiented by an equivalence relation) seem to work as a model for boolean arithmetic. and idk if we need more mathematical justification than "it works fine"
EDIT: changed "integers" to "natural numbers", because
5 + -5 = 0
so the `[1]+[1] = [1]` property doesn't holdNeither does the multiplication `[1]*[1] = [1]`
x || y = x + y - xyThere are really only two reasons I can think of for not wanting default initialization.
1. performance
2. to catch mistakes on the edges of your system.
A good example of the second is converting incoming data to enumerations. If you're not explicitly checking for it, you can accidentally default initialize to a valid enumeration. This is why I always set the first enumeration to 1 for any language that allows me to. If someone makes that mistake, the missing data causes an error rather than silently doing the wrong thing.
But I can't really think of any reason outside of those two cases where you wouldn't want default initialization.
And my point was that one of the reasons 0 is chosen as false because it's consistent with respect to default initialization of booleans. I realize that isn't the original reason, but today it's a good reason for having the behavior.
(That JS coerces the empty string to false is also bad.)
Personally I'd prefer that not even null/undefined coerced to false, or could otherwise be used in a boolean comparison, as for me they still signify different meanings and even in type-safe languages can lead to subtle bugs. I really dislike coercion having principally worked with JavaScript/TypeScript.
The major advantage of empty collections being falsy is that you can kill two birds with one stone - it enables you to reduce your use of nil/null/None dramatically, since empty collections will fail a very idiomatic "if items" check just as well as null. But at the same time, you don't have to depend on users (library or otherwise) being careful to provide an empty list rather than null when they mean 'no value' - if they pass a null then your code will take the exact same shortcut with the exact same simple, idiomatic test. Whereas the alternative is needing to do both the null check AND the empty check any place you cannot trust the users of your function/library/whatever to pass real empty collections rather than nulls, which not only becomes tedious but is quite easy for new programmers to fail to do consistently.
In my own experience, the number of cases where an empty collection implies that data is effectively 'missing' is far greater than the number of cases where an empty list being provided would be semantically different from a null - in both cases "there is nothing there". And being freed to then create and pass around empty lists means that my own code is no longer littered with the necessary guards against Nullness in cases where I am the consumer of my own functions and want to be able to write simple list comprehensions without any guards.
In Clojure this is somewhat less of an issue because, as others have mentioned, it's idiomatic to pass `nil` around in the place of an empty collection, and so it's _generally_ safe to do so where in another language like Python or Javascript the function you're calling might not expect it. But in my opinion there was no real advantage to establishing this convention when it could just as easily have been the other way around, and there would therefore be somewhat less opportunity for inconsistent or inexperienced programmers causing NullPointerExceptions.
Generally the more special values, that are treated as false, you have in a language - the more complex and confusing your programs become. Just my opinion.
In languages with more elaborate type systems, you usually have the pointer and number representation separated.
0 has no special meaning in regards to boolean logic but it has the special meaning of the identity/zero element in an additive group. Likewise, it makes sense to have "[1] + [] => [1]" in your programming language.
But making zero elements evaluate to falsy is problematic. "" would be falsy as well then.
In Python 0, [], and "" are all falsy. In my experience it's only been useful - why do you consider it problematic?
* Does it exist (true) or not (false)?
* Does it have a defined value (true) or not (false)?
* Is it non-zero (true) or not non-zero (false)?
* Does it have non-zero length (true) or not non-zero length (false)?
I think using zero as a magic number is less consistent than not using any magic numbers at all. If you want to check the length, or whether something equals zero, just check for that, not for whether it's "truthy".
def f_to_c(deg_f=None):
if deg_f:
return (deg_f - 32) * 5 / 9
else:
raise TypeError('Invalid value passed in')
The problem is that it works for all numbers, except 0, because 0 is falsy, and will return a TypeError exception.There are lots of different solutions to this issue, but I wouldn't write the pedantic if statements.
I would just do this:
def f_to_c(deg_f):
return (deg_f - 32) * 5 / 9
And let python raise the TypeError.Right?
def my_calc(x, scale = None):
if not scale:
scale = 1
return (x + 4) * scale
where the (x + 4) bit stands for some interesting calculation whose result is being scaled: this code makes it unclear whether or not scale == 0 is intentionally conflated with scale == None or whether this is a programming error. It would be better to do this, if this is intentional: def my_calc(x, scale = None):
if scale == 0 or scale is None:
scale = 1
return (x + 4) * scale
Or, if it's unintentional, one should have written: def my_calc(x, scale = None):
if scale is None:
scale = 1
return (x + 4) * scaleI get that you might want to know if it was supplied or if it is the default, and common lisp has a way to do that. It is very niche to need, thought.
That all said, I think this is just suffering the fate if anything with examples. I accept there are places where zero as false can be annoying.
Generally speaking we're creating contrived examples because in all fairness, I've only seen this once I can remember in the last 5 years of writing business logic in python. Usually language purists would prefer us to be explicit in our languages. And type safety is the best thing since sliced bread.
But effectively that's why python has taken off. Types do matter, but for most things they don't Lots of good software has been written using python.
I think this is one of the symptoms of the changing priority of simplicity in Python language design, at some point the design decision about 0/1 for simplicity became dated enough to change it despite being a big change and requiring backwards compatibility hacks with 0/1.
I do think it would've been better to have 0, specifically, not be falsy - unlike the other cases, 0 is not 'the absence of any actual data' but rather a special case of the data. I assume Python stuck with tradition on this one mostly because it is so tightly tied to C and UNIX-style scripting that changing it would've been confusing for most early users.
[] being truthy is the same as Ruby, but somewhat of a gotcha as an empty vector [] does not have the same truthiness as an empty list '() aka nil.
Instead of
if x = 5 and y = 7 then goto 10
use if (x = 5) * (y = 7) then goto 10
Good to know when you're 11 years old typing in some program from a magazine meant for another computer that had AND and OR.