Readit News logoReadit News
lmkg · 3 years ago
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.

zasdffaa · 3 years ago
> 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.

mikepurvis · 3 years ago
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.
Someone · 3 years ago
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.

mlazos · 3 years ago
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 ..
zasdffaa · 3 years ago
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.
anamax · 3 years ago
> 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?

Slackwise · 3 years ago
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).
tialaramex · 3 years ago
Something even easier to reason about: Only true is true, and only false is false!
eropple · 3 years ago
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".

Deleted Comment

capableweb · 3 years ago
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.
happytoexplain · 3 years ago
I do not agree that treating nil (or zero, or an empty collection) as false is "correct".
normie3000 · 3 years ago
Colloquially, “nil” and “zero” are the same thing. Presumably not the case in clojure.
ARandomerDude · 3 years ago
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.

samatman · 3 years ago
Add Lua to the languages which chose to be sensible about this.
ErikCorry · 3 years ago
And Toit. Only null and false are falsey.
IshKebab · 3 years ago
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`.
SeanLuke · 3 years ago
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.

m45t3r · 3 years ago
> 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.

samatman · 3 years ago
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.

pacaro · 3 years ago
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
dgb23 · 3 years ago
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.
SeanLuke · 3 years ago
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.

gavinpc · 3 years ago
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

mc4ndr3 · 3 years ago
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.
dgb23 · 3 years ago
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.

ramesh31 · 3 years ago
>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.

tmm84 · 3 years ago
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.
samatman · 3 years ago
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.

boardwaalk · 3 years ago
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.

JamesSwift · 3 years ago
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"

toomanybeersies · 3 years ago
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"

Deleted Comment

StefanKarpinski · 3 years ago
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.
smegsicle · 3 years ago
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"

weatherlight · 3 years ago
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?