Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Evil Coding Incantations (9tabs.com)
124 points by Garbage on Dec 25, 2017 | hide | past | favorite | 80 comments


I know I'm biased by familiarity, but I really like that 0 is truthy in Ruby. I often find that I want to check whether there is a number there at all, whether it is 0 or not, and a truthiness test works well for that.

Is there a good reason, other than expectations brought from other langs, why 0 should be falsey?


0 for false goes all the way back to Boole. He used multiplying for AND and adding for OR. You can also think of a Boolean as an index into a list of choices: “if t then c else a” could be written “[a, c][t]”. This is sometimes a good way to think about booleans, not just a hack: for instance it leads you to multiway decision diagrams to generalize BDDs.

Still I prefer a distinct Boolean type, too.


I hate truthy- and falsy-ness in general. It just seems like it's ripe for messing up expectations. Why not just get an actual boolean?


I like the line in the Zen of Python, "Explicit is better than implicit."

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).


But i assume you're ok with upcasting an int to a double. That is, unless you make the Haskell Prelude. There is a real and useful sense in which booleans are a subtype of integers and i think its not unreasonable to automatically cast between them. Can't say if it's a good idea or not though.


There is a real and useful sense in which _everything_ is an integer; see Godel numbering. There's also a real and (debatably) useful sense in mathematics in which everything is a set. Even still, we prefer to impose structure and convention because it helps us understand what's going on, by restricting the kinds of things we need to be able to expect.

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`.


I'd love to hear of a case where anyone has actually computed a Goedel number.

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.


That is not the only homomorphism. You could just as well have 0 ~ True and 1 ~ False, where && and || are addition and multiplication. Booleans don't have enough algebraic structure to nail down a unique embedding. Integers and reals don't have the same problem, because there's a unique ring homomorphism between them.


Blessing noncanonical structures and mappings is indeed evil. Let's take this to its logical conclusion and banish type classes.


I'm not sure what you mean by that. Type classes are all about making canonical structures explicit.


Type class instances are only “canonical” by fiat. Once you elaborate type classes into record-passing, by no means are the resulting instance types uniquely inhabited.

This is just as bad as the existence of truthy or falsy values.


You should get a type error if you try to use an int in boolean logic.

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.


That’s how Swift does it. It may annoy you now and then but a few seconds later you are glad being forced to be explocit.


But I think we can agree that truth should be truth and that false should be false and that we shouldn't be able to assign false to truth.


Is alternate truth ternary?


Just as much as a coin landing on its edge is ternary.


I'd love a language that implemented 'mu' [0]

0: http://wiki.c2.com/?MuAnswer


Java has

    Boolean mu = null;


Java's `null` denotes the well known `FILE_NOT_FOUND` value in mathematics and programming language semantics.


But, does Java have the sound of one hand clapping? That is the question.


Thank you for the read.


If your language has “low level” integers that can take on exactly 2^n values, all bit patters are numbers, so there is no need to “check whether there is a number there at all”.

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.


> because assembly doesn’t have boolean.

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.


They're actually Boolean types created by a bitfield mapping of an integer.


Ints. :)


Is there a good reason, other than expectations brought from other langs, why 0 should be falsey?

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.


Hmm. If you have a measurement of zero, you still have a measurement, which is more different to having no measurement at all than it is to having a measurement of 1. Especially if you are measuring centigrade!


Is there a measure at all? This is a different question than the value of the measurement. (Though "I don't know" doesn't map very well to rational numbers... maybe a float NaN could be abused).

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.


>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?


I only received a downvote. To clarify, I mean that most people know what it means to "have -1 apples" (it would mean you owe one apple.) So why should negative values be truthful, if it's a simple analogy with the real world?


So why should negative values be truthful, if it's a simple analogy with the real world?

Do you owe anything if you have 0 apples?


>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. :)


I agree completely. This behavior is internally consistent within Ruby and the standard library and allows for some of the terseness of Ruby that I love. Things like =~ which return the position of the first regex match on a string (which could be 0 position).


> Is there a good reason, other than expectations brought from other langs, why 0 should be falsey?

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.


More fundamentally, you want to be able to distinguish the representation(s) of true from false as efficiently as possible, so to work on most machines the choice is either zero vs nonzero or negative vs nonnegative.


Nope. JS is notoriously bad at this matter having several falsy values. At least Ruby keeps it at minimum, only false and nil.


I don't really like 0 being truthy or falsy. For 'true' or 'false' flags you could just have a bitset, and everything else can have specific tests for type or value. I actually kind of half suspect that you don't need booleans or 'truthy' things at all. Or null.

I'm probably wrong though.


If a function returns 0 when it succeeds and some nonzero value as an error code when it fails, then you can write

    if(myfunc(...)) { ...


This works fine until you have some other function that returns 0 on failure.


This has never been a problem for me and I've never heard of it being a problem for anyone else.


Success being false is definitely an odd pattern to me. I know I’m not the only one tripped up by it (‘canWrite()’ should return true on success...except if you want to return an error code by value). This is sort of fixed with C++ ‘enum class’, but obviously this isn’t always possible to use.


Well, if you're talking philosophy, then no. There's no reason.

It's a design decision, so it should be informed by the audience of the language.


Why not use an isNumeric() to check if there is a number?


If you don't allow uninitialised variables


To me this comment is actually good evidence for why 0 shouldn’t be truthy. The reason you gave sounds like an anti-pattern that can only exist because 0 is truthy; so a good reason to have a falsy 0 is that you couldn’t do what you said you often do. In my opinion type checking should be more explicit than you’re comment suggests.


Maybe it's an antipattern to take "if x" to mean "if x is a numeric value". But if so it's surely more of an antipattern to take it to mean "if x is a numeric value that is not zero". I think the way out of this sort of antipattern, if antipattern it is, is to get rid of the idea of numeric values being truthy or falsey at all. I think your problem is with the whole concept of truthiness.


More simple semantics are easier to understand. Ergo we should make the semantics as simple as possible.

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.)


"is" isn't equivalent to equality in Python. Is specifically checks the memory address, not equality. Equality is checked with "==" like in any other language. The author probably saw the is operator used somewhere and didn't know enough Python and figured that since && and || are reserved for bitwise operations whereas "and" and "or" are used for logical operations, "is" must also be the Python version of "==".


Java's == is not used for equality for objects either, so it's a valid comparison because Java's behaviour is equally unintuitive here. It's the same behaviour as is for objects.


Java's behavior feels like an almost deliberate trap, though. I have previously lost hours to bugs resulting from a refactoring that changed a value's type from int to Integer, and the resulting change in equality semantics.


That seems like such an anti-feature. I'd want the compiler to issue a warning for it by default and the IDE to ship with 'warnings are errors' by default.


Instead, we have some $$$$ static analysis tool that nags me endlessly about using Optional<T> private fields, but lets using the == operator for non-primitives sail past without comment.


I don't think the author meant to imply that "is" is equivalent to equality. I think the author was specifically trying to check the references to see where python stops coalescing integers.


I guess, but then it's also kind of uninteresting. Lists in Python are an array of pointers. That Python implements assignment differently is kind of an odd detail, but altering the value of one of the variables doesn't alter the value of the other. It's just sort of an under-the-hood implementation detail visible only through Python's introspection tools, not something that actually affects code. I tried it myself:

    a, b = [1, 1]
    a = 2
    print(a is b)
and got an output of false.


The point is that is is making those implementation details visible. That is absolutely going to affect code relying on those details. It won't affect good code, of course, but everyone writes stupid code in a while, and if it appears to work, nobody will fix it.


I am starting to hate languages that are not super picky and precise. We are working with C#, PHP and JavaScript and between them it's almost Impossible to remember what will evaluate as what. I would be perfectly fine if the only input to an if was true or false, no number, no strings.


Same here. I’ve used C for ages and now I’ve been doing a lot of Swift, where if statements only operate on Bool values. I find it much better to be explicit about any condition that isn’t literally true or false. The common style of accepting all types and defining a set of “falsey” values just encourages pointless shortcuts.


where if statements only operate on Bool values

I mostly use Asm and C, and I have the exact opposite opinion: I hate languages like that. It makes simple problems like "determine if more than 1 of 3 integers are greater than 4" require ridiculously verbose solutions.


How can it be ridiculously verbose? Converting an arbitrary expression into C’s notion of true or false just requires adding != 0 or != nil or similar.


Try to write something more succinct and elegant than this:

    return ((a > 4) + (b > 4) + (c > 4)) > 1;


  return [a, b, c].filter({ $0 > 4 }).count > 1
I disagree that your version is elegant. Succinct, yes, but I don’t think I’d like to leave that for the next guy to discover.


If you only do C you know this stuff. It gets confusing when you use a few other languages that all have slightly different semantics.


To me,

  [a, b, c].count(x -> x > 4) > 1
makes the intent of the code more immediately apparent.


I'll make a suggestion that's more verbose but maybe(?) more elegant (in the sense I'd argue it's more quickly evident what's being done and might conditionally improve performance):

  if (a > 4) {
    return b > 4 || c > 4
  } else { 
    return b > 4 && c > 4
  }
My argument is that the boolean operators make it more evident immediately that the return value is a boolean value (well, a 1 or 0 in C). Also this doesn't explicitly check the case of all the checks evaluating to true, but it doesn't need to because in that event then the if condition evaluates to true and the return value is true, which is the correct return value in that situation.

You could use the ternary operator in place of the if syntax to reduce the verbosity, but I find the smaller statements easier to read quickly.

Additionally, I think(?) this code would have an ever-so-slight performance advantage in some situations since in the event a > 4 and b > 4 evaluate to true, then c > 4 doesn't get evaluated. Same if a > 4 but b < 4.

Not with a computer I can easily run C on at the moment to check these claims, but I welcome input on the elegance/performance/readability comparisons.


Branch prediction might make this slower. (And if you have to add a fourth variable it will be harder to extend).


That example has a boolean operator in it, so in a strict language will already be a boolean.

A better example would be one that returned 0, so you’d have to add `== 0` to it.

I kind of understand your point with asm, but with higher level languages we’re optimising for maintenance rather than performance.


    max(a, b, c) > 4


This isn’t correct for a=5, b=0, c=0. It would return true while OP’s example would return false


Oh, I missed the "more than 1 integer". I was wondering why the other solutions were so awkward, thus needed to share my immense wisdom. I feel shame.


If it makes you feel any better, there are surely other people who thought that way, and they'll be enlightened upon reading this comment chain.


You might like Haskell. The only possible conditional is a boolean and in general it's quite picky and precise about everything (with a few notable exceptions like partial functions).


I also think i would like Haskell. However, it seems there is not much demand for it in the market.


A common JS pitfall: never ever use == in JS. Always use ===


The item in the list supplied for index starting at 1 shouldn't be there.

There are valid reasons for using 1 instead of 0 for the first element. It all hinges on whether the address is being considered or the first element is being considered.

One could consider languages that use 0 as the alternative referencing for the end of some value that can be indexed and -1 as the last item of that value.


The “Goes Down To” Operator

Incredible elegant. If I used loops, I'd use that, too!


In C, these are legal:

    while (a -->> k --> 0 <-- b <<-- c);

    k = a <- - - - - b;


What year did you win IOCCC?


How do you not use loops?


Functional programming. Loops usually have some kind of mutated state and in FP you use constructs like map and reduce. No for or while.


    def a
        def a
            "use ruby"
        end
        "dont"
    end
    a
    > "dont"
    a
    > "use ruby
:(




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

Search: