|
|
|
@ -1,7 +1,7 @@ |
|
|
|
|
--- |
|
|
|
|
title: "How Many Values Does a Boolean Have?" |
|
|
|
|
date: 2020-08-20T18:37:50-07:00 |
|
|
|
|
draft: ["Java", "Haskell"] |
|
|
|
|
date: 2020-08-21T23:05:55-07:00 |
|
|
|
|
tags: ["Java", "Haskell", "C and C++"] |
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
A friend of mine recently had an interview for a software |
|
|
|
@ -38,12 +38,12 @@ in Haskell, and `1` and `0` in C. A boolean value is either true or false. |
|
|
|
|
So, what's there to think about? There are a few things, _ackshually_. |
|
|
|
|
Let's explore them, starting from the theoretical perspective. |
|
|
|
|
|
|
|
|
|
### What's a Type, Anyway? |
|
|
|
|
### Types, Values, and Expressions |
|
|
|
|
Boolean, or `bool`, is a type. Broadly speaking, a type |
|
|
|
|
is a property of _something_ that defines what the _something_ |
|
|
|
|
means and what you can do with it. That _something_ can be |
|
|
|
|
several things; for our purposes, it can either be an |
|
|
|
|
_expression_ in a programming language (in the form of `fact(n)`) |
|
|
|
|
_expression_ in a programming language (like those in the form `fact(n)`) |
|
|
|
|
or a value in that same programming language (like `5`). |
|
|
|
|
|
|
|
|
|
Dealing with values is rather simple. Most languages have finite numbers, |
|
|
|
@ -81,7 +81,7 @@ int meaningOfLife() { |
|
|
|
|
|
|
|
|
|
No, wait, doesn't say "stack overflow" just yet. That's no fun. |
|
|
|
|
And anyway, this is technically a tail call, so maybe our |
|
|
|
|
C++ compiler can avoid growing the stack And indeed, |
|
|
|
|
C++ compiler can avoid growing the stack. And indeed, |
|
|
|
|
flicking on the `-O2` flag in this [compiler explorer example](https://godbolt.org/z/9cv4nY), |
|
|
|
|
we can see that no stack growth is necessary: it's just |
|
|
|
|
an infinite loop. But `meaningOfLife` will never return a value. One could say |
|
|
|
@ -131,7 +131,7 @@ It turns out to be convenient -- formally -- to treat the result of a diverging |
|
|
|
|
as its own value. This value is usually called 'bottom', and written as \\(\\bot\\). |
|
|
|
|
Since in most programming languages, you can write a nonterminating expression or |
|
|
|
|
function of any type, this 'bottom' is included in _all_ types. So in fact, the |
|
|
|
|
set of possible values for `unsigned int`: \\(\\bot, 0, 1, 2, ...\\) and so on. |
|
|
|
|
possible values of `unsigned int` are \\(\\bot, 0, 1, 2, ...\\) and so on. |
|
|
|
|
As you may have by now guessed, the same is true for a boolean: we have \\(\\bot\\), `true`, and `false`. |
|
|
|
|
|
|
|
|
|
### Haskell and Bottom |
|
|
|
@ -173,7 +173,7 @@ They can then compile their program; the typechecker will find any mistakes |
|
|
|
|
they've made so far, but, since the type of `undefined` can be _anything_, |
|
|
|
|
that part of the program will be accepted without second thought. |
|
|
|
|
|
|
|
|
|
The language `Idris` extends this practice with the idea of typed holes, |
|
|
|
|
The language Idris extends this practice with the idea of typed holes, |
|
|
|
|
where you can leave fragments of your program unwritten, and ask the |
|
|
|
|
compiler what kind of _thing_ you need to write to fill that hole. |
|
|
|
|
|
|
|
|
@ -185,8 +185,8 @@ really count as a value, since it's just a nonterminating |
|
|
|
|
expression. What you're doing is a kind of academic autofellatio. |
|
|
|
|
|
|
|
|
|
Alright, I can accept this criticism. Perhaps just calling a nonterminating |
|
|
|
|
function a value _is_ far-fetched (even though denotational semantics |
|
|
|
|
_do_ extend types with \\(\\bot\\)). But denotational semantics is not |
|
|
|
|
function a value _is_ far-fetched (even though in [denotational semantics](https://en.wikipedia.org/wiki/Denotational_semantics) |
|
|
|
|
we _do_ extend types with \\(\\bot\\)). But denotational semantics are not |
|
|
|
|
the only place where types are implicitly extend with an extra value; |
|
|
|
|
let's look at Java. |
|
|
|
|
|
|
|
|
@ -195,13 +195,14 @@ core language level, any function or method that accepts a class can also take ` |
|
|
|
|
if `null` is not to that function or method's liking, it has to |
|
|
|
|
explicitly check for it using `if(x == null)`. |
|
|
|
|
|
|
|
|
|
Java's booleans are not, at first glance, classes. Unlike classes, which you have |
|
|
|
|
This `null` value does not at first interact with booleans. |
|
|
|
|
After all, Java's booleans are not classes. Unlike classes, which you have |
|
|
|
|
to allocate using `new`, you can just throw around `true` and `false` as you see |
|
|
|
|
fit. Also unlike classes, you can't assign `null` to a boolean value. |
|
|
|
|
The trouble is, the _generics_ part of Java, which allows you to write |
|
|
|
|
fit. Also unlike classes, you simply can't assign `null` to a boolean value. |
|
|
|
|
|
|
|
|
|
The trouble is, the parts of Java dealing with _generics_, which allow you to write |
|
|
|
|
polymorphic functions, can't handle 'primitives' like `bool`. If you want to have an `ArrayList` |
|
|
|
|
of something, that something _must_ be a class. |
|
|
|
|
|
|
|
|
|
But what if you really _do_ want an `ArrayList` of booleans? Java solves this problem by introducing |
|
|
|
|
'boxed' booleans: they're primitives wrapped in a class, called `Boolean`. This class |
|
|
|
|
can then be used for generics. |
|
|
|
@ -219,7 +220,7 @@ Boolean myFalse = false; |
|
|
|
|
Boolean myBool = null; |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
Beautiful, isn't it? And, unlike Haskell, where you can't _really_ |
|
|
|
|
Beautiful, isn't it? Better yet, unlike Haskell, where you can't _really_ |
|
|
|
|
check if your `Bool` is `undefined` (because you can't tell whether |
|
|
|
|
a non-terminating computation is as such), you can very easily |
|
|
|
|
check if your `Boolean` is `true`, `false`, or `null`: |
|
|
|
@ -249,17 +250,20 @@ while(test) test -= 1; |
|
|
|
|
|
|
|
|
|
This loop will run 255 times, thereby demonstrating |
|
|
|
|
that C has at least 255 values that can be used |
|
|
|
|
to represent the boolean `true`. There are other languages |
|
|
|
|
with the notion of 'truthy' and 'falsey' values. However, |
|
|
|
|
some of them differ from C in that they also extend this notion |
|
|
|
|
to apply to equality. In JavaScript: |
|
|
|
|
to represent the boolean `true`. |
|
|
|
|
|
|
|
|
|
There are other languages |
|
|
|
|
with this notion of 'truthy' and 'falsey' values, in which |
|
|
|
|
something not exactly `true` or `false` can be used as a condition. However, |
|
|
|
|
some of them differ from C in that they also extend this idea |
|
|
|
|
to equality. In JavaScript: |
|
|
|
|
|
|
|
|
|
```JavaScript |
|
|
|
|
console.assert(true == 1) |
|
|
|
|
console.assert(false == 0) |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
Then, there are still exactly two distinct values |
|
|
|
|
Then, there are still exactly two distinct boolean values |
|
|
|
|
modulo `==`. No such luck in C, though! We have 256 values that fit in `unsigned char`, |
|
|
|
|
all of which are also distinct modulo `==`. Our boolean |
|
|
|
|
variable can contain all of these values. And there is no |
|
|
|
@ -269,7 +273,7 @@ respite to be found with `enum`s, either. We could try define: |
|
|
|
|
enum bool { TRUE, FALSE }; |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
But unfortunately, all this does is define `bool` to be a numeric |
|
|
|
|
Unfortunately, all this does is define `bool` to be a numeric |
|
|
|
|
type that can hold at least 2 distinct values, and define |
|
|
|
|
numeric constants `TRUE` and `FALSE`. So in fact, you can |
|
|
|
|
_still_ write the following code: |
|
|
|
|