How do I work with Boolean values?

← Getting Started · Ref: Q30

In most languages, a Boolean is either true or false. In EK9, a Boolean has three states: true, false, and unset. An unset Boolean is neither true nor false. It means 'not yet determined' or 'we do not know yet'. This is a fundamental difference.

Consider a medical test result. 'Negative' (false) and 'we have not run the test yet' (unset) are completely different states. In Java, Python, or Go, an uninitialised boolean defaults to false, which silently conflates 'no' with 'unknown'. EK9 makes the distinction explicit.

CREATING BOOLEANS

  ready <- true
  failed <- false
  unknown <- Boolean()

The first two are set. The third exists but is unset. Use ? to check:

  if ready?
    stdout.println("ready has a value")
  if unknown?
    stdout.println("this will not print")

LOGICAL OPERATORS

EK9 provides four logical operators:

  - and: Logical conjunction: true and false gives false
  - or: Logical disjunction: true or false gives true
  - xor: Exclusive or: true xor true gives false, true xor false gives true
  - not: Logical negation: written as 'not' keyword
  - ~: Complement operator: ~true gives false, ~false gives true

These operators work on set Boolean values. An unset operand makes the result unset, propagating the 'unknown' state rather than silently treating it as false - unless the LEFT operand already decides the result: 'and' and 'or' short-circuit, so false and X is a set false, and true or X is a set true, whatever X is. With the unset value on the left (unset and false, unset or true), or with xor and ~, the result is unset.

COMPARISON OPERATORS

  ==    Equality: true == true gives true
  <>    Inequality: true <> false gives true
  <=>   Comparison: returns Integer for ordering

OTHER OPERATORS

  - $: String conversion: $ready gives "true"
  - $$: JSON conversion
  - :=: Copy assignment
  - :^: Replace assignment
  - :~: Merge assignment
  - +=: Accumulate (logical or)
  - +: Addition (logical or, pure)
  - |: Pipeline operator

BOOLEAN FROM STRING

You can construct a Boolean from a String:

  fromText <- Boolean("true")
  invalid <- Boolean("maybe")

Only the text 'true' (in any case) gives true. Any other set String, 'maybe' included, gives a SET false - no exception is thrown, and the result is not unset. Only an unset String gives an unset Boolean. So Boolean("maybe")? is true and its value is false; validate the text first if 'maybe' must not mean false.

WHY TRI-STATE MATTERS

In Java: boolean defaults to false. Did the user decline, or did we forget to ask?
In Python: bool defaults to False. Is the flag off, or was it never configured?
In Go: bool defaults to false. Is the check complete with a negative result, or was it never performed?
In JavaScript: undefined, null, and false are all falsy but mean different things.

EK9 eliminates this ambiguity. Boolean() is unset. Boolean(true) is true. Boolean(false) is false. Three distinct states, three distinct meanings.

For locale-specific Boolean display, see Q44 (How does the Locale type work in EK9?).

See also Q29 (What is the default value of an unset variable?) for the full tri-state model. See Q47 for how the ? operator is used with Optional types. See Q244 for boolean and bitwise operators (and, or, xor, ~).

Example

defines module qa.boolean.values

  defines program
    BooleanDemo()
      stdout <- Stdout()

      // Three states
      ready <- true
      failed <- false
      unknown <- Boolean()

      stdout.println(`ready: ${ready}, is set: ${ready?}`)
      stdout.println(`failed: ${failed}, is set: ${failed?}`)
      stdout.println(`unknown is set: ${unknown?}`)

      // Logical operators
      both <- ready and failed
      either <- ready or failed
      exclusive <- ready xor failed
      flipped <- ~ready

      stdout.println(`and: ${both}, or: ${either}`)
      stdout.println(`xor: ${exclusive}, complement: ${flipped}`)

      // Boolean from String
      fromText <- Boolean("true")
      invalid <- Boolean("maybe")
      //Any text other than 'true' gives a SET false, not an unset Boolean
      stdout.println(`from text: ${fromText?}, invalid set: ${invalid?}, value: ${invalid}`)

      // Assign-if-unset
      unknown :=? true
      stdout.println(`unknown after guard: ${unknown}`)

Common mistakes

E07620 — EK9 logical operators require Boolean operands. Using an Integer where a Boolean is expected triggers E07620 — type incompatibility. Use a comparison operator to produce a Boolean. See ek9 -h E07620 for details.

Incorrect:

both <- ready and 42

Correct:

both <- ready and failed
Other ways to ask this
  • What logical operators does EK9 have?
  • Can a Boolean be unset in EK9?
  • How does Boolean tri-state work in EK9?

Coming from another language?

Java: boolean defaults to false, Boolean wrapper can be null (NPE risk). Python: bool defaults to False, truthy/falsy rules for other types. Go: bool defaults to false, no nullable bool. JavaScript: undefined/null/false all falsy with different semantics. Rust: bool is true/false, Option<bool> for tri-state. C#: bool defaults to false, Nullable<bool> for tri-state. EK9: Boolean has three states (true, false, unset), logical operators (and, or, xor, not, ~), unset propagates through operations unless a short-circuiting left operand already decides the result (false and X, true or X).

Keywords: true, tristate, xor, tri-state, not, migrate, unset, bool, start, complement, beginner, first, or, boolean, intro, false, and, logical