8 ms·
I’m used to 0 and [] being falsy. Upsides and downsides to Clojure’s choice here?
by asplake 7y ago
I’m used to 0 and [] being falsy. Upsides and downsides to Clojure’s choice here?
- eropple 7y ago0 and [] are real and meaningful values that have different meanings than nil/null (in what I assume you're referring to as Python) or (in JavaScript) undefined. They should be treated as such. (That JS coerces the empty string to false is also bad.)
- samhh 7y agoThere is both null and undefined in JavaScript. Additionally, an empty array coerces to true because it's an object and objects are always truthy. 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.
- SEMW 7y agoThe trouble is that `if(value)` or `value && ..` ends up getting used as an idiomatic shorthand for "if value is present" even in languages (such as javascript and python) where 0 is falsey. Because most of the time it works, so people do it. And then inevitably get bugs when the value happens to be 0. 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/ 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).
- np_tedious 7y agoHere's another prime python example. The response object in the requests library has a truthiness equivalent to request success. Can easily trip you to with `res and res.content` https://github.com/psf/requests/blob/master/requests/models.py#L668 https://github.com/psf/requests/blob/master/requests/models.... https://stackoverflow.com/questions/48347290/why-does-requests-response-object-bool-check-for-200-status-400 https://stackoverflow.com/questions/48347290/why-does-reques...
- raverbashing 7y agoI disagree with this rationale (not with your specific explanation, with the rationale) 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)
- hibbelig 7y ago> Python's rule is simple, it's JS that came up with confusing rules. 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.
- mikeash 7y ago"The following values are considered false: * 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.
- smcameron 7y agoI'm also used to 0 being false. A possible upside to this is that you can use multiplication and addition as boolean AND and OR, which might not seem that useful, but I remember that BASIC on a ti99/4a had neither AND nor OR, but boolean expressions evaluated to 0 or 1, so you could use this trick. 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.
- lolinder 7y agoIt sounds like that was only useful because the language actually had no Boolean type (hence no operations for Boolean types). It doesn't really demonstrate that there is value to a "falsey" 0 in a language that already has a perfectly good value for false.
- smcameron 7y agoUh yeah, no kidding, that's why I qualified what I was saying with "possible...", and "which might not seem that useful, but..."
- bhaak 7y ago0 being falsy makes no sense outside of the C family of programming languages. But there 0 is overloaded both as a number and pointer (it's a bit more complicated though, as the value of the null pointer doesn't need to be zero but assigning 0 to a pointer will assign the null pointer). 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.
- bitcurious 7y ago>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?
- jarfil 7y agoThe problem lies in what question does it answer: * 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".
- bb88 7y agoSo here's an example where it matters in python: 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.
- Sharlin 7y agoI'm not sure 0 being falsy is ever a good idea, it's just an assembly-ism that became a C-ism and spread from there. Zero is not a "special enough" value in the ring of integers. In general, these days I prefer as few implicit coercions as possible. 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.
- celticmusic 7y ago0 is the value integral types get default initialized too, it's the equivalent of default initializing a boolean to false.
- Sharlin 7y agoBut the fact that some types have a reasonable default value doesn’t make the values intrinsically equivalent to Boolean false. Plus, implicit default initialization is itself not necessarily a desirable feature. Especially not the kind that just default initializes to an all-zeroes bit pattern.
- celticmusic 7y agodefault initialization is a desirable feature in most circumstances, and I would argue most languages should be opt-out rather than opt-in. There 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.
- tosh 7y agolikewise, I forgot about (boolean 0) being true and was confused for a few minutes today :)
- karmakaze 7y agoZero being a present value and truthy makes sense to me--same as Ruby. [] 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.
- keymone 7y ago0 does not indicate absence of value. Think of temperature - 0 is a valid temperature and is actually not equal to 0 when converted to Fahrenheit. It becomes incorrect to check presence of temperature by just relying on Boolean coercion, you have to check for not-None(in python) explicitly. 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.
- a1369209993 7y agoNitpick: a actual zero (ie, in Kelvin or Rankine) is not a valid temperature but a lower bound that temperature can't actually reach (like -infinity for real numbers).
- keymone 7y agoyeah, fair point, maybe there's a better example, but i hope i've conveyed the idea: some zero values are not absence of values.
- ledauphin 7y agoAll of the comments here seem to be addressing the issue of 0 being falsy, and I agree with them - 0 is a perfect 'useful' value that is no different from 1 in many use cases. But no one seems to be addressing the advantage of the empty list/array/collection being treated as falsy, which in my opinion is significant. 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.