Hacker News new | past | comments | ask | show | jobs | submit login
Why is zero not falsy in Clojure? (2015) (stackoverflow.com)
40 points by tosh on May 20, 2022 | hide | past | favorite | 59 comments



The framing of the original question is ill-posed. It says that C treats 0 as falsey in a boolean context, but early versions of C did not have booleans. Thinking that C treats 0 as false is looking at it from a perspective deeply influenced by modern languages. The IF statement in C tests for zero, just like many assembly languages. The idea of "false" is a higher-level abstraction that was successfully back-ported to C.

My own thoughts is that what values should be falsey in a language depends on what your language is supposed to manipulate. C is a language designed for mucking about with machine data: bits, numbers, and pointers. And those are the data types that can be sensibly used in a boolean context in C. The Lisp family is designed for processing lists (it's in the name), and lists are the only type that some Lisps let be falsey (although many don't even allow that). It seems sensible to me that a fundamentally object-oriented language would treat a null object as falsey, or the empty string in a language for text processing. I dislike it when multi-paradigm languages have many falsey values.


> The idea of "false" is a higher-level abstraction that was successfully back-ported to C.

You're pushing 'back-ported' a bit much. It's just a blatant equivalence that 0 is a conceptual false, when a zero caused a particular behaviour if used in an if/while/etc.

'false' as a concept has always been in C afaik whether it had that name (via #DEFINE) or not.


In my opinion if a language has False/None/Null/Nil as first class values, then the only sane path is for everything else to be True: the number zero, NaN, empty string, empty list, empty set, etc. All of those should evaluate to True.


That opinion rules out many strongly typed languages where booleans are strictly separate from other types, just as, in such languages, floats are separate from doubles, and any assignment of a value of type T1 to a variable of type T2, or passing a variable of type T3 to a function taking an argument of type T4 requires explicit conversion, even if that conversion could be lossless.

For example, in Swift

    let f: Float = 1.0
    let d: Double = f
is invalid code. You have to write

    let f: Float = 1.0
    let d: Double = Double(f)
In such languages, true is true, false is false, and all other values are neither true nor false.

I think that, certainly for booleans, that’s the sanest choice.


I tend to think the only sane path is a type error and anything else is arbitrary. It isn’t that hard to have converters which make sense in a standard library. isNone, isNil ..


Yeah... ISWYM and it sounds good, then again it confuses the boolean concept with the list concept, the set concept etc. etc. etc. Doesn't mean it isn't useful, and maybe there's very little risk of mistakes but still.


If every list or set or dict evaluates to true, even empty ones, what is there to confuse? Or is the argument that non-bool types shouldn't coerce to a bool at all, so that you're force to always explicitly call a bool-returning method, like "if my_list.empty() then" type of thing?

In any case, the next thread down is making the same point, and specifically calling out languages that do exactly this and get it right: Clojure, Lua, Ruby, and others.


It's a theoretical challenge to your idea, that's all. In fact IIRC from the distant past that lambda calculus is where this behaviour comes from so it is in fact theoretically well-founded. I withdraw my doubts.


> if a language has False/None/Null/Nil as first class values, then the only sane path is for everything else to be True

I very much disagree.

Every type should have a false value.

Note that False is a bool while None/Null are pointers/references and Nil is Lisp's empty list. (Python's empty list is [] and it also has empty tuples.) Why should those types and no others have a false?


The notion that any non-boolean type has a "false value" is strange to me. There's some convenience in it, certainly, but I don't see it making sense on a more basic level.


One could define a BooleanConvertible interface with a toBoolean() method.


To me that makes just as much sense as saying that every type should have a zero, or that every type should have the empty string. Just nonsense.

If a type is not a boolean already, the only way to get a boolean out of it is by using a comparison function.


The fact that there is exactly one false object in Lisp means that we can test whether a value is false extremely efficiently, even in the presence of any static type inference. It's just a machine instruction to compare the value's bit pattern to the NIL bit pattern.


What's the false value for my EmailAddress type?


Not necessarily every type, but just those that obviously look false: False, 0, null. Empty string iff it's a very high level language. Empty list iff it's a lisp.

But better yet, `if` should only accept a boolean expression. Passing a string to `if` should be a type error.


It's unclear why empty strings or lists should be considered false yet it's somehow evil for empty sets to be considered false.

The purpose of type checking is to help people write correct programs.

It's unclear that requiring "if {string}.isempty() ..." does that better than "if {string} ..." in a strongly-typed language.


0 shouldn't be false. 0 compared to something can be false. Null isn't false. Null compared to something can be false.

The entire concept of "falsey" makes my teeth ache.

Non-boolean expressions in a boolean context is asking for a runtime error.


> Non-boolean expressions in a boolean context is asking for a runtime error.

And yet, it doesn't, in strongly-typed languages that define which values are "false" and "true" for types such as strings, sets, dictionaries, and yes, even numbers.


Clojure, like Ruby, does falseyness correctly in that there's only 2 cases of false: nil and false. Everything else is true. This is easy to reason about, and it allows for nils to "flow" through your logic (especially with nil-punning where in the context of collections, nil is also an empty sequence).


