I found out that some languages like C don't have support for boolean variables and programmers use integers with values of 0 and 1 instead. Is there any specific reason why some languages moved away from this other than improved readability?
Built in boolean types are there to more succinctly express true or false (most typically in condition statements). The bool goes directly to being able to write a conditional that is semantically clear - testing for and creating boolean results in the code. It would be more natural and clearer to be able to use a genuine boolean type.
Originally C used integral values (there is now a type
_Bool) with the condition that false was 0 (or NULL in pointer tests) and true was not false (not necessarily just 1). Which is generally all "well", but you then can't expressly/directly test for and store a true condition (only for a not false).
They are semantically different types, used for different purposes, so it's very useful for the type checker to be able to distinguish between them. Take this infamous bug, for example:
if (x = 1) ...
In most languages, the type checker will catch this, because it is expecting a boolean, but got an int (or unit/void in languages where assignments don't return their value). C is one of very few languages that permit this particular bug.
It proved very helpful to standardize them.
You'd think 0 is 'false' and 1 is 'true' is easy. However, it is also common to see -1 for 'true,' because in 2's complement notation, that is all 1's. The more generalized version is also used: 0 for false and non-zero for true. However, that leads to interesting situations where you can bitwise AND two non-zero values together and get a false value, so much consternation occurred.
In the end, the confusion of using different boolean conventions from library to library was sufficient. We now have bool to handle that.
(And even then, it doesn't always work if you get code from different compilers and try to link them!)
The main reasons are:
1. Use cases for booleans are extremely common.
I'll assume the well-known convention that 0 means false and 1 means true is enough proof of this.
2. We want type safety.
An integer can do everything that a boolean can, and a lot more, so it's not like we were missing any functionality.
But, if we used integers to represent booleans, then our "booleans" would be able to do things that booleans shouldn't be able to do (such as addition and subtraction), and they'd be able to interact with "real integers" in ways that don't make any sense (strictly speaking, true should not be equal to 1).
We usually use the term "type safety" to refer to issues like this. Using integers to represent booleans is not a "type-safe" solution.
Type safety is generally considered a very good thing, especially in statically typed languages where the compiler is effectively checking your program for type errors on every compilation. Given that
true == 42 is not a valid or meaningful comparison, it would be nice if the compiler simply did not allow it and forced us to go fix that piece of code. The "improved readability" you mention in your question is another typical benefit.
3. A user-defined type (i.e., a class or struct) cannot represent a boolean correctly, so it would have to be a primitive or built-in type
For many of us, this is the really interesting and non-obvious part. In the case of C++, this issue is explained quite thoroughly by Herb Sutter's Guru of the Week #26. I'll quote his summary here:
A typedef ... bool wouldn't allow overloading on bool.
A #define bool wouldn't allow overloading either and would wreak the usual havoc of #defines.
An enum bool would allow overloading but couldn't be automatically converted from a conditional expression (as in "b = (i == j);").
A class bool would allow overloading but wouldn't let a bool object be tested in conditions (as in "if( b )") unless it provided an automatic conversion to something like int or void*, which would wreak the usual havoc of automatic conversions.
Yes, we really did need a builtin bool! And, finally, there's one more thing (related to overloading) that we couldn't have done otherwise, either, except perhaps with [a class]: specify that conditional expressions have type bool.
So C++ can't do non-primitive booleans. At the risk of oversimplifying the differences between langauges, if you can't do something in C++, there's a good chance you can't do it in any of the other mainstream OOP langauges. I believe the C# implementation would get exactly as close as the C++ one, but no closer, and Java would be significantly worse off since it deliberately doesn't offer operator overloading or implicit conversions.