Something even easier to reason about: Only true is true, and only false is false!


I disagree - presence/absence allows reuse of clear and terse operators, reduces the need for an extra null coalescence operator when logical or can do the job as well, and enables better developer experience in places like options hashes where "not set" always means the same thing as "false".


I think modern languages have demonstrated that the dev UX difference between allowing "falsey"/"truthy" and not allowing it can be reduced to be quite small. Meanwhile, consider that the bugs introduced by implicitly considering things false/true are arguably a type of "bad dev UX".


I can't think of a case, personally, where conflating false and nil/null/undefined has created a bug. I can think of many for, like, JS treating the empty string or zero as false, but not for specifically null/undefined. Can you provide an example?


You're talking about hard true/false, in which you are correct. The person you're replying to is talking about "Truthy"/"Falsy", which is different than the hard true/false you're talking about.


I read the parent as, slightly sarcastically, saying "it's easiest to reason when the concept of truthy/falsy is completely absent".


I do not agree that treating nil (or zero, or an empty collection) as false is "correct".


Colloquially, “nil” and “zero” are the same thing. Presumably not the case in clojure.


True, however in software there are many cases in which they're different. Simple example:

How many checked luggage items do you have?

nil means the user hasn't answered the question

0 means they saw the question and don't have any checked luggage.


in your example, the end result is the same. the user is not interested in luggage.


The difference between an answered and unanswered question is absolutely not the same. Nil means the user has not told us how much luggage they have. 0 means the user has 0 luggage. We can assume a lack of response means zero, or we can mark the field as required, in which case the UI needs to prompt the user for a response in the nil case but not the 0 case. This is an extremely simple, common example. There are many cases where nil is not equivalent to 0 semantically or logically.


no that's you assuming since that part was not filled.


Add Lua to the languages which chose to be sensible about this.


And Toit. Only null and false are falsey.


That's doing it almost correctly. The correct way to do truthiness is not to do it at all. `nil` should not be automatically coerced to `bool`.


This is a baloney C-biased question. The real question is why nil is falsey (ALONG with false) in Clojure.

To my mind this is an incredibly boneheaded mistake in a modern language. Common Lisp can be excused as it is old and has an even older ancestry. nil has been false in common lisp and its ancestors since forever, but this is an accident of history, and nobody claims it to be a good idea, as it is a source of many bugs. And indeed some lisp functions have to have additional gizmos added to them to work around it.

Scheme fixed this with real true and false constants. And then clojure, um, decided to combine the worst of both worlds, by making TWO things be false.


> as it is a source of many bugs

Citation needed.

I worked with two languages that have exactly this same behavior (Clojure and Ruby), and both of them this is not the source of any bug that I can remember [1].

Also, I would argue that Clojure as a language is much more ergonomic thanks to `nil` being `falsey`, so this is all about the trade-offs between safety and ergonomics. Considering that, IMO, Clojure without nil being falsey would lose much more than Clojure being a more safe language, I really don't seem much problem.

[1]: well, there is one, where one of our internal tooling had this function called `assoc-if` that it would assoc a key to a map if truthy, however this also meant that when the key was of boolean type and false we would also not assoc this; but we eventually introduced `assoc-some` that does what you expect.


Two things being false can be quite pleasant, but making the empty list nil isn't worth it.

Lua gets this right, `nil` is the Ground State of Being, you get it back in lieu of a value any time you try to find one.

This requires also having a `false` because logical conditions have to be streamed sometimes and you can't transport a key with a nil value by definition.

But an empty list/table being 'false' is convenient right until it isn't. It's a kludge.


Scheme's explicit #f is false and everything else is truthy takes some getting used to, but conversely look at Tcl where boolean values are any numeric value (0 is falsy) or the strings: true, false, yes, no, on, off


False and nil being two distinct values isn't Clojure's fault though. Or not directly. There are a bunch of trade offs that Clojure makes to have very direct interop with its host languages.


Clojure sits on the JVM. Why can't false be false and nil be NULL?

I know of no other JVM-based lisp system which makes this "tradeoff". Indeed Kawa, which is rather faster than Clojure in my experience, does not.


Clojure's nil on the JVM and Java's null are the same value. Are we misunderstanding each other, or am I missing something?


I think we're talking past each other. Clearly in Clojure nil and false are different as NULL and false are different in Java. But in Clojure, they're both falsey, a really stupid design decision: but in Java they are NOT both falsey, only false is.


Nope, it's a great design decision.


See also this question: https://stackoverflow.com/questions/5830571/why-did-father-o...

Which leads to this link: https://groups.google.com/g/clojure/c/OnagUrQZ1NE/m/Uwm8fvak...

Where Rich comments on the thinking behind this.

Also see https://clojure.org/reference/lisps , which compares Clojure with other Lisp dialects on this and other points


A good, illustrative case for why falsiness, of any kind, leads to disaster. Boolean operations should require explicit mapping to boolean types. Make the intent clear every time, instead of requiring the programmer to memorize umpteen different rules.


There's no disaster as far as I know. This is a old, well understood Lisp idiom. Clojure specifically has a ton of macros, functions and patterns around this concept, such as if-let, when-let, some->, looking up stuff in a map, just to name a few, much this goes under the umbrella of "nil-punning" and "logically true values" etc.

Caveats and boundaries: It is typically clear whether something can return a nil or accepts a nil value one versus when it doesn't. A good example would be string methods and other Java interop, where you will make sure you don't pass in a nil value, you are crossing language boundaries here so to speak. Another boundary of nil usage would be sequences, which will not evaluate to falsy, even if empty, so you explicitly would call seq or empty? on them.

The bottom line is that you are very aware of nil values and their falsiness in Clojure and that you program with them rather than against them.


>A good, illustrative case for why falsiness, of any kind, leads to disaster.

Yup. It took a decade of ES updates to overcome these issues with Javascript. The falsy zero in JS has probably created more bugs in aggregate than any other language feature in history.


I agree that using a boolean or calling a function that returns a boolean is the best case for readable code. I think that familiarity with a language leads to using the falsy logic rules without explicit boolean logic. When script programming falsy logic can be broad and useful but for software that needs to be robust explicit boolean logic can not be beat.


How about two rules, or if you absolutely insist, three?

"Values are falsey if they are `nil` or `false`, otherwise they are truthy".

It's hard for me to map this rule to 'disaster', either in principle or in practice. It means that `if number then` does what you expect, if you expect that maybe number exists, maybe it doesn't.


The “umpteen rules”, I presume, are the ones for all the languages you know and use, not just Clojure or what have you. I would wager most people don’t work in a single language bubble.

It’s a bit optimistic that we’d ever have most languages working under the same rules though, no matter how sane it is and how dubious the value of falseyness (and the terseness you can get from it) is.


Some people do work in a language bubble, probably even most developers by weight are producing exclusively one of Java, C#, and JavaScript, but let's leave that aside.

What's the absolute minimum level of mental imposition on an if statement? I'd say it's "this must take a boolean", but that implies compile-time types, and I'm not here to litigate the mere existence of dynamic languages today.

I would argue that the rule I sketched above is both the clearest and simplest rule which is compatible with a dynamic language. It imposes the least amount of mental burden in understanding or writing code.


Expanding "boolean-ness" and treating more things as expressions lets ruby give you more readable code. e.g.

  if user = db.query(id)
      user.update(...)
  end
and

  user = db.query(id) || raise "UserNotFound"


FYI your second example isn't actually valid ruby. You need to either wrap the arg for raise in parens:

    user = db.query(id) || raise("UserNotFound")
Or use the `or` keyword:

    user = db.query(id) or raise "UserNotFound"


Ah, thank you. I knew about the issue, but I actually changed it from an initial `or` because I had them mixed up.

Instead of changing the boolean rules, they should definitely address the different precedence rules for boolean operators : )


Inspired by this I wrote a post on the Julia discourse forum about the arbitrariness of truthiness in programming languages: https://discourse.julialang.org/t/on-the-arbitrariness-of-tr.... Julia requires actual `true` or `false` values in conditionals. Some people are annoyed coming from languages where they can test if a list is non-empty without having to write `isempty(a)`. But I have no regrets and this is a great example of why. Languages cannot agree on truthiness because it's arbitrary and just becomes a set of random rules you have to remember and which causes nasty bugs if you get them wrong.


zero being false is fine in statically typed languages like C, because there's still only one false value available, and you wouldn't be checking for it if you weren't expecting zero to be false

in dynamic languages, wanting to take advantage of brevity tricks with number zero or empty list or empty string being falsy can seem like a good idea, but gets confusing fast- different languages are going to have different falsy values, and you can easily get caught off guard when legitimately wanting to accept an 'empty' value

common lisp has NIL as its definition of the end of a list, which ends up working out fine since lists are so deeply embedded in the language design that it's hard to forget that they're there (any other kind of object is 'truthy', including any size of regular array)

scheme has a separate #f that is taken as the only falsy value, which is probably also fine

python and javascript are a mess, with different kinds of 'empty' or 'nothing' values being falsy whether you want them to be or not, which is the kind of helpfulness we don't need- where evaluating in boolean context means "if this value is present but not 'empty', in a way that is dependent on the objects type, and may change over time"


All values / types in Elixir apart from nil and false are truthy, like Clojure and Ruby.

Pattern matching in Elixir is so powerful, why would you even bother?


Falsey 0 in JavaScript is a big source of problems and is often linted against (forcing explicit checks, which then make code a bit uglier, but at least you won't get bitten by the case where 0 gets treated like null instead of a number)

Count yourself lucky

Edit: Thinking about it, this is probably a frame-shift from static to dynamic languages, because in the latter any value can be nil/null at any time. Whereas in C, C++, and even Java etc, unboxed numbers are always numbers. So coming from those you may not be used to thinking about the case where you have a nullable number and mean to check for null and accidentally also check for 0. But in dynamic languages like Clojure, it's a common situation to have a nullable number.


Because not every language is based on C semantics.

Ruby also does this, nil and false are the only falsey values.

Funny enough, NULL::bool = false in postgres is NULL - because of course it is.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